diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 10da8d8d7b477fa7939159564c60edd98a4592ba..2cc9770f65e71abd985d6c1b98da83081bc59326 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.177 2006/10/16 19:58:26 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/datatype.sgml,v 1.178 2006/10/17 21:03:20 tgl Exp $ --> <chapter id="datatype"> <title id="datatype-title">Data Types</title> @@ -1675,12 +1675,16 @@ SELECT b, char_length(b) FROM test2; <tbody> <row> <entry><literal>PST</literal></entry> - <entry>Pacific Standard Time</entry> + <entry>Abbreviation (for Pacific Standard Time)</entry> </row> <row> <entry><literal>America/New_York</literal></entry> <entry>Full time zone name</entry> </row> + <row> + <entry><literal>PST8PDT</literal></entry> + <entry>POSIX-style time zone specification</entry> + </row> <row> <entry><literal>-8:00</literal></entry> <entry>ISO-8601 offset for PST</entry> @@ -2183,7 +2187,7 @@ January 8 04:05:06 1999 PST <listitem> <para> In addition to the timezone names and abbreviations, - <productname>PostgreSQL</productname> will accept time zone + <productname>PostgreSQL</productname> will accept POSIX-style time zone specifications of the form <replaceable>STD</><replaceable>offset</> or <replaceable>STD</><replaceable>offset</><replaceable>DST</>, where <replaceable>STD</> is a zone abbreviation, <replaceable>offset</> is a @@ -2220,12 +2224,6 @@ January 8 04:05:06 1999 PST prior to 8.2, which were case-sensitive in some contexts and not others.) </para> - <para> - Note that timezone names are <emphasis>not</> used for date/time output - — all supported output formats use numeric timezone displays to - avoid ambiguity. - </para> - <para> Neither full names nor abbreviations are hard-wired into the server; they are obtained from configuration files stored under diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml index b275ff803e0aabdadd43797e5095be40d5299e1b..3d80d254f03c5298e009f15fd9132b7da9dd8fe5 100644 --- a/doc/src/sgml/datetime.sgml +++ b/doc/src/sgml/datetime.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.54 2006/09/22 16:35:55 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/datetime.sgml,v 2.55 2006/10/17 21:03:20 tgl Exp $ --> <appendix id="datetime-appendix"> <title>Date/Time Support</title> @@ -46,9 +46,9 @@ <para> If the numeric token contains a dash (<literal>-</>), slash (<literal>/</>), or two or more dots (<literal>.</>), this is - a date string which may have a text month. In case of a slash - (<literal>/</>) it can also be a full time zone name like - <literal>America/New_York</>. + a date string which may have a text month. If a date token has + already been seen, it is instead interpreted as a time zone + name (e.g., <literal>America/New_York</>). </para> </step> @@ -64,7 +64,7 @@ <step> <para> If the token starts with a plus (<literal>+</>) or minus - (<literal>-</>), then it is either a time zone or a special + (<literal>-</>), then it is either a numeric time zone or a special field. </para> </step> @@ -73,30 +73,24 @@ <step> <para> - If the token is a text string, match up with possible strings. + If the token is a text string, match up with possible strings: </para> <substeps> <step> <para> - Do a binary-search table lookup for the token - as either a special string (e.g., <literal>today</literal>), - day (e.g., <literal>Thursday</literal>), - month (e.g., <literal>January</literal>), - or noise word (e.g., <literal>at</literal>, <literal>on</literal>). - </para> - - <para> - Set field values and bit mask for fields. - For example, set year, month, day for <literal>today</literal>, - and additionally hour, minute, second for <literal>now</literal>. + Do a binary-search table lookup for the token as a time zone + abbreviation. </para> </step> <step> <para> If not found, do a similar binary-search table lookup to match - the token with a time zone. + the token as either a special string (e.g., <literal>today</literal>), + day (e.g., <literal>Thursday</literal>), + month (e.g., <literal>January</literal>), + or noise word (e.g., <literal>at</literal>, <literal>on</literal>). </para> </step> diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index fac642ddc97935f93de303a08f6ede9b423a0d99..cfb29dce5687742fbe18b93b22d688b1f418dff0 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.172 2006/10/04 00:29:58 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.173 2006/10/17 21:03:21 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -39,8 +39,6 @@ static int DecodeNumberField(int len, char *str, static int DecodeTime(char *str, int fmask, int *tmask, struct pg_tm * tm, fsec_t *fsec); static int DecodeTimezone(char *str, int *tzp); -static int DecodePosixTimezone(char *str, int *tzp); -static int DecodeZicTimezone(char *str, int *tzp, struct pg_tm * tm); static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); static int DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm); static void TrimTrailingZeros(char *str); @@ -173,7 +171,7 @@ static const datetkn datetktbl[] = { {"wednesday", DOW, 3}, {"weds", DOW, 3}, {"y", UNITS, DTK_YEAR}, /* "year" for ISO input */ - {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */ + {YESTERDAY, RESERV, DTK_YESTERDAY} /* yesterday midnight */ }; static int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0]; @@ -243,7 +241,7 @@ static datetkn deltatktbl[] = { {DYEAR, UNITS, DTK_YEAR}, /* "year" relative */ {"years", UNITS, DTK_YEAR}, /* "years" relative */ {"yr", UNITS, DTK_YEAR}, /* "year" relative */ - {"yrs", UNITS, DTK_YEAR}, /* "years" relative */ + {"yrs", UNITS, DTK_YEAR} /* "years" relative */ }; static int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0]; @@ -427,14 +425,14 @@ TrimTrailingZeros(char *str) * DTK_NUMBER - digits and (possibly) a decimal point * DTK_DATE - digits and two delimiters, or digits and text * DTK_TIME - digits, colon delimiters, and possibly a decimal point - * DTK_STRING - text (no digits) + * DTK_STRING - text (no digits or punctuation) * DTK_SPECIAL - leading "+" or "-" followed by text - * DTK_TZ - leading "+" or "-" followed by digits + * DTK_TZ - leading "+" or "-" followed by digits (also eats ':' or '.') * * Note that some field types can hold unexpected items: * DTK_NUMBER can hold date fields (yy.ddd) * DTK_STRING can hold months (January) and time zones (PST) - * DTK_DATE can hold Posix time zones (GMT-8) + * DTK_DATE can hold time zone names (America/New_York, GMT-8) */ int ParseDateTime(const char *timestr, char *workbuf, size_t buflen, @@ -546,46 +544,42 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen, */ else if (isalpha((unsigned char) *cp)) { + bool is_date; + ftype[nf] = DTK_STRING; APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); while (isalpha((unsigned char) *cp)) APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); /* - * Full date string with leading text month? Could also be a POSIX - * time zone... + * Dates can have embedded '-', '/', or '.' separators. It could + * also be a timezone name containing embedded '/', '+', '-', + * '_', or ':' (but '_' or ':' can't be the first punctuation). + * If the next character is a digit or '+', we need to check + * whether what we have so far is a recognized non-timezone + * keyword --- if so, don't believe that this is the start of + * a timezone. */ + is_date = false; if (*cp == '-' || *cp == '/' || *cp == '.') + is_date = true; + else if (*cp == '+' || isdigit((unsigned char) *cp)) { - char delim = *cp; - - if (*cp == '/') - { - ftype[nf] = DTK_TZ; - - /* - * set the first character of the region to upper case - * again - */ - field[nf][0] = pg_toupper((unsigned char) field[nf][0]); - - /* - * we have seen "Region/" of a POSIX timezone, continue to - * read the City part - */ - do - { - APPEND_CHAR(bufp, bufend, *cp++); - /* there is for example America/New_York */ - } while (isalpha((unsigned char) *cp) || *cp == '_'); - } - else + *bufp = '\0'; /* null-terminate current field value */ + /* we need search only the core token table, not TZ names */ + if (datebsearch(field[nf], datetktbl, szdatetktbl) == NULL) + is_date = true; + } + if (is_date) + { + ftype[nf] = DTK_DATE; + do { - ftype[nf] = DTK_DATE; - APPEND_CHAR(bufp, bufend, *cp++); - } - while (isdigit((unsigned char) *cp) || *cp == delim) - APPEND_CHAR(bufp, bufend, *cp++); + APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++)); + } while (*cp == '+' || *cp == '-' || + *cp == '/' || *cp == '_' || + *cp == '.' || *cp == ':' || + isalnum((unsigned char) *cp)); } } /* sign? then special or numeric timezone */ @@ -674,7 +668,7 @@ DecodeDateTime(char **field, int *ftype, int nf, bool haveTextMonth = FALSE; int is2digits = FALSE; int bc = FALSE; - int zicTzFnum = -1; + pg_tz *namedTz = NULL; /* * We'll insist on at least all of the date fields, but initialize the @@ -724,8 +718,8 @@ DecodeDateTime(char **field, int *ftype, int nf, break; } /*** - * Already have a date? Then this might be a POSIX time - * zone with an embedded dash (e.g. "PST-3" == "EST") or + * Already have a date? Then this might be a time zone name + * with embedded punctuation (e.g. "America/New_York") or * a run-together time with trailing time zone (e.g. hhmmss-zz). * - thomas 2001-12-25 ***/ @@ -774,7 +768,6 @@ DecodeDateTime(char **field, int *ftype, int nf, fsec, &is2digits); if (dterr < 0) return dterr; - ftype[i] = dterr; /* * modify tmask after returning from @@ -784,11 +777,20 @@ DecodeDateTime(char **field, int *ftype, int nf, } else { - dterr = DecodePosixTimezone(field[i], tzp); - if (dterr) - return dterr; - - ftype[i] = DTK_TZ; + namedTz = pg_tzset(field[i]); + if (!namedTz) + { + /* + * We should return an error code instead of + * ereport'ing directly, but then there is no + * way to report the bad time zone name. + */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("time zone \"%s\" not recognized", + field[i]))); + } + /* we'll apply the zone setting below */ tmask = DTK_M(TZ); } } @@ -822,34 +824,11 @@ DecodeDateTime(char **field, int *ftype, int nf, if (tzp == NULL) return DTERR_BAD_FORMAT; - if (strchr(field[i], '/') != NULL) - { - /* remember to apply the timezone at the end */ - zicTzFnum = i; - tmask = DTK_M(TZ); - break; - } - else - dterr = DecodeTimezone(field[i], &tz); + dterr = DecodeTimezone(field[i], &tz); if (dterr) return dterr; - - /* - * Already have a time zone? Then maybe this is the second - * field of a POSIX time: EST+3 (equivalent to PST) - */ - if (i > 0 && (fmask & DTK_M(TZ)) != 0 && - ftype[i - 1] == DTK_TZ && - isalpha((unsigned char) *field[i - 1])) - { - *tzp -= tz; - tmask = 0; - } - else - { - *tzp = tz; - tmask = DTK_M(TZ); - } + *tzp = tz; + tmask = DTK_M(TZ); } break; @@ -988,8 +967,6 @@ DecodeDateTime(char **field, int *ftype, int nf, fsec, &is2digits); if (dterr < 0) return dterr; - ftype[i] = dterr; - if (tmask != DTK_TIME_M) return DTERR_BAD_FORMAT; break; @@ -1030,7 +1007,6 @@ DecodeDateTime(char **field, int *ftype, int nf, fsec, &is2digits); if (dterr < 0) return dterr; - ftype[i] = dterr; } else if (flen > 4) { @@ -1039,7 +1015,6 @@ DecodeDateTime(char **field, int *ftype, int nf, fsec, &is2digits); if (dterr < 0) return dterr; - ftype[i] = dterr; } /* otherwise it is a single date/time field... */ else @@ -1168,7 +1143,6 @@ DecodeDateTime(char **field, int *ftype, int nf, if (tzp == NULL) return DTERR_BAD_FORMAT; *tzp = val * MINS_PER_HOUR; - ftype[i] = DTK_TZ; break; case TZ: @@ -1176,7 +1150,6 @@ DecodeDateTime(char **field, int *ftype, int nf, if (tzp == NULL) return DTERR_BAD_FORMAT; *tzp = val * MINS_PER_HOUR; - ftype[i] = DTK_TZ; break; case IGNORE_DTF: @@ -1308,18 +1281,17 @@ DecodeDateTime(char **field, int *ftype, int nf, if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) return DTERR_FIELD_OVERFLOW; - if (zicTzFnum != -1) + /* + * If we had a full timezone spec, compute the offset (we could not + * do it before, because we need the date to resolve DST status). + */ + if (namedTz != NULL) { - Datum tsTz; - Timestamp timestamp; - - tm2timestamp(tm, *fsec, NULL, ×tamp); - tsTz = DirectFunctionCall2(timestamp_zone, - DirectFunctionCall1(textin, - CStringGetDatum(field[zicTzFnum])), - TimestampGetDatum(timestamp)); - timestamp2tm(DatumGetTimestampTz(tsTz), tzp, tm, fsec, NULL, NULL); - fmask &= ~DTK_M(TZ); + /* daylight savings time modifier disallowed with full TZ */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + *tzp = DetermineTimeZoneOffset(tm, namedTz); } /* timezone not specified? then find local timezone if possible */ @@ -1492,6 +1464,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, int dterr; int is2digits = FALSE; int mer = HR24; + pg_tz *namedTz = NULL; *dtype = DTK_TIME; tm->tm_hour = 0; @@ -1567,10 +1540,20 @@ DecodeTimeOnly(char **field, int *ftype, int nf, } else { - dterr = DecodePosixTimezone(field[i], tzp); - if (dterr) - return dterr; - + namedTz = pg_tzset(field[i]); + if (!namedTz) + { + /* + * We should return an error code instead of + * ereport'ing directly, but then there is no + * way to report the bad time zone name. + */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("time zone \"%s\" not recognized", + field[i]))); + } + /* we'll apply the zone setting below */ ftype[i] = DTK_TZ; tmask = DTK_M(TZ); } @@ -1591,34 +1574,11 @@ DecodeTimeOnly(char **field, int *ftype, int nf, if (tzp == NULL) return DTERR_BAD_FORMAT; - if (strchr(field[i], '/') != NULL) - { - /* a date has to be specified */ - if ((fmask & DTK_DATE_M) != DTK_DATE_M) - return DTERR_BAD_FORMAT; - dterr = DecodeZicTimezone(field[i], &tz, tm); - } - else - dterr = DecodeTimezone(field[i], &tz); + dterr = DecodeTimezone(field[i], &tz); if (dterr) return dterr; - - /* - * Already have a time zone? Then maybe this is the second - * field of a POSIX time: EST+3 (equivalent to PST) - */ - if (i > 0 && (fmask & DTK_M(TZ)) != 0 && - ftype[i - 1] == DTK_TZ && - isalpha((unsigned char) *field[i - 1])) - { - *tzp -= tz; - tmask = 0; - } - else - { - *tzp = tz; - tmask = DTK_M(TZ); - } + *tzp = tz; + tmask = DTK_M(TZ); } break; @@ -1974,21 +1934,38 @@ DecodeTimeOnly(char **field, int *ftype, int nf, if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 || tm->tm_sec < 0 || tm->tm_sec > 60 || tm->tm_hour > 24 || - /* test for > 24:00:00 */ - (tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0 || + /* test for > 24:00:00 */ #ifdef HAVE_INT64_TIMESTAMP + (tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > INT64CONST(0))) || - *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC) - return DTERR_FIELD_OVERFLOW; + *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC #else + (tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0)) || - *fsec < 0 || *fsec >= 1) - return DTERR_FIELD_OVERFLOW; + *fsec < 0 || *fsec >= 1 #endif + ) + return DTERR_FIELD_OVERFLOW; if ((fmask & DTK_TIME_M) != DTK_TIME_M) return DTERR_BAD_FORMAT; + /* + * If we had a full timezone spec, compute the offset (we could not + * do it before, because we need the date to resolve DST status). + */ + if (namedTz != NULL) + { + /* a date has to be specified */ + if ((fmask & DTK_DATE_M) != DTK_DATE_M) + return DTERR_BAD_FORMAT; + /* daylight savings time modifier disallowed with full TZ */ + if (fmask & DTK_M(DTZMOD)) + return DTERR_BAD_FORMAT; + + *tzp = DetermineTimeZoneOffset(tm, namedTz); + } + /* timezone not specified? then find local timezone if possible */ if (tzp != NULL && !(fmask & DTK_M(TZ))) { @@ -2548,7 +2525,8 @@ DecodeTimezone(char *str, int *tzp) { int tz; int hr, - min; + min, + sec = 0; char *cp; /* leading character must be "+" or "-" */ @@ -2567,22 +2545,32 @@ DecodeTimezone(char *str, int *tzp) min = strtol(cp + 1, &cp, 10); if (errno == ERANGE) return DTERR_TZDISP_OVERFLOW; + if (*cp == ':') + { + errno = 0; + sec = strtol(cp + 1, &cp, 10); + if (errno == ERANGE) + return DTERR_TZDISP_OVERFLOW; + } } /* otherwise, might have run things together... */ else if (*cp == '\0' && strlen(str) > 3) { min = hr % 100; hr = hr / 100; + /* we could, but don't, support a run-together hhmmss format */ } else min = 0; - if (hr < 0 || hr > 13) + if (hr < 0 || hr > 14) return DTERR_TZDISP_OVERFLOW; if (min < 0 || min >= 60) return DTERR_TZDISP_OVERFLOW; + if (sec < 0 || sec >= 60) + return DTERR_TZDISP_OVERFLOW; - tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE; + tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE + sec; if (*str == '-') tz = -tz; @@ -2594,75 +2582,6 @@ DecodeTimezone(char *str, int *tzp) return 0; } - -/* DecodePosixTimezone() - * Interpret string as a POSIX-compatible timezone: - * PST-hh:mm - * PST+h - * PST - * - thomas 2000-03-15 - * - * Return 0 if okay (and set *tzp), a DTERR code if not okay. - */ -static int -DecodePosixTimezone(char *str, int *tzp) -{ - int val, - tz; - int type; - int dterr; - char *cp; - char delim; - - /* advance over name part */ - cp = str; - while (*cp && isalpha((unsigned char) *cp)) - cp++; - - /* decode offset, if present */ - if (*cp) - { - dterr = DecodeTimezone(cp, &tz); - if (dterr) - return dterr; - } - else - tz = 0; - - /* decode name part. We must temporarily scribble on the input! */ - delim = *cp; - *cp = '\0'; - type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val); - *cp = delim; - - switch (type) - { - case DTZ: - case TZ: - *tzp = (val * MINS_PER_HOUR) - tz; - break; - - default: - return DTERR_BAD_FORMAT; - } - - return 0; -} - -static int -DecodeZicTimezone(char *str, int *tzp, struct pg_tm * tm) -{ - struct pg_tz *tz; - - tz = pg_tzset(str); - if (!tz) - return DTERR_BAD_FORMAT; - - *tzp = DetermineTimeZoneOffset(tm, tz); - - return 0; -} - /* DecodeSpecial() * Decode text string using lookup table. * @@ -3194,6 +3113,33 @@ datebsearch(const char *key, const datetkn *base, int nel) return NULL; } +/* EncodeTimezone() + * Append representation of a numeric timezone offset to str. + */ +static void +EncodeTimezone(char *str, int tz) +{ + int hour, + min, + sec; + + sec = abs(tz); + min = sec / SECS_PER_MINUTE; + sec -= min * SECS_PER_MINUTE; + hour = min / MINS_PER_HOUR; + min -= hour * MINS_PER_HOUR; + + str += strlen(str); + /* TZ is negated compared to sign we wish to display ... */ + *str++ = (tz <= 0 ? '+' : '-'); + + if (sec != 0) + sprintf(str, "%02d:%02d:%02d", hour, min, sec); + else if (min != 0) + sprintf(str, "%02d:%02d", hour, min); + else + sprintf(str, "%02d", hour); +} /* EncodeDateOnly() * Encode date as local time. @@ -3284,14 +3230,7 @@ EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, int *tzp, int style, char *str) sprintf(str + strlen(str), ":%02d", tm->tm_sec); if (tzp != NULL) - { - int hour, - min; - - hour = -(*tzp / SECS_PER_HOUR); - min = (abs(*tzp) / MINS_PER_HOUR) % MINS_PER_HOUR; - sprintf(str + strlen(str), (min != 0) ? "%+03d:%02d" : "%+03d", hour, min); - } + EncodeTimezone(str, *tzp); return TRUE; } /* EncodeTimeOnly() */ @@ -3311,9 +3250,7 @@ EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, int *tzp, int style, char *str) int EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, char *str) { - int day, - hour, - min; + int day; /* * Why are we checking only the month field? Change this to an assert... @@ -3360,11 +3297,7 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, * a valid time zone translation. */ if (tzp != NULL && tm->tm_isdst >= 0) - { - hour = -(*tzp / SECS_PER_HOUR); - min = (abs(*tzp) / MINS_PER_HOUR) % MINS_PER_HOUR; - sprintf(str + strlen(str), (min != 0) ? "%+03d:%02d" : "%+03d", hour, min); - } + EncodeTimezone(str, *tzp); if (tm->tm_year <= 0) sprintf(str + strlen(str), " BC"); @@ -3410,11 +3343,7 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, if (*tzn != NULL) sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn); else - { - hour = -(*tzp / SECS_PER_HOUR); - min = (abs(*tzp) / MINS_PER_HOUR) % MINS_PER_HOUR; - sprintf(str + strlen(str), (min != 0) ? "%+03d:%02d" : "%+03d", hour, min); - } + EncodeTimezone(str, *tzp); } if (tm->tm_year <= 0) @@ -3458,11 +3387,7 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, if (*tzn != NULL) sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn); else - { - hour = -(*tzp / SECS_PER_HOUR); - min = (abs(*tzp) / MINS_PER_HOUR) % MINS_PER_HOUR; - sprintf(str + strlen(str), (min != 0) ? "%+03d:%02d" : "%+03d", hour, min); - } + EncodeTimezone(str, *tzp); } if (tm->tm_year <= 0) @@ -3524,9 +3449,8 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, * avoid formatting something which would be rejected by * the date/time parser later. - thomas 2001-10-19 */ - hour = -(*tzp / SECS_PER_HOUR); - min = (abs(*tzp) / MINS_PER_HOUR) % MINS_PER_HOUR; - sprintf(str + strlen(str), (min != 0) ? " %+03d:%02d" : " %+03d", hour, min); + sprintf(str + strlen(str), " "); + EncodeTimezone(str, *tzp); } } diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index 15b397c74f2b92e4e8a9291b0549ae7e43d505d7..3aa8e3714bfca3f49b3523cb451077b3e37d5fae 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -448,7 +448,7 @@ SELECT '' AS "64", d1 + interval '1 year' AS one_year FROM TIMESTAMP_TBL; | Tue Feb 10 17:32:01 1998 | Tue Feb 10 17:32:01 1998 | Tue Feb 10 17:32:01 1998 - | Tue Feb 10 14:32:01 1998 + | Tue Feb 10 17:32:01 1998 | Wed Jun 10 18:32:01 1998 | Tue Feb 10 17:32:01 1998 | Wed Feb 11 17:32:01 1998 @@ -518,7 +518,7 @@ SELECT '' AS "64", d1 - interval '1 year' AS one_year FROM TIMESTAMP_TBL; | Sat Feb 10 17:32:01 1996 | Sat Feb 10 17:32:01 1996 | Sat Feb 10 17:32:01 1996 - | Sat Feb 10 14:32:01 1996 + | Sat Feb 10 17:32:01 1996 | Mon Jun 10 18:32:01 1996 | Sat Feb 10 17:32:01 1996 | Sun Feb 11 17:32:01 1996 @@ -936,8 +936,8 @@ SELECT t.d1 + i.f1 AS "102" FROM TIMESTAMP_TBL t, INTERVAL_TBL i Mon Feb 10 22:32:01 1997 Mon Feb 10 17:33:01 1997 Mon Feb 10 22:32:01 1997 - Mon Feb 10 14:33:01 1997 - Mon Feb 10 19:32:01 1997 + Mon Feb 10 17:33:01 1997 + Mon Feb 10 22:32:01 1997 Tue Jun 10 18:33:01 1997 Tue Jun 10 23:32:01 1997 Mon Feb 10 17:33:01 1997 @@ -1047,8 +1047,8 @@ SELECT t.d1 - i.f1 AS "102" FROM TIMESTAMP_TBL t, INTERVAL_TBL i Mon Feb 10 12:32:01 1997 Mon Feb 10 17:31:01 1997 Mon Feb 10 12:32:01 1997 - Mon Feb 10 14:31:01 1997 - Mon Feb 10 09:32:01 1997 + Mon Feb 10 17:31:01 1997 + Mon Feb 10 12:32:01 1997 Tue Jun 10 18:31:01 1997 Tue Jun 10 13:32:01 1997 Mon Feb 10 17:31:01 1997 @@ -2538,7 +2538,7 @@ SELECT '' AS "64", d1 AS us_postgres FROM TIMESTAMP_TBL; | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 - | Mon Feb 10 14:32:01 1997 + | Mon Feb 10 17:32:01 1997 | Tue Jun 10 18:32:01 1997 | Mon Feb 10 17:32:01 1997 | Tue Feb 11 17:32:01 1997 @@ -2621,7 +2621,7 @@ SELECT '' AS "64", d1 AS us_iso FROM TIMESTAMP_TBL; | 1997-02-10 17:32:01 | 1997-02-10 17:32:01 | 1997-02-10 17:32:01 - | 1997-02-10 14:32:01 + | 1997-02-10 17:32:01 | 1997-06-10 18:32:01 | 1997-02-10 17:32:01 | 1997-02-11 17:32:01 @@ -2710,7 +2710,7 @@ SELECT '' AS "64", d1 AS us_sql FROM TIMESTAMP_TBL; | 02/10/1997 17:32:01 | 02/10/1997 17:32:01 | 02/10/1997 17:32:01 - | 02/10/1997 14:32:01 + | 02/10/1997 17:32:01 | 06/10/1997 18:32:01 | 02/10/1997 17:32:01 | 02/11/1997 17:32:01 @@ -2806,7 +2806,7 @@ SELECT '' AS "65", d1 AS european_postgres FROM TIMESTAMP_TBL; | Mon 10 Feb 17:32:01 1997 | Mon 10 Feb 17:32:01 1997 | Mon 10 Feb 17:32:01 1997 - | Mon 10 Feb 14:32:01 1997 + | Mon 10 Feb 17:32:01 1997 | Tue 10 Jun 18:32:01 1997 | Mon 10 Feb 17:32:01 1997 | Tue 11 Feb 17:32:01 1997 @@ -2896,7 +2896,7 @@ SELECT '' AS "65", d1 AS european_iso FROM TIMESTAMP_TBL; | 1997-02-10 17:32:01 | 1997-02-10 17:32:01 | 1997-02-10 17:32:01 - | 1997-02-10 14:32:01 + | 1997-02-10 17:32:01 | 1997-06-10 18:32:01 | 1997-02-10 17:32:01 | 1997-02-11 17:32:01 @@ -2986,7 +2986,7 @@ SELECT '' AS "65", d1 AS european_sql FROM TIMESTAMP_TBL; | 10/02/1997 17:32:01 | 10/02/1997 17:32:01 | 10/02/1997 17:32:01 - | 10/02/1997 14:32:01 + | 10/02/1997 17:32:01 | 10/06/1997 18:32:01 | 10/02/1997 17:32:01 | 11/02/1997 17:32:01 diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index 5f3de34149a7470c192a25512db1850e7a95c10b..c145279e73e9013ef267463cf0fc900fd385867f 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -80,12 +80,12 @@ INSERT INTO TIMESTAMP_TBL VALUES ('1997-02-10 17:32:01 -08:00'); INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 -0800'); INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 17:32:01 -07:00'); INSERT INTO TIMESTAMP_TBL VALUES ('2001-09-22T18:19:20'); --- POSIX format +-- POSIX format (note that the timezone abbrev is just decoration here) INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 08:14:01 GMT+8'); INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 13:14:02 GMT-1'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT -2'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 EST+3'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 EST +2:00'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT-2'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 PST+8'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 MST+7:00'); -- Variations for acceptable input formats INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997 -0800'); INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997'); @@ -101,9 +101,9 @@ INSERT INTO TIMESTAMP_TBL VALUES ('97/02/10 17:32:01 UTC'); reset datestyle; INSERT INTO TIMESTAMP_TBL VALUES ('1997.041 17:32:01 UTC'); INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 America/New_York'); --- this fails +-- this fails (even though TZ is a no-op, we still look it up) INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist'); -ERROR: time zone "America/Does_not_exist" not recognized +ERROR: time zone "america/does_not_exist" not recognized -- Check date conversion and date arithmetic INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT'); INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997'); @@ -179,7 +179,7 @@ SELECT '' AS "64", d1 FROM TIMESTAMP_TBL; | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 - | Mon Feb 10 14:32:01 1997 + | Mon Feb 10 17:32:01 1997 | Tue Jun 10 18:32:01 1997 | Mon Feb 10 17:32:01 1997 | Tue Feb 11 17:32:01 1997 @@ -248,7 +248,7 @@ SELECT '' AS "48", d1 FROM TIMESTAMP_TBL | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 - | Mon Feb 10 14:32:01 1997 + | Mon Feb 10 17:32:01 1997 | Tue Jun 10 18:32:01 1997 | Mon Feb 10 17:32:01 1997 | Tue Feb 11 17:32:01 1997 @@ -333,7 +333,7 @@ SELECT '' AS "63", d1 FROM TIMESTAMP_TBL | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 - | Mon Feb 10 14:32:01 1997 + | Mon Feb 10 17:32:01 1997 | Tue Jun 10 18:32:01 1997 | Mon Feb 10 17:32:01 1997 | Tue Feb 11 17:32:01 1997 @@ -424,7 +424,7 @@ SELECT '' AS "49", d1 FROM TIMESTAMP_TBL | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 | Mon Feb 10 17:32:01 1997 - | Mon Feb 10 14:32:01 1997 + | Mon Feb 10 17:32:01 1997 | Tue Jun 10 18:32:01 1997 | Mon Feb 10 17:32:01 1997 | Tue Feb 11 17:32:01 1997 @@ -480,7 +480,7 @@ SELECT '' AS "54", d1 - timestamp without time zone '1997-01-02' AS diff | @ 39 days 17 hours 32 mins 1 sec | @ 39 days 17 hours 32 mins 1 sec | @ 39 days 17 hours 32 mins 1 sec - | @ 39 days 14 hours 32 mins 1 sec + | @ 39 days 17 hours 32 mins 1 sec | @ 159 days 18 hours 32 mins 1 sec | @ 39 days 17 hours 32 mins 1 sec | @ 40 days 17 hours 32 mins 1 sec @@ -550,7 +550,7 @@ SELECT '' AS "54", d1 - timestamp without time zone '1997-01-02' AS diff | @ 39 days 17 hours 32 mins 1 sec | @ 39 days 17 hours 32 mins 1 sec | @ 39 days 17 hours 32 mins 1 sec - | @ 39 days 14 hours 32 mins 1 sec + | @ 39 days 17 hours 32 mins 1 sec | @ 159 days 18 hours 32 mins 1 sec | @ 39 days 17 hours 32 mins 1 sec | @ 40 days 17 hours 32 mins 1 sec @@ -614,7 +614,7 @@ SELECT '' AS "54", d1 as "timestamp", | Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1 | Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1 | Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1 - | Mon Feb 10 14:32:01 1997 | 1997 | 2 | 10 | 14 | 32 | 1 + | Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1 | Tue Jun 10 18:32:01 1997 | 1997 | 6 | 10 | 18 | 32 | 1 | Mon Feb 10 17:32:01 1997 | 1997 | 2 | 10 | 17 | 32 | 1 | Tue Feb 11 17:32:01 1997 | 1997 | 2 | 11 | 17 | 32 | 1 @@ -677,7 +677,7 @@ SELECT '' AS "54", d1 as "timestamp", | Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000 | Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000 | Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000 - | Mon Feb 10 14:32:01 1997 | 1 | 1000 | 1000000 + | Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000 | Tue Jun 10 18:32:01 1997 | 2 | 1000 | 1000000 | Mon Feb 10 17:32:01 1997 | 1 | 1000 | 1000000 | Tue Feb 11 17:32:01 1997 | 1 | 1000 | 1000000 @@ -1025,7 +1025,7 @@ SELECT '' AS to_char_5, to_char(d1, 'HH HH12 HH24 MI SS SSSS') | 05 05 17 32 01 63121 | 05 05 17 32 01 63121 | 05 05 17 32 01 63121 - | 02 02 14 32 01 52321 + | 05 05 17 32 01 63121 | 06 06 18 32 01 66721 | 05 05 17 32 01 63121 | 05 05 17 32 01 63121 @@ -1096,7 +1096,7 @@ SELECT '' AS to_char_6, to_char(d1, E'"HH:MI:SS is" HH:MI:SS "\\"text between qu | HH:MI:SS is 05:32:01 "text between quote marks" | HH:MI:SS is 05:32:01 "text between quote marks" | HH:MI:SS is 05:32:01 "text between quote marks" - | HH:MI:SS is 02:32:01 "text between quote marks" + | HH:MI:SS is 05:32:01 "text between quote marks" | HH:MI:SS is 06:32:01 "text between quote marks" | HH:MI:SS is 05:32:01 "text between quote marks" | HH:MI:SS is 05:32:01 "text between quote marks" @@ -1167,7 +1167,7 @@ SELECT '' AS to_char_7, to_char(d1, 'HH24--text--MI--text--SS') | 17--text--32--text--01 | 17--text--32--text--01 | 17--text--32--text--01 - | 14--text--32--text--01 + | 17--text--32--text--01 | 18--text--32--text--01 | 17--text--32--text--01 | 17--text--32--text--01 @@ -1310,7 +1310,7 @@ SELECT '' AS to_char_9, to_char(d1, 'YYYY A.D. YYYY a.d. YYYY bc HH:MI:SS P.M. H | 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm | 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm | 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm - | 1997 A.D. 1997 a.d. 1997 ad 02:32:01 P.M. 02:32:01 p.m. 02:32:01 pm + | 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm | 1997 A.D. 1997 a.d. 1997 ad 06:32:01 P.M. 06:32:01 p.m. 06:32:01 pm | 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm | 1997 A.D. 1997 a.d. 1997 ad 05:32:01 P.M. 05:32:01 p.m. 05:32:01 pm diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index 4448ef6e20a1bf19ce42b48d7dc5c9f1741f85de..4a17503324d5f10c0eb8f815a60288e8dae24e19 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -75,12 +75,12 @@ INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-02-10 17:32:01 -08:00'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 -0800'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 17:32:01 -07:00'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('2001-09-22T18:19:20'); --- POSIX format +-- POSIX format (note that the timezone abbrev is just decoration here) INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 08:14:01 GMT+8'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 13:14:02 GMT-1'); -INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 12:14:03 GMT -2'); -INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 03:14:04 EST+3'); -INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 02:14:05 EST +2:00'); +INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 12:14:03 GMT-2'); +INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 03:14:04 PST+8'); +INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 02:14:05 MST+7:00'); -- Variations for acceptable input formats INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997 -0800'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997'); @@ -111,7 +111,7 @@ SELECT '19970710 173201' AT TIME ZONE 'America/New_York'; (1 row) INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970710 173201 America/Does_not_exist'); -ERROR: time zone "America/Does_not_exist" not recognized +ERROR: time zone "america/does_not_exist" not recognized SELECT '19970710 173201' AT TIME ZONE 'America/Does_not_exist'; ERROR: time zone "America/Does_not_exist" not recognized -- Check date conversion and date arithmetic diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 31235704a9c418bd54464e3b1341928b46d7f7e5..34689564daec969df82b72371b2a8bd00cbc8303 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -62,12 +62,12 @@ INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 -0800'); INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 17:32:01 -07:00'); INSERT INTO TIMESTAMP_TBL VALUES ('2001-09-22T18:19:20'); --- POSIX format +-- POSIX format (note that the timezone abbrev is just decoration here) INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 08:14:01 GMT+8'); INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 13:14:02 GMT-1'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT -2'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 EST+3'); -INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 EST +2:00'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 12:14:03 GMT-2'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 03:14:04 PST+8'); +INSERT INTO TIMESTAMP_TBL VALUES ('2000-03-15 02:14:05 MST+7:00'); -- Variations for acceptable input formats INSERT INTO TIMESTAMP_TBL VALUES ('Feb 10 17:32:01 1997 -0800'); @@ -83,12 +83,10 @@ INSERT INTO TIMESTAMP_TBL VALUES ('97FEB10 5:32:01PM UTC'); INSERT INTO TIMESTAMP_TBL VALUES ('97/02/10 17:32:01 UTC'); reset datestyle; INSERT INTO TIMESTAMP_TBL VALUES ('1997.041 17:32:01 UTC'); - INSERT INTO TIMESTAMP_TBL VALUES ('19970210 173201 America/New_York'); --- this fails +-- this fails (even though TZ is a no-op, we still look it up) INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist'); - -- Check date conversion and date arithmetic INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT'); diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index 65af1b04173ff02e9c022ab6003f8cb52c5ab5f3..fc597a6b2cf477da44d86c11282dd7833e378937 100644 --- a/src/test/regress/sql/timestamptz.sql +++ b/src/test/regress/sql/timestamptz.sql @@ -56,12 +56,12 @@ INSERT INTO TIMESTAMPTZ_TBL VALUES ('19970210 173201 -0800'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 17:32:01 -07:00'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('2001-09-22T18:19:20'); --- POSIX format +-- POSIX format (note that the timezone abbrev is just decoration here) INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 08:14:01 GMT+8'); INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 13:14:02 GMT-1'); -INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 12:14:03 GMT -2'); -INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 03:14:04 EST+3'); -INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 02:14:05 EST +2:00'); +INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 12:14:03 GMT-2'); +INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 03:14:04 PST+8'); +INSERT INTO TIMESTAMPTZ_TBL VALUES ('2000-03-15 02:14:05 MST+7:00'); -- Variations for acceptable input formats INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 10 17:32:01 1997 -0800');