? log ? config.log ? GNUmakefile ? config.status ? doc/src/sgml/HISTORY ? src/Makefile.custom ? src/Makefile.global ? src/log ? src/backend/postgres ? src/backend/catalog/postgres.bki ? src/backend/catalog/postgres.description ? src/backend/utils/mb/conversion_procs/conversion_create.sql ? src/backend/utils/mb/conversion_procs/ascii_and_mic/libascii_and_mic.so.0.0 ? src/backend/utils/mb/conversion_procs/cyrillic_and_mic/libcyrillic_and_mic.so.0.0 ? src/backend/utils/mb/conversion_procs/euc_cn_and_mic/libeuc_cn_and_mic.so.0.0 ? src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/libeuc_jp_and_sjis.so.0.0 ? src/backend/utils/mb/conversion_procs/euc_kr_and_mic/libeuc_kr_and_mic.so.0.0 ? src/backend/utils/mb/conversion_procs/euc_tw_and_big5/libeuc_tw_and_big5.so.0.0 ? src/backend/utils/mb/conversion_procs/latin2_and_win1250/liblatin2_and_win1250.so.0.0 ? src/backend/utils/mb/conversion_procs/latin_and_mic/liblatin_and_mic.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_ascii/libutf8_and_ascii.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_big5/libutf8_and_big5.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/libutf8_and_cyrillic.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/libutf8_and_euc_cn.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/libutf8_and_euc_jp.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/libutf8_and_euc_kr.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/libutf8_and_euc_tw.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_gb18030/libutf8_and_gb18030.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_gbk/libutf8_and_gbk.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_iso8859/libutf8_and_iso8859.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/libutf8_and_iso8859_1.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_johab/libutf8_and_johab.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_sjis/libutf8_and_sjis.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_tcvn/libutf8_and_tcvn.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_uhc/libutf8_and_uhc.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_win1250/libutf8_and_win1250.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_win1256/libutf8_and_win1256.so.0.0 ? src/backend/utils/mb/conversion_procs/utf8_and_win874/libutf8_and_win874.so.0.0 ? src/bin/initdb/initdb ? src/bin/initlocation/initlocation ? src/bin/ipcclean/ipcclean ? src/bin/pg_config/pg_config ? src/bin/pg_controldata/pg_controldata ? src/bin/pg_ctl/pg_ctl ? src/bin/pg_dump/pg_dump ? src/bin/pg_dump/pg_restore ? src/bin/pg_dump/pg_dumpall ? src/bin/pg_encoding/pg_encoding ? src/bin/pg_resetxlog/pg_resetxlog ? src/bin/pgtclsh/pgtclsh ? src/bin/pgtclsh/pgtksh ? src/bin/psql/psql ? src/bin/scripts/createdb ? src/bin/scripts/dropdb ? src/bin/scripts/createlang ? src/bin/scripts/createuser ? src/bin/scripts/droplang ? src/bin/scripts/dropuser ? src/bin/scripts/clusterdb ? src/bin/scripts/vacuumdb ? src/include/pg_config.h ? src/include/stamp-h ? src/interfaces/ecpg/compatlib/libecpg_compat.so.1.1 ? src/interfaces/ecpg/ecpglib/libecpg.so.4.1 ? src/interfaces/ecpg/pgtypeslib/libpgtypes.so.1.1 ? src/interfaces/ecpg/preproc/ecpg ? src/interfaces/libpgtcl/libpgtcl.so.2.5 ? src/interfaces/libpq/libpq.so.3.2 ? src/pl/plperl/SPI.c ? src/pl/plperl/libplperl.so.0.0 ? src/pl/plpgsql/src/libplpgsql.so.1.0 ? src/pl/tcl/libpltcl.so.2.0 ? src/pl/tcl/modules/pltcl_loadmod ? src/pl/tcl/modules/pltcl_delmod ? src/pl/tcl/modules/pltcl_listmod ? src/test/regress/pg_regress ? src/test/regress/log ? src/test/regress/tmp_check ? src/test/regress/results ? src/test/regress/expected/copy.out ? src/test/regress/expected/create_function_1.out ? src/test/regress/expected/create_function_2.out ? src/test/regress/expected/misc.out ? src/test/regress/expected/constraints.out ? src/test/regress/sql/copy.sql ? src/test/regress/sql/create_function_1.sql ? src/test/regress/sql/create_function_2.sql ? src/test/regress/sql/misc.sql ? src/test/regress/sql/constraints.sql ? src/tools/thread/thread_test Index: doc/src/sgml/datatype.sgml =================================================================== RCS file: /cvsroot/pgsql-server/doc/src/sgml/datatype.sgml,v retrieving revision 1.135 retrieving revision 1.136 diff -c -r1.135 -r1.136 *** doc/src/sgml/datatype.sgml 1 Dec 2003 22:07:55 -0000 1.135 --- doc/src/sgml/datatype.sgml 20 Dec 2003 15:32:54 -0000 1.136 *************** *** 1,5 **** --- 1,5 ---- *************** *** 1785,1790 **** --- 1785,1841 ---- p should be between 0 and 6, and defaults to the precision of the input literal. + + + + Alternatively, interval values can be written as + ISO 8601 time intervals, using the "Format with time-unit designators". + This format always starts with the character 'P', followed + by a string of values followed by single character time-unit designators. + A 'T' separates the date and time parts of the interval. + + + + Format: PnYnMnDTnHnMnS + + + In this format, 'n' gets replaced by a number, and + Y represents years, + M (in the date part) months, + D months, + H hours, + M (in the time part) minutes, + and S seconds. + + + + + Interval Example + + + + Traditional + ISO-8601 time-interval + + + + + 1 month + P1M + + + 1 hour 30 minutes + PT1H30M + + + 2 years 10 months 15 days 10 hours 30 minutes 20 seconds + P2Y10M15DT10H30M20S + + + +
+ + *************** *** 1941,1946 **** --- 1992,2002 ---- regional style 17.12.1997 07:37:16.00 PST + + ISO8601basic + ISO 8601 basic format + 19971217T073716-08 + *************** *** 1995,2000 **** --- 2051,2061 ---- quantity unit ... days hours:minutes:sekunden + + + + If the datestyle is set to iso8601basic, the interval + output is a ISO-8601 time interval with time-unit designator (like P1Y6M or PT23H59M59S). Index: src/backend/commands/variable.c =================================================================== RCS file: /cvsroot/pgsql-server/src/backend/commands/variable.c,v retrieving revision 1.90 retrieving revision 1.91 diff -c -r1.90 -r1.91 *** src/backend/commands/variable.c 29 Nov 2003 19:51:48 -0000 1.90 --- src/backend/commands/variable.c 20 Dec 2003 15:32:54 -0000 1.91 *************** *** 9,15 **** * * * IDENTIFICATION ! * $PostgreSQL: pgsql-server/src/backend/commands/variable.c,v 1.90 2003/11/29 19:51:48 pgsql Exp $ * *------------------------------------------------------------------------- */ --- 9,15 ---- * * * IDENTIFICATION ! * $PostgreSQL: pgsql-server/src/backend/commands/variable.c,v 1.91 2003/12/20 15:32:54 momjian Exp $ * *------------------------------------------------------------------------- */ *************** *** 82,88 **** /* Ugh. Somebody ought to write a table driven version -- mjl */ ! if (strcasecmp(tok, "ISO") == 0) { newDateStyle = USE_ISO_DATES; scnt++; --- 82,93 ---- /* Ugh. Somebody ought to write a table driven version -- mjl */ ! if (strcasecmp(tok, "ISO8601BASIC") == 0) ! { ! newDateStyle = USE_ISO8601BASIC_DATES; ! scnt++; ! } ! else if (strcasecmp(tok, "ISO") == 0) { newDateStyle = USE_ISO_DATES; scnt++; *************** *** 197,202 **** --- 202,210 ---- { case USE_ISO_DATES: strcpy(result, "ISO"); + break; + case USE_ISO8601BASIC_DATES: + strcpy(result, "ISO8601BASIC"); break; case USE_SQL_DATES: strcpy(result, "SQL"); Index: src/backend/utils/adt/datetime.c =================================================================== RCS file: /cvsroot/pgsql-server/src/backend/utils/adt/datetime.c,v retrieving revision 1.121 retrieving revision 1.122 diff -c -r1.121 -r1.122 *** src/backend/utils/adt/datetime.c 17 Dec 2003 21:45:44 -0000 1.121 --- src/backend/utils/adt/datetime.c 20 Dec 2003 15:32:54 -0000 1.122 *************** *** 8,14 **** * * * IDENTIFICATION ! * $PostgreSQL: pgsql-server/src/backend/utils/adt/datetime.c,v 1.121 2003/12/17 21:45:44 tgl Exp $ * *------------------------------------------------------------------------- */ --- 8,14 ---- * * * IDENTIFICATION ! * $PostgreSQL: pgsql-server/src/backend/utils/adt/datetime.c,v 1.122 2003/12/20 15:32:54 momjian Exp $ * *------------------------------------------------------------------------- */ *************** *** 37,42 **** --- 37,43 ---- static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel); static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm); static void TrimTrailingZeros(char *str); + static int DecodeISO8601Interval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec); int day_tab[2][13] = { *************** *** 2888,2893 **** --- 2889,3134 ---- } + /* + * A small helper function to avoid cut&paste code in DecodeIso8601Interval + */ + static void adjust_fval(double fval,struct tm * tm, fsec_t *fsec, int scale) + { + int sec; + fval *= scale; + sec = fval; + tm->tm_sec += sec; + #ifdef HAVE_INT64_TIMESTAMP + *fsec += ((fval - sec) * 1000000); + #else + *fsec += (fval - sec); + #endif + } + + + /* DecodeISO8601Interval() + * + * Check if it's a ISO 8601 Section 5.5.4.2 "Representation of + * time-interval by duration only." + * Basic extended format: PnYnMnDTnHnMnS + * PnW + * For more info. + * http://www.astroclark.freeserve.co.uk/iso8601/index.html + * ftp://ftp.qsl.net/pub/g1smd/154N362_.PDF + * + * Examples: P1D for 1 day + * PT1H for 1 hour + * P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min + * + * The first field is exactly "p" or "pt" it may be of this type. + * + * Returns -1 if the field is not of this type. + * + * It pretty strictly checks the spec, with the two exceptions + * that a week field ('W') may coexist with other units, and that + * this function allows decimals in fields other than the least + * significant units. + */ + int + DecodeISO8601Interval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec) + { + char *cp; + int fmask = 0, + tmask; + int val; + double fval; + int arg; + int datepart; + + /* + * An ISO 8601 "time-interval by duration only" must start + * with a 'P'. If it contains a date-part, 'p' will be the + * only character in the field. If it contains no date part + * it will contain exactly to characters 'PT' indicating a + * time part. + * Anything else is illegal and will be treated like a + * traditional postgresql interval. + */ + if (!(field[0][0] == 'p' && + ((field[0][1] == 0) || (field[0][1] == 't' && field[0][2] == 0)))) + { + return -1; + } + + + /* + * If the first field is exactly 1 character ('P'), it starts + * with date elements. Otherwise it's two characters ('PT'); + * indicating it starts with a time part. + */ + datepart = (field[0][1] == 0); + + /* + * Every value must have a unit, so we require an even + * number of value/unit pairs. Therefore we require an + * odd nubmer of fields, including the prefix 'P'. + */ + if ((nf & 1) == 0) + return -1; + + /* + * Process pairs of fields at a time. + */ + for (arg = 1 ; arg < nf ; arg+=2) + { + char * value = field[arg ]; + char * units = field[arg+1]; + + /* + * The value part must be a number. + */ + if (ftype[arg] != DTK_NUMBER) + return -1; + + /* + * extract the number, almost exactly like the non-ISO interval. + */ + val = strtol(value, &cp, 10); + + /* + * One difference from the normal postgresql interval below... + * ISO 8601 states that "Of these, the comma is the preferred + * sign" so I allow it here for locales that support it. + * Note: Perhaps the old-style interval code below should + * allow for this too, but I didn't want to risk backward + * compatability. + */ + if (*cp == '.' || *cp == ',') + { + fval = strtod(cp, &cp); + if (*cp != '\0') + return -1; + + if (val < 0) + fval = -(fval); + } + else if (*cp == '\0') + fval = 0; + else + return -1; + + + if (datepart) + { + /* + * All the 8601 unit specifiers are 1 character, but may + * be followed by a 'T' character if transitioning between + * the date part and the time part. If it's not either + * one character or two characters with the second being 't' + * it's an error. + */ + if (!(units[1] == 0 || (units[1] == 't' && units[2] == 0))) + return -1; + + if (units[1] == 't') + datepart = 0; + + switch (units[0]) /* Y M D W */ + { + case 'd': + tm->tm_mday += val; + if (fval != 0) + adjust_fval(fval,tm,fsec, 86400); + tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY)); + break; + + case 'w': + tm->tm_mday += val * 7; + if (fval != 0) + adjust_fval(fval,tm,fsec,7 * 86400); + tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY)); + break; + + case 'm': + tm->tm_mon += val; + if (fval != 0) + adjust_fval(fval,tm,fsec,30 * 86400); + tmask = DTK_M(MONTH); + break; + + case 'y': + /* + * Why can fractional months produce seconds, + * but fractional years can't? Well the older + * interval code below has the same property + * so this one follows the other one too. + */ + tm->tm_year += val; + if (fval != 0) + tm->tm_mon += (fval * 12); + tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR)); + break; + + default: + return -1; /* invald date unit prefix */ + } + } + else + { + /* + * ISO 8601 time part. + * In the time part, only one-character + * unit prefixes are allowed. If it's more + * than one character, it's not a valid ISO 8601 + * time interval by duration. + */ + if (units[1] != 0) + return -1; + + switch (units[0]) /* H M S */ + { + case 's': + tm->tm_sec += val; + #ifdef HAVE_INT64_TIMESTAMP + *fsec += (fval * 1000000); + #else + *fsec += fval; + #endif + tmask = DTK_M(SECOND); + break; + + case 'm': + tm->tm_min += val; + if (fval != 0) + adjust_fval(fval,tm,fsec,60); + tmask = DTK_M(MINUTE); + break; + + case 'h': + tm->tm_hour += val; + if (fval != 0) + adjust_fval(fval,tm,fsec,3600); + tmask = DTK_M(HOUR); + break; + + default: + return -1; /* invald time unit prefix */ + } + } + fmask |= tmask; + } + + if (*fsec != 0) + { + int sec; + + #ifdef HAVE_INT64_TIMESTAMP + sec = (*fsec / INT64CONST(1000000)); + *fsec -= (sec * INT64CONST(1000000)); + #else + TMODULO(*fsec, sec, 1e0); + #endif + tm->tm_sec += sec; + } + return (fmask != 0) ? 0 : -1; + } + + /* DecodeInterval() * Interpret previously parsed fields for general time interval. * Returns 0 if successful, DTERR code if bogus input detected. *************** *** 2897,2903 **** --- 3138,3148 ---- * * Allow ISO-style time span, with implicit units on number of days * preceding an hh:mm:ss field. - thomas 1998-04-30 + * + * Allow ISO-8601 style "Representation of time-interval by duration only" + * of the format 'PnYnMnDTnHnMnS' and 'PnW' - ron 2003-08-30 */ + int DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec) { *************** *** 2922,2927 **** --- 3167,3189 ---- tm->tm_sec = 0; *fsec = 0; + /* + * Check if it's a ISO 8601 Section 5.5.4.2 "Representation of + * time-interval by duration only." + * Basic extended format: PnYnMnDTnHnMnS + * PnW + * http://www.astroclark.freeserve.co.uk/iso8601/index.html + * ftp://ftp.qsl.net/pub/g1smd/154N362_.PDF + * Examples: P1D for 1 day + * PT1H for 1 hour + * P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min + * + * The first field is exactly "p" or "pt" it may be of this type. + */ + if (DecodeISO8601Interval(field,ftype,nf,dtype,tm,fsec) == 0) { + return 0; + } + /* read through list backwards to pick up units before values */ for (i = nf - 1; i >= 0; i--) { *************** *** 2999,3004 **** --- 3261,3267 ---- if (type == IGNORE_DTF) type = DTK_SECOND; + /* should this allow ',' for locales that use it ? */ if (*cp == '.') { fval = strtod(cp, &cp); *************** *** 3370,3375 **** --- 3633,3648 ---- -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); break; + case USE_ISO8601BASIC_DATES: + /* compatible with ISO date formats */ + if (tm->tm_year > 0) + sprintf(str, "%04d%02d%02d", + tm->tm_year, tm->tm_mon, tm->tm_mday); + else + sprintf(str, "%04d%02d%02d %s", + -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); + break; + case USE_SQL_DATES: /* compatible with Oracle/Ingres date formats */ if (DateOrder == DATEORDER_DMY) *************** *** 3525,3530 **** --- 3798,3848 ---- } break; + case USE_ISO8601BASIC_DATES: // BASIC + /* Compatible with ISO-8601 date formats */ + + sprintf(str, "%04d%02d%02dT%02d%02d", + ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)), + tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); + + /* + * Print fractional seconds if any. The field widths here + * should be at least equal to MAX_TIMESTAMP_PRECISION. + * + * In float mode, don't print fractional seconds before 1 AD, + * since it's unlikely there's any precision left ... + */ + #ifdef HAVE_INT64_TIMESTAMP + if (fsec != 0) + { + sprintf((str + strlen(str)), "%02d.%06d", tm->tm_sec, fsec); + #else + if ((fsec != 0) && (tm->tm_year > 0)) + { + sprintf((str + strlen(str)), "%09.6f", tm->tm_sec + fsec); + #endif + TrimTrailingZeros(str); + } + else + sprintf((str + strlen(str)), "%02d", tm->tm_sec); + + if (tm->tm_year <= 0) + sprintf((str + strlen(str)), " BC"); + + /* + * tzp == NULL indicates that we don't want *any* time zone + * info in the output string. *tzn != NULL indicates that we + * have alpha time zone info available. tm_isdst != -1 + * indicates that we have a valid time zone translation. + */ + if ((tzp != NULL) && (tm->tm_isdst >= 0)) + { + hour = -(*tzp / 3600); + min = ((abs(*tzp) / 60) % 60); + sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min); + } + break; + case USE_SQL_DATES: /* Compatible with Oracle/Ingres date formats */ *************** *** 3688,3693 **** --- 4006,4020 ---- } /* EncodeDateTime() */ + /* + * Small helper function to avoid cut&paste in EncodeInterval below + */ + static char * AppendISO8601Fragment(char * cp, int value, char character) + { + sprintf(cp,"%d%c",value,character); + return cp + strlen(cp); + } + /* EncodeInterval() * Interpret time structure as a delta time and convert to string. * *************** *** 3695,3700 **** --- 4022,4035 ---- * Actually, afaik ISO does not address time interval formatting, * but this looks similar to the spec for absolute date/time. * - thomas 1998-04-30 + * + * Actually, afaik, ISO 8601 does specify formats for "time + * intervals...[of the]...format with time-unit designators", which + * are pretty ugly. The format looks something like + * P1Y1M1DT1H1M1.12345S + * If you want this (perhaps for interoperability with computers + * rather than humans), datestyle 'iso8601basic' will output these. + * - ron 2003-07-14 */ int EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str) *************** *** 3777,3782 **** --- 4112,4159 ---- } break; + case USE_ISO8601BASIC_DATES: + sprintf(cp,"P"); + cp++; + if (tm->tm_year != 0) cp = AppendISO8601Fragment(cp,tm->tm_year,'Y'); + if (tm->tm_mon != 0) cp = AppendISO8601Fragment(cp,tm->tm_mon ,'M'); + if (tm->tm_mday != 0) cp = AppendISO8601Fragment(cp,tm->tm_mday,'D'); + if ((tm->tm_hour != 0) || (tm->tm_min != 0) || + (tm->tm_sec != 0) || (fsec != 0)) + { + sprintf(cp,"T"), + cp++; + } + if (tm->tm_hour != 0) cp = AppendISO8601Fragment(cp,tm->tm_hour,'H'); + if (tm->tm_min != 0) cp = AppendISO8601Fragment(cp,tm->tm_min ,'M'); + + if ((tm->tm_year == 0) && (tm->tm_mon == 0) && (tm->tm_mday == 0) && + (tm->tm_hour == 0) && (tm->tm_min == 0) && (tm->tm_sec == 0) && + (fsec == 0)) + { + sprintf(cp,"T0S"), + cp+=2; + } + else if (fsec != 0) + { + #ifdef HAVE_INT64_TIMESTAMP + sprintf(cp, "%d", abs(tm->tm_sec)); + cp += strlen(cp); + sprintf(cp, ".%6dS", ((fsec >= 0) ? fsec : -(fsec))); + #else + fsec += tm->tm_sec; + sprintf(cp, "%fS", fabs(fsec)); + #endif + TrimTrailingZeros(cp); + cp += strlen(cp); + } + else if (tm->tm_sec != 0) + { + cp = AppendISO8601Fragment(cp,tm->tm_sec ,'S'); + cp += strlen(cp); + } + break; + case USE_POSTGRES_DATES: default: strcpy(cp, "@ "); *************** *** 3901,3907 **** } /* identically zero? then put in a unitless zero... */ ! if (!is_nonzero) { strcat(cp, "0"); cp += strlen(cp); --- 4278,4284 ---- } /* identically zero? then put in a unitless zero... */ ! if (!is_nonzero && (style!=USE_ISO8601BASIC_DATES)) { strcat(cp, "0"); cp += strlen(cp); Index: src/include/miscadmin.h =================================================================== RCS file: /cvsroot/pgsql-server/src/include/miscadmin.h,v retrieving revision 1.138 retrieving revision 1.139 diff -c -r1.138 -r1.139 *** src/include/miscadmin.h 29 Nov 2003 22:40:53 -0000 1.138 --- src/include/miscadmin.h 20 Dec 2003 15:32:55 -0000 1.139 *************** *** 12,18 **** * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * ! * $PostgreSQL: pgsql-server/src/include/miscadmin.h,v 1.138 2003/11/29 22:40:53 pgsql Exp $ * * NOTES * some of the information in this file should be moved to --- 12,18 ---- * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * ! * $PostgreSQL: pgsql-server/src/include/miscadmin.h,v 1.139 2003/12/20 15:32:55 momjian Exp $ * * NOTES * some of the information in this file should be moved to *************** *** 150,155 **** --- 150,157 ---- * USE_ISO_DATES specifies ISO-compliant format * USE_SQL_DATES specifies Oracle/Ingres-compliant format * USE_GERMAN_DATES specifies German-style dd.mm/yyyy + * USE_ISO8601BASIC_DATES specifies ISO-8601-basic format (including + * ISO compliant but non-human-friendly intervals) * * DateOrder defines the field order to be assumed when reading an * ambiguous date (anything not in YYYY-MM-DD format, with a four-digit *************** *** 169,174 **** --- 171,177 ---- #define USE_ISO_DATES 1 #define USE_SQL_DATES 2 #define USE_GERMAN_DATES 3 + #define USE_ISO8601BASIC_DATES 4 /* valid DateOrder values */ #define DATEORDER_YMD 0 Index: src/interfaces/ecpg/pgtypeslib/dt.h =================================================================== RCS file: /cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/dt.h,v retrieving revision 1.14 retrieving revision 1.15 diff -c -r1.14 -r1.15 *** src/interfaces/ecpg/pgtypeslib/dt.h 5 Oct 2003 11:12:00 -0000 1.14 --- src/interfaces/ecpg/pgtypeslib/dt.h 20 Dec 2003 15:32:55 -0000 1.15 *************** *** 21,26 **** --- 21,27 ---- #define USE_ISO_DATES 1 #define USE_SQL_DATES 2 #define USE_GERMAN_DATES 3 + #define USE_ISO8601BASIC_DATES 4 #define DAGO "ago" #define EPOCH "epoch" Index: src/interfaces/ecpg/pgtypeslib/dt_common.c =================================================================== RCS file: /cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/dt_common.c,v retrieving revision 1.12 retrieving revision 1.13 diff -c -r1.12 -r1.13 *** src/interfaces/ecpg/pgtypeslib/dt_common.c 5 Oct 2003 11:12:00 -0000 1.12 --- src/interfaces/ecpg/pgtypeslib/dt_common.c 20 Dec 2003 15:32:55 -0000 1.13 *************** *** 704,709 **** --- 704,719 ---- -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); break; + case USE_ISO8601BASIC_DATES: + /* compatible with ISO date formats */ + if (tm->tm_year > 0) + sprintf(str, "%04d%02d%02d", + tm->tm_year, tm->tm_mon, tm->tm_mday); + else + sprintf(str, "%04d%02d%02d %s", + -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); + break; + case USE_SQL_DATES: /* compatible with Oracle/Ingres date formats */ if (EuroDates) *************** *** 802,807 **** --- 812,862 ---- } else sprintf((str + strlen(str)), ":%02d", tm->tm_sec); + + if (tm->tm_year <= 0) + sprintf((str + strlen(str)), " BC"); + + /* + * tzp == NULL indicates that we don't want *any* time zone + * info in the output string. *tzn != NULL indicates that we + * have alpha time zone info available. tm_isdst != -1 + * indicates that we have a valid time zone translation. + */ + if ((tzp != NULL) && (tm->tm_isdst >= 0)) + { + hour = -(*tzp / 3600); + min = ((abs(*tzp) / 60) % 60); + sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min); + } + break; + + case USE_ISO8601BASIC_DATES: + /* Compatible with ISO-8601 date formats */ + + sprintf(str, "%04d%02d%02dT%02d%02d", + ((tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)), + tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); + + /* + * Print fractional seconds if any. The field widths here + * should be at least equal to MAX_TIMESTAMP_PRECISION. + * + * In float mode, don't print fractional seconds before 1 AD, + * since it's unlikely there's any precision left ... + */ + #ifdef HAVE_INT64_TIMESTAMP + if (fsec != 0) + { + sprintf((str + strlen(str)), "%02d.%06d", tm->tm_sec, fsec); + #else + if ((fsec != 0) && (tm->tm_year > 0)) + { + sprintf((str + strlen(str)), "%09.6f", tm->tm_sec + fsec); + #endif + TrimTrailingZeros(str); + } + else + sprintf((str + strlen(str)), "%02d", tm->tm_sec); if (tm->tm_year <= 0) sprintf((str + strlen(str)), " BC"); Index: src/interfaces/ecpg/pgtypeslib/interval.c =================================================================== RCS file: /cvsroot/pgsql-server/src/interfaces/ecpg/pgtypeslib/interval.c,v retrieving revision 1.7 retrieving revision 1.8 diff -c -r1.7 -r1.8 *** src/interfaces/ecpg/pgtypeslib/interval.c 3 Oct 2003 10:07:28 -0000 1.7 --- src/interfaces/ecpg/pgtypeslib/interval.c 20 Dec 2003 15:32:55 -0000 1.8 *************** *** 442,447 **** --- 442,458 ---- return (fmask != 0) ? 0 : -1; } /* DecodeInterval() */ + + /* + * Small helper function to avoid cut&paste in EncodeInterval below + */ + static char * AppendISO8601Fragment(char * cp, int value, char character) + { + sprintf(cp,"%d%c",value,character); + return cp + strlen(cp); + } + + /* EncodeInterval() * Interpret time structure as a delta time and convert to string. * *************** *** 449,454 **** --- 460,473 ---- * Actually, afaik ISO does not address time interval formatting, * but this looks similar to the spec for absolute date/time. * - thomas 1998-04-30 + * + * Actually, afaik, ISO 8601 does specify formats for "time + * intervals...[of the]...format with time-unit designators", which + * are pretty ugly. The format looks something like + * P1Y1M1DT1H1M1.12345S + * If you want this (perhaps for interoperability with computers + * rather than humans), datestyle 'iso8601basic' will output these. + * - ron 2003-07-14 */ int EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str) *************** *** 465,471 **** */ switch (style) { ! /* compatible with ISO date formats */ case USE_ISO_DATES: if (tm->tm_year != 0) { --- 484,495 ---- */ switch (style) { ! /* compatible with ISO date formats ! ([ram] Not for ISO 8601, perhaps some other ISO format. ! but I'm leaving it that way because it's more human ! readable than ISO8601 time intervals and for backwards ! compatability.) ! */ case USE_ISO_DATES: if (tm->tm_year != 0) { *************** *** 530,535 **** --- 554,601 ---- cp += strlen(cp); is_nonzero = TRUE; } + } + break; + + case USE_ISO8601BASIC_DATES: + sprintf(cp,"P"); + cp++; + if (tm->tm_year != 0) cp = AppendISO8601Fragment(cp,tm->tm_year,'Y'); + if (tm->tm_mon != 0) cp = AppendISO8601Fragment(cp,tm->tm_mon ,'M'); + if (tm->tm_mday != 0) cp = AppendISO8601Fragment(cp,tm->tm_mday,'D'); + if ((tm->tm_hour != 0) || (tm->tm_min != 0) || + (tm->tm_sec != 0) || (fsec != 0)) + { + sprintf(cp,"T"), + cp++; + } + if (tm->tm_hour != 0) cp = AppendISO8601Fragment(cp,tm->tm_hour,'H'); + if (tm->tm_min != 0) cp = AppendISO8601Fragment(cp,tm->tm_min ,'M'); + + if ((tm->tm_year == 0) && (tm->tm_mon == 0) && (tm->tm_mday == 0) && + (tm->tm_hour == 0) && (tm->tm_min == 0) && (tm->tm_sec == 0) && + (fsec == 0)) + { + sprintf(cp,"T0S"), + cp+=2; + } + else if (fsec != 0) + { + #ifdef HAVE_INT64_TIMESTAMP + sprintf(cp, "%d", abs(tm->tm_sec)); + cp += strlen(cp); + sprintf(cp, ".%6dS", ((fsec >= 0) ? fsec : -(fsec))); + #else + fsec += tm->tm_sec; + sprintf(cp, "%fS", fabs(fsec)); + #endif + TrimTrailingZeros(cp); + cp += strlen(cp); + } + else if (tm->tm_sec != 0) + { + cp = AppendISO8601Fragment(cp,tm->tm_sec ,'S'); + cp += strlen(cp); } break;