Re: Proposal for better support of time-varying timezone abbreviations - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Proposal for better support of time-varying timezone abbreviations |
Date | |
Msg-id | 27680.1413325584@sss.pgh.pa.us Whole thread Raw |
In response to | Proposal for better support of time-varying timezone abbreviations (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Proposal for better support of time-varying timezone
abbreviations
Re: Proposal for better support of time-varying timezone abbreviations |
List | pgsql-hackers |
I wrote: > I got interested in the problem discussed in > http://www.postgresql.org/message-id/20714.1412456604@sss.pgh.pa.us > to wit: >> It's becoming clear to me that our existing design whereby zone >> abbreviations represent fixed GMT offsets isn't really good enough. >> I've been wondering whether we could change things so that, for instance, >> "EDT" means "daylight time according to America/New_York" and the system >> would consult the zic database to find out what the prevailing GMT offset >> was in that zone on that date. This would be a lot more robust in the >> face of the kind of foolishness we now see actually goes on. Attached is an updated patch for this, incorporating my previous work on changing the representation of datetkn. The code changes are complete I believe, but I've not touched the user documentation yet, nor updated the tznames data files except for changing MSK for testing purposes. Some notes: * There wasn't any reasonable way to return the required information about a dynamic zone abbreviation from DecodeSpecial(). However, on looking closely I found that of the existing callers of DecodeSpecial(), only two actually relied on it to find both timezone abbreviations and regular keywords. The other callers only wanted one case or the other. So it seemed to me that it'd be best to split the abbreviation-lookup behavior apart from keyword-lookup. DecodeSpecial() now does only the latter, and there's a new function DecodeTimezoneAbbrev() to do the former. This avoids touching any code that doesn't care about timezone abbreviations, and should be a bit faster for those cases too (but about the same speed otherwise). * I found that there were pre-existing bugs in DetermineTimeZoneOffset() for cases in which a zone changed offset but neither the preceding nor following time segment was marked as DST time. This caused strange behaviors for cases like Europe/Moscow's 2011 and 2014 time changes. This is not terribly surprising because we never thought about zone changes other than simple DST spring-forward/fall-back changes when that code was written. * I've not touched ecpg except for cosmetic changes to keep the struct definitions in sync, and to fix the previously-mentioned bogus free() attempt. I doubt that it would be worth teaching ecpg how to access the zic timezone database --- the problem of configuring where to find those files seems like more trouble than it's worth given the lack of complaints. I'm not sure what we should do about the obsolete timezone abbreviations in its table. * timetz_zone() and DecodeTimeOnly() are not terribly consistent about how they resolve timezones when not given a date. The former resolves based on the value of time(NULL), which is a moving target even within a transaction (which is why the function is marked volatile I guess). The latter gets today's date as of start of transaction (so at least it's stable) and then merges that m/d/y with the h/m/s from the time value. That behavior does not seem terribly well thought out either: on the day of, or day before or after, a DST change it's likely to produce strange results, especially if the session timezone is different from the zone specified in the input. I think we ought to consider changing one or both of these, though it's not material for back-patching. * In HEAD, we might want to extend the pg_timezone_abbrevs view to have a column that shows the underlying zone for a dynamic abbreviation. The attached patch just shows the abbreviation's behavior as of current time (defined as now()), which is sort of good enough but certainly not the whole truth. Comments? regards, tom lane diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c index a13dcc8..b9c2b49 100644 *** a/contrib/btree_gist/btree_ts.c --- b/contrib/btree_gist/btree_ts.c *************** tstz_dist(PG_FUNCTION_ARGS) *** 200,226 **** **************************************************/ ! static Timestamp tstz_to_ts_gmt(TimestampTz ts) { ! Timestamp gmt; ! int val, ! tz; ! ! gmt = ts; ! DecodeSpecial(0, "gmt", &val); ! ! if (ts < DT_NOEND && ts > DT_NOBEGIN) ! { ! tz = val * 60; ! ! #ifdef HAVE_INT64_TIMESTAMP ! gmt -= (tz * INT64CONST(1000000)); ! #else ! gmt -= tz; ! #endif ! } ! return gmt; } --- 200,210 ---- **************************************************/ ! static inline Timestamp tstz_to_ts_gmt(TimestampTz ts) { ! /* No timezone correction is needed, since GMT is offset 0 by definition */ ! return (Timestamp) ts; } diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 073104d..bb23b12 100644 *** a/src/backend/utils/adt/date.c --- b/src/backend/utils/adt/date.c *************** timetz_zone(PG_FUNCTION_ARGS) *** 2695,2718 **** pg_tz *tzp; /* ! * Look up the requested timezone. First we look in the date token table ! * (to handle cases like "EST"), and if that fails, we look in the ! * timezone database (to handle cases like "America/New_York"). (This ! * matches the order in which timestamp input checks the cases; it's ! * important because the timezone database unwisely uses a few zone names ! * that are identical to offset abbreviations.) */ text_to_cstring_buffer(zone, tzname, sizeof(tzname)); lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeSpecial(0, lowzone, &val); if (type == TZ || type == DTZ) ! tz = val * MINS_PER_HOUR; else { tzp = pg_tzset(tzname); if (tzp) { --- 2695,2733 ---- pg_tz *tzp; /* ! * Look up the requested timezone. First we look in the timezone ! * abbreviation table (to handle cases like "EST"), and if that fails, we ! * look in the timezone database (to handle cases like ! * "America/New_York"). (This matches the order in which timestamp input ! * checks the cases; it's important because the timezone database unwisely ! * uses a few zone names that are identical to offset abbreviations.) */ text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + /* DecodeTimezoneAbbrev requires lowercase input */ lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp); if (type == TZ || type == DTZ) ! { ! /* fixed-offset abbreviation */ ! tz = -val; ! } ! else if (type == DYNTZ) ! { ! /* dynamic-offset abbreviation, resolve using current time */ ! pg_time_t now = (pg_time_t) time(NULL); ! struct pg_tm *tm; ! ! tm = pg_localtime(&now, tzp); ! tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp); ! } else { + /* try it as a full zone name */ tzp = pg_tzset(tzname); if (tzp) { diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 7632d11..4381a5a 100644 *** a/src/backend/utils/adt/datetime.c --- b/src/backend/utils/adt/datetime.c *************** static void AdjustFractSeconds(double fr *** 50,55 **** --- 50,60 ---- int scale); static void AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec, int scale); + static int DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp, + pg_time_t *tp); + static int DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, + pg_tz *tzp, int *isdst); + static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp); const int day_tab[2][13] = *************** const char *const days[] = {"Sunday", "M *** 70,110 **** *****************************************************************************/ /* - * Definitions for squeezing values into "value" - * We set aside a high bit for a sign, and scale the timezone offsets - * in minutes by a factor of 15 (so can represent quarter-hour increments). - */ - #define ABS_SIGNBIT ((char) 0200) - #define VALMASK ((char) 0177) - #define POS(n) (n) - #define NEG(n) ((n)|ABS_SIGNBIT) - #define SIGNEDCHAR(c) ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c)) - #define FROMVAL(tp) (-SIGNEDCHAR((tp)->value) * 15) /* uncompress */ - #define TOVAL(tp, v) ((tp)->value = ((v) < 0? NEG((-(v))/15): POS(v)/15)) - - /* * datetktbl holds date/time keywords. * * Note that this table must be strictly alphabetically ordered to allow an * O(ln(N)) search algorithm to be used. * ! * The token field is NOT guaranteed to be NULL-terminated. ! * ! * To keep this table reasonably small, we divide the value for TZ and DTZ ! * entries by 15 (so they are on 15 minute boundaries) and truncate the token ! * field at TOKMAXLEN characters. ! * Formerly, we divided by 10 rather than 15 but there are a few time zones ! * which are 30 or 45 minutes away from an even hour, most are on an hour ! * boundary, and none on other boundaries. * ! * The static table contains no TZ or DTZ entries, rather those are loaded ! * from configuration files and stored in timezonetktbl, which has the same ! * format as the static datetktbl. */ - static datetkn *timezonetktbl = NULL; - - static int sztimezonetktbl = 0; - static const datetkn datetktbl[] = { /* token, type, value */ {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */ --- 75,92 ---- *****************************************************************************/ /* * datetktbl holds date/time keywords. * * Note that this table must be strictly alphabetically ordered to allow an * O(ln(N)) search algorithm to be used. * ! * The token field must be NUL-terminated; we truncate entries to TOKMAXLEN ! * characters to fit. * ! * The static table contains no TZ, DTZ, or DYNTZ entries; rather those ! * are loaded from configuration files and stored in zoneabbrevtbl, whose ! * abbrevs[] field has the same format as the static datetktbl. */ static const datetkn datetktbl[] = { /* token, type, value */ {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */ *************** static const datetkn datetktbl[] = { *** 123,129 **** {"december", MONTH, 12}, {"dow", RESERV, DTK_DOW}, /* day of week */ {"doy", RESERV, DTK_DOY}, /* day of year */ ! {"dst", DTZMOD, 6}, {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */ {"feb", MONTH, 2}, {"february", MONTH, 2}, --- 105,111 ---- {"december", MONTH, 12}, {"dow", RESERV, DTK_DOW}, /* day of week */ {"doy", RESERV, DTK_DOY}, /* day of year */ ! {"dst", DTZMOD, SECS_PER_HOUR}, {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */ {"feb", MONTH, 2}, {"february", MONTH, 2}, *************** static const datetkn datetktbl[] = { *** 185,190 **** --- 167,176 ---- static int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0]; + /* + * deltatktbl: same format as datetktbl, but holds keywords used to represent + * time units (eg, for intervals, and for EXTRACT). + */ static const datetkn deltatktbl[] = { /* token, type, value */ {"@", IGNORE_DTF, 0}, /* postgres relative prefix */ *************** static const datetkn deltatktbl[] = { *** 254,263 **** --- 240,255 ---- static int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0]; + static TimeZoneAbbrevTable *zoneabbrevtbl = NULL; + + /* Caches of recent lookup results in the above tables */ + static const datetkn *datecache[MAXDATEFIELDS] = {NULL}; static const datetkn *deltacache[MAXDATEFIELDS] = {NULL}; + static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL}; + /* * strtoi --- just like strtol, but returns int not long *************** DecodeDateTime(char **field, int *ftype, *** 798,803 **** --- 790,798 ---- bool is2digits = FALSE; bool bc = FALSE; pg_tz *namedTz = NULL; + pg_tz *abbrevTz = NULL; + pg_tz *valtz; + char *abbrev = NULL; struct pg_tm cur_tm; /* *************** DecodeDateTime(char **field, int *ftype, *** 1194,1200 **** case DTK_STRING: case DTK_SPECIAL: ! type = DecodeSpecial(i, field[i], &val); if (type == IGNORE_DTF) continue; --- 1189,1198 ---- case DTK_STRING: case DTK_SPECIAL: ! /* timezone abbrevs take precedence over built-in tokens */ ! type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz); ! if (type == UNKNOWN_FIELD) ! type = DecodeSpecial(i, field[i], &val); if (type == IGNORE_DTF) continue; *************** DecodeDateTime(char **field, int *ftype, *** 1286,1292 **** tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp += val * MINS_PER_HOUR; break; case DTZ: --- 1284,1290 ---- tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp -= val; break; case DTZ: *************** DecodeDateTime(char **field, int *ftype, *** 1299,1315 **** tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = val * MINS_PER_HOUR; break; case TZ: tm->tm_isdst = 0; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = val * MINS_PER_HOUR; break; ! case IGNORE_DTF: break; case AMPM: --- 1297,1319 ---- tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = -val; break; case TZ: tm->tm_isdst = 0; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = -val; break; ! case DYNTZ: ! tmask |= DTK_M(TZ); ! if (tzp == NULL) ! return DTERR_BAD_FORMAT; ! /* we'll determine the actual offset later */ ! abbrevTz = valtz; ! abbrev = field[i]; break; case AMPM: *************** DecodeDateTime(char **field, int *ftype, *** 1419,1425 **** *tzp = DetermineTimeZoneOffset(tm, namedTz); } ! /* timezone not specified? then find local timezone if possible */ if (tzp != NULL && !(fmask & DTK_M(TZ))) { /* --- 1423,1442 ---- *tzp = DetermineTimeZoneOffset(tm, namedTz); } ! /* ! * Likewise, if we had a dynamic timezone abbreviation, resolve it ! * now. ! */ ! if (abbrevTz != NULL) ! { ! /* daylight savings time modifier disallowed with dynamic TZ */ ! if (fmask & DTK_M(DTZMOD)) ! return DTERR_BAD_FORMAT; ! ! *tzp = DetermineTimeZoneAbbrevOffset(tm, abbrev, abbrevTz); ! } ! ! /* timezone not specified? then use session timezone */ if (tzp != NULL && !(fmask & DTK_M(TZ))) { /* *************** DecodeDateTime(char **field, int *ftype, *** 1439,1455 **** /* DetermineTimeZoneOffset() * ! * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and ! * tm_sec fields are set, attempt to determine the applicable time zone ! * (ie, regular or daylight-savings time) at that time. Set the struct pg_tm's ! * tm_isdst field accordingly, and return the actual timezone offset. * * Note: it might seem that we should use mktime() for this, but bitter * experience teaches otherwise. This code is much faster than most versions * of mktime(), anyway. */ ! int ! DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp) { int date, sec; --- 1456,1495 ---- /* DetermineTimeZoneOffset() * ! * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, ! * and tm_sec fields are set, and a zic-style time zone definition, determine ! * the applicable GMT offset and daylight-savings status at that time. ! * Set the struct pg_tm's tm_isdst field accordingly, and return the GMT ! * offset as the function result. ! * ! * Note: if the date is out of the range we can deal with, we return zero ! * as the GMT offset and set tm_isdst = 0. We don't throw an error here, ! * though probably some higher-level code will. ! */ ! int ! DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp) ! { ! pg_time_t t; ! ! return DetermineTimeZoneOffsetInternal(tm, tzp, &t); ! } ! ! ! /* DetermineTimeZoneOffsetInternal() ! * ! * As above, but also return the actual UTC time imputed to the date/time ! * into *tp. ! * ! * In event of an out-of-range date, we punt by returning zero into *tp. ! * This is okay for the immediate callers but is a good reason for not ! * exposing this worker function globally. * * Note: it might seem that we should use mktime() for this, but bitter * experience teaches otherwise. This code is much faster than most versions * of mktime(), anyway. */ ! static int ! DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp, pg_time_t *tp) { int date, sec; *************** DetermineTimeZoneOffset(struct pg_tm * t *** 1468,1475 **** /* * First, generate the pg_time_t value corresponding to the given * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the ! * timezone is GMT. (We only need to worry about overflow on machines ! * where pg_time_t is 32 bits.) */ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) goto overflow; --- 1508,1515 ---- /* * First, generate the pg_time_t value corresponding to the given * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the ! * timezone is GMT. (For a valid Julian date, integer overflow should be ! * impossible with 64-bit pg_time_t, but let's check for safety.) */ if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday)) goto overflow; *************** DetermineTimeZoneOffset(struct pg_tm * t *** 1506,1511 **** --- 1546,1552 ---- { /* Non-DST zone, life is simple */ tm->tm_isdst = before_isdst; + *tp = mytime - before_gmtoff; return -(int) before_gmtoff; } *************** DetermineTimeZoneOffset(struct pg_tm * t *** 1526,1563 **** goto overflow; /* ! * If both before or both after the boundary time, we know what to do */ ! if (beforetime <= boundary && aftertime < boundary) { tm->tm_isdst = before_isdst; return -(int) before_gmtoff; } if (beforetime > boundary && aftertime >= boundary) { tm->tm_isdst = after_isdst; return -(int) after_gmtoff; } /* ! * It's an invalid or ambiguous time due to timezone transition. Prefer ! * the standard-time interpretation. */ ! if (after_isdst == 0) { ! tm->tm_isdst = after_isdst; ! return -(int) after_gmtoff; } ! tm->tm_isdst = before_isdst; ! return -(int) before_gmtoff; overflow: /* Given date is out of range, so assume UTC */ tm->tm_isdst = 0; return 0; } /* DecodeTimeOnly() * Interpret parsed string as time fields only. * Returns 0 if successful, DTERR code if bogus input detected. --- 1567,1690 ---- goto overflow; /* ! * If both before or both after the boundary time, we know what to do. The ! * boundary time itself is considered to be after the transition, which ! * means we can accept aftertime == boundary in the second case. */ ! if (beforetime < boundary && aftertime < boundary) { tm->tm_isdst = before_isdst; + *tp = beforetime; return -(int) before_gmtoff; } if (beforetime > boundary && aftertime >= boundary) { tm->tm_isdst = after_isdst; + *tp = aftertime; return -(int) after_gmtoff; } /* ! * It's an invalid or ambiguous time due to timezone transition. In a ! * spring-forward transition, prefer the "before" interpretation; in a ! * fall-back transition, prefer "after". (We used to define and implement ! * this test as "prefer the standard-time interpretation", but that rule ! * does not help to resolve the behavior when both times are reported as ! * standard time; which does happen, eg Europe/Moscow in Oct 2014.) */ ! if (beforetime > aftertime) { ! tm->tm_isdst = before_isdst; ! *tp = beforetime; ! return -(int) before_gmtoff; } ! tm->tm_isdst = after_isdst; ! *tp = aftertime; ! return -(int) after_gmtoff; overflow: /* Given date is out of range, so assume UTC */ tm->tm_isdst = 0; + *tp = 0; return 0; } + /* DetermineTimeZoneAbbrevOffset() + * + * Determine the GMT offset and DST flag to be attributed to a dynamic + * time zone abbreviation, that is one whose meaning has changed over time. + * *tm contains the local time at which the meaning should be determined, + * and tm->tm_isdst receives the DST flag. + * + * This differs from the behavior of DetermineTimeZoneOffset() in that a + * standard-time or daylight-time abbreviation forces use of the corresponding + * GMT offset even when the zone was then in DS or standard time respectively. + */ + int + DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp) + { + pg_time_t t; + + /* + * Compute the UTC time we want to probe at. (In event of overflow, we'll + * probe at the epoch, which is a bit random but probably doesn't matter.) + */ + (void) DetermineTimeZoneOffsetInternal(tm, tzp, &t); + + return DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, &tm->tm_isdst); + } + + + /* DetermineTimeZoneAbbrevOffsetTS() + * + * As above but the probe time is specified as a TimestampTz (hence, UTC time), + * and DST status is returned into *isdst rather than into tm->tm_isdst. + */ + int + DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr, + pg_tz *tzp, int *isdst) + { + pg_time_t t = timestamptz_to_time_t(ts); + + return DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, isdst); + } + + + /* DetermineTimeZoneAbbrevOffsetInternal() + * + * Workhorse for above two functions: work from a pg_time_t probe instant. + * DST status is returned into *isdst. + */ + static int + DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, + pg_tz *tzp, int *isdst) + { + char upabbr[TZ_STRLEN_MAX + 1]; + unsigned char *p; + long int gmtoff; + + /* We need to force the abbrev to upper case */ + strlcpy(upabbr, abbr, sizeof(upabbr)); + for (p = (unsigned char *) upabbr; *p; p++) + *p = pg_toupper(*p); + + /* Look up the abbrev's meaning at this time in this zone */ + if (!pg_interpret_timezone_abbrev(upabbr, + &t, + &gmtoff, + isdst, + tzp)) + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("time zone abbreviation \"%s\" is not used in time zone \"%s\"", + abbr, pg_get_timezone_name(tzp)))); + + /* Change sign to agree with DetermineTimeZoneOffset() */ + return (int) -gmtoff; + } + + /* DecodeTimeOnly() * Interpret parsed string as time fields only. * Returns 0 if successful, DTERR code if bogus input detected. *************** DecodeTimeOnly(char **field, int *ftype, *** 1586,1591 **** --- 1713,1721 ---- bool bc = FALSE; int mer = HR24; pg_tz *namedTz = NULL; + pg_tz *abbrevTz = NULL; + char *abbrev = NULL; + pg_tz *valtz; *dtype = DTK_TIME; tm->tm_hour = 0; *************** DecodeTimeOnly(char **field, int *ftype, *** 1930,1936 **** case DTK_STRING: case DTK_SPECIAL: ! type = DecodeSpecial(i, field[i], &val); if (type == IGNORE_DTF) continue; --- 2060,2069 ---- case DTK_STRING: case DTK_SPECIAL: ! /* timezone abbrevs take precedence over built-in tokens */ ! type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz); ! if (type == UNKNOWN_FIELD) ! type = DecodeSpecial(i, field[i], &val); if (type == IGNORE_DTF) continue; *************** DecodeTimeOnly(char **field, int *ftype, *** 1978,1984 **** tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp += val * MINS_PER_HOUR; break; case DTZ: --- 2111,2117 ---- tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp -= val; break; case DTZ: *************** DecodeTimeOnly(char **field, int *ftype, *** 1991,1997 **** tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = val * MINS_PER_HOUR; ftype[i] = DTK_TZ; break; --- 2124,2130 ---- tm->tm_isdst = 1; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = -val; ftype[i] = DTK_TZ; break; *************** DecodeTimeOnly(char **field, int *ftype, *** 1999,2009 **** tm->tm_isdst = 0; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = val * MINS_PER_HOUR; ftype[i] = DTK_TZ; break; ! case IGNORE_DTF: break; case AMPM: --- 2132,2149 ---- tm->tm_isdst = 0; if (tzp == NULL) return DTERR_BAD_FORMAT; ! *tzp = -val; ftype[i] = DTK_TZ; break; ! case DYNTZ: ! tmask |= DTK_M(TZ); ! if (tzp == NULL) ! return DTERR_BAD_FORMAT; ! /* we'll determine the actual offset later */ ! abbrevTz = valtz; ! abbrev = field[i]; ! ftype[i] = DTK_TZ; break; case AMPM: *************** DecodeTimeOnly(char **field, int *ftype, *** 2123,2129 **** } } ! /* timezone not specified? then find local timezone if possible */ if (tzp != NULL && !(fmask & DTK_M(TZ))) { struct pg_tm tt, --- 2263,2298 ---- } } ! /* ! * Likewise, if we had a dynamic timezone abbreviation, resolve it now. ! */ ! if (abbrevTz != NULL) ! { ! struct pg_tm tt, ! *tmp = &tt; ! ! /* ! * daylight savings time modifier but no standard timezone? then error ! */ ! if (fmask & DTK_M(DTZMOD)) ! return DTERR_BAD_FORMAT; ! ! if ((fmask & DTK_DATE_M) == 0) ! GetCurrentDateTime(tmp); ! else ! { ! tmp->tm_year = tm->tm_year; ! tmp->tm_mon = tm->tm_mon; ! tmp->tm_mday = tm->tm_mday; ! } ! tmp->tm_hour = tm->tm_hour; ! tmp->tm_min = tm->tm_min; ! tmp->tm_sec = tm->tm_sec; ! *tzp = DetermineTimeZoneAbbrevOffset(tmp, abbrev, abbrevTz); ! tm->tm_isdst = tmp->tm_isdst; ! } ! ! /* timezone not specified? then use session timezone */ if (tzp != NULL && !(fmask & DTK_M(TZ))) { struct pg_tm tt, *************** DecodeNumberField(int len, char *str, in *** 2710,2717 **** * Interpret string as a numeric timezone. * * Return 0 if okay (and set *tzp), a DTERR code if not okay. - * - * NB: this must *not* ereport on failure; see commands/variable.c. */ int DecodeTimezone(char *str, int *tzp) --- 2879,2884 ---- *************** DecodeTimezone(char *str, int *tzp) *** 2776,2789 **** return 0; } /* DecodeSpecial() * Decode text string using lookup table. * * Implement a cache lookup since it is likely that dates * will be related in format. - * - * NB: this must *not* ereport on failure; - * see commands/variable.c. */ int DecodeSpecial(int field, char *lowtoken, int *val) --- 2943,3017 ---- return 0; } + + /* DecodeTimezoneAbbrev() + * Interpret string as a timezone abbreviation, if possible. + * + * Returns an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if + * string is not any known abbreviation. On success, set *offset and *tz to + * represent the UTC offset (for TZ or DTZ) or underlying zone (for DYNTZ). + * Note that full timezone names (such as America/New_York) are not handled + * here, mostly for historical reasons. + * + * Given string must be lowercased already. + * + * Implement a cache lookup since it is likely that dates + * will be related in format. + */ + int + DecodeTimezoneAbbrev(int field, char *lowtoken, + int *offset, pg_tz **tz) + { + int type; + const datetkn *tp; + + tp = abbrevcache[field]; + /* use strncmp so that we match truncated tokens */ + if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) + { + if (zoneabbrevtbl) + tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs, + zoneabbrevtbl->numabbrevs); + else + tp = NULL; + } + if (tp == NULL) + { + type = UNKNOWN_FIELD; + *offset = 0; + *tz = NULL; + } + else + { + abbrevcache[field] = tp; + type = tp->type; + if (type == DYNTZ) + { + *offset = 0; + *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp); + } + else + { + *offset = tp->value; + *tz = NULL; + } + } + + return type; + } + + /* DecodeSpecial() * Decode text string using lookup table. * + * Recognizes the keywords listed in datetktbl. + * Note: at one time this would also recognize timezone abbreviations, + * but no more; use DecodeTimezoneAbbrev for that. + * + * Given string must be lowercased already. + * * Implement a cache lookup since it is likely that dates * will be related in format. */ int DecodeSpecial(int field, char *lowtoken, int *val) *************** DecodeSpecial(int field, char *lowtoken, *** 2792,2802 **** const datetkn *tp; tp = datecache[field]; if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) { ! tp = datebsearch(lowtoken, timezonetktbl, sztimezonetktbl); ! if (tp == NULL) ! tp = datebsearch(lowtoken, datetktbl, szdatetktbl); } if (tp == NULL) { --- 3020,3029 ---- const datetkn *tp; tp = datecache[field]; + /* use strncmp so that we match truncated tokens */ if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) { ! tp = datebsearch(lowtoken, datetktbl, szdatetktbl); } if (tp == NULL) { *************** DecodeSpecial(int field, char *lowtoken, *** 2807,2824 **** { datecache[field] = tp; type = tp->type; ! switch (type) ! { ! case TZ: ! case DTZ: ! case DTZMOD: ! *val = FROMVAL(tp); ! break; ! ! default: ! *val = tp->value; ! break; ! } } return type; --- 3034,3040 ---- { datecache[field] = tp; type = tp->type; ! *val = tp->value; } return type; *************** DecodeISO8601Interval(char *str, *** 3494,3501 **** /* DecodeUnits() * Decode text string using lookup table. ! * This routine supports time interval decoding ! * (hence, it need not recognize timezone names). */ int DecodeUnits(int field, char *lowtoken, int *val) --- 3710,3722 ---- /* DecodeUnits() * Decode text string using lookup table. ! * ! * This routine recognizes keywords associated with time interval units. ! * ! * Given string must be lowercased already. ! * ! * Implement a cache lookup since it is likely that dates ! * will be related in format. */ int DecodeUnits(int field, char *lowtoken, int *val) *************** DecodeUnits(int field, char *lowtoken, i *** 3504,3509 **** --- 3725,3731 ---- const datetkn *tp; tp = deltacache[field]; + /* use strncmp so that we match truncated tokens */ if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0) { tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl); *************** DecodeUnits(int field, char *lowtoken, i *** 3517,3526 **** { deltacache[field] = tp; type = tp->type; ! if (type == TZ || type == DTZ) ! *val = FROMVAL(tp); ! else ! *val = tp->value; } return type; --- 3739,3745 ---- { deltacache[field] = tp; type = tp->type; ! *val = tp->value; } return type; *************** datebsearch(const char *key, const datet *** 3593,3601 **** while (last >= base) { position = base + ((last - base) >> 1); ! result = key[0] - position->token[0]; if (result == 0) { result = strncmp(key, position->token, TOKMAXLEN); if (result == 0) return position; --- 3812,3822 ---- while (last >= base) { position = base + ((last - base) >> 1); ! /* precheck the first character for a bit of extra speed */ ! result = (int) key[0] - (int) position->token[0]; if (result == 0) { + /* use strncmp so that we match truncated tokens */ result = strncmp(key, position->token, TOKMAXLEN); if (result == 0) return position; *************** CheckDateTokenTable(const char *tablenam *** 4142,4156 **** bool ok = true; int i; ! for (i = 1; i < nel; i++) { ! if (strncmp(base[i - 1].token, base[i].token, TOKMAXLEN) >= 0) { /* %.*s is safe since all our tokens are ASCII */ ! elog(LOG, "ordering error in %s table: \"%.*s\" >= \"%.*s\"", tablename, ! TOKMAXLEN, base[i - 1].token, ! TOKMAXLEN, base[i].token); ok = false; } } --- 4363,4388 ---- bool ok = true; int i; ! for (i = 0; i < nel; i++) { ! /* check for token strings that don't fit */ ! if (strlen(base[i].token) > TOKMAXLEN) { /* %.*s is safe since all our tokens are ASCII */ ! elog(LOG, "token too long in %s table: \"%.*s\"", tablename, ! TOKMAXLEN + 1, base[i].token); ! ok = false; ! break; /* don't risk applying strcmp */ ! } ! /* check for out of order */ ! if (i > 0 && ! strcmp(base[i - 1].token, base[i].token) >= 0) ! { ! elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"", ! tablename, ! base[i - 1].token, ! base[i].token); ok = false; } } *************** TemporalTransform(int32 max_precis, Node *** 4208,4234 **** /* * This function gets called during timezone config file load or reload * to create the final array of timezone tokens. The argument array ! * is already sorted in name order. The data is converted to datetkn ! * format and installed in *tbl, which must be allocated by the caller. */ ! void ! ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl, ! struct tzEntry *abbrevs, int n) { ! datetkn *newtbl = tbl->abbrevs; int i; tbl->numabbrevs = n; for (i = 0; i < n; i++) { ! /* do NOT use strlcpy here; token field need not be null-terminated */ ! strncpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN); ! newtbl[i].type = abbrevs[i].is_dst ? DTZ : TZ; ! TOVAL(&newtbl[i], abbrevs[i].offset / MINS_PER_HOUR); } /* Check the ordering, if testing */ ! Assert(CheckDateTokenTable("timezone offset", newtbl, n)); } /* --- 4440,4527 ---- /* * This function gets called during timezone config file load or reload * to create the final array of timezone tokens. The argument array ! * is already sorted in name order. ! * ! * The result is a TimeZoneAbbrevTable (which must be a single malloc'd chunk) ! * or NULL on malloc failure. No other error conditions are defined. */ ! TimeZoneAbbrevTable * ! ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n) { ! TimeZoneAbbrevTable *tbl; ! Size tbl_size; int i; + /* Space for fixed fields and datetkn array */ + tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) + + n * sizeof(datetkn); + tbl_size = MAXALIGN(tbl_size); + /* Count up space for dynamic abbreviations */ + for (i = 0; i < n; i++) + { + struct tzEntry *abbr = abbrevs + i; + + if (abbr->zone != NULL) + { + Size dsize; + + dsize = offsetof(DynamicZoneAbbrev, zone) + + strlen(abbr->zone) + 1; + tbl_size += MAXALIGN(dsize); + } + } + + /* Alloc the result ... */ + tbl = malloc(tbl_size); + if (!tbl) + return NULL; + + /* ... and fill it in */ + tbl->tblsize = tbl_size; tbl->numabbrevs = n; + /* in this loop, tbl_size reprises the space calculation above */ + tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) + + n * sizeof(datetkn); + tbl_size = MAXALIGN(tbl_size); for (i = 0; i < n; i++) { ! struct tzEntry *abbr = abbrevs + i; ! datetkn *dtoken = tbl->abbrevs + i; ! ! /* use strlcpy to truncate name if necessary */ ! strlcpy(dtoken->token, abbr->abbrev, TOKMAXLEN + 1); ! if (abbr->zone != NULL) ! { ! /* Allocate a DynamicZoneAbbrev for this abbreviation */ ! DynamicZoneAbbrev *dtza; ! Size dsize; ! ! dtza = (DynamicZoneAbbrev *) ((char *) tbl + tbl_size); ! dtza->tz = NULL; ! strcpy(dtza->zone, abbr->zone); ! ! dtoken->type = DYNTZ; ! /* value is offset from table start to DynamicZoneAbbrev */ ! dtoken->value = (int32) tbl_size; ! ! dsize = offsetof(DynamicZoneAbbrev, zone) + ! strlen(abbr->zone) + 1; ! tbl_size += MAXALIGN(dsize); ! } ! else ! { ! dtoken->type = abbr->is_dst ? DTZ : TZ; ! dtoken->value = abbr->offset; ! } } + /* Assert the two loops above agreed on size calculations */ + Assert(tbl->tblsize == tbl_size); + /* Check the ordering, if testing */ ! Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n)); ! ! return tbl; } /* *************** ConvertTimeZoneAbbrevs(TimeZoneAbbrevTab *** 4239,4254 **** void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl) { ! int i; ! timezonetktbl = tbl->abbrevs; ! sztimezonetktbl = tbl->numabbrevs; ! /* clear date cache in case it contains any stale timezone names */ ! for (i = 0; i < MAXDATEFIELDS; i++) ! datecache[i] = NULL; } /* * This set-returning function reads all the available time zone abbreviations * and returns a set of (abbrev, utc_offset, is_dst). --- 4532,4577 ---- void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl) { ! zoneabbrevtbl = tbl; ! /* reset abbrevcache, which may contain pointers into old table */ ! memset(abbrevcache, 0, sizeof(abbrevcache)); ! } ! /* ! * Helper subroutine to locate pg_tz timezone for a dynamic abbreviation. ! */ ! static pg_tz * ! FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp) ! { ! DynamicZoneAbbrev *dtza; ! /* Just some sanity checks to prevent indexing off into nowhere */ ! Assert(tp->type == DYNTZ); ! Assert(tp->value > 0 && tp->value < tbl->tblsize); ! ! dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value); ! ! /* Look up the underlying zone if we haven't already */ ! if (dtza->tz == NULL) ! { ! dtza->tz = pg_tzset(dtza->zone); ! ! /* ! * Ideally we'd let the caller ereport instead of doing it here, but ! * then there is no way to report the bad time zone name. ! */ ! if (dtza->tz == NULL) ! ereport(ERROR, ! (errcode(ERRCODE_CONFIG_FILE_ERROR), ! errmsg("time zone \"%s\" not recognized", ! dtza->zone), ! errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".", ! tp->token))); ! } ! return dtza->tz; } + /* * This set-returning function reads all the available time zone abbreviations * and returns a set of (abbrev, utc_offset, is_dst). *************** pg_timezone_abbrevs(PG_FUNCTION_ARGS) *** 4262,4268 **** --- 4585,4594 ---- HeapTuple tuple; Datum values[3]; bool nulls[3]; + const datetkn *tp; char buffer[TOKMAXLEN + 1]; + int gmtoffset; + bool is_dst; unsigned char *p; struct pg_tm tm; Interval *resInterval; *************** pg_timezone_abbrevs(PG_FUNCTION_ARGS) *** 4306,4336 **** funcctx = SRF_PERCALL_SETUP(); pindex = (int *) funcctx->user_fctx; ! if (*pindex >= sztimezonetktbl) SRF_RETURN_DONE(funcctx); MemSet(nulls, 0, sizeof(nulls)); /* * Convert name to text, using upcasing conversion that is the inverse of * what ParseDateTime() uses. */ ! strncpy(buffer, timezonetktbl[*pindex].token, TOKMAXLEN); ! buffer[TOKMAXLEN] = '\0'; /* may not be null-terminated */ for (p = (unsigned char *) buffer; *p; p++) *p = pg_toupper(*p); values[0] = CStringGetTextDatum(buffer); MemSet(&tm, 0, sizeof(struct pg_tm)); ! tm.tm_min = (-1) * FROMVAL(&timezonetktbl[*pindex]); resInterval = (Interval *) palloc(sizeof(Interval)); tm2interval(&tm, 0, resInterval); values[1] = IntervalPGetDatum(resInterval); ! Assert(timezonetktbl[*pindex].type == DTZ || ! timezonetktbl[*pindex].type == TZ); ! values[2] = BoolGetDatum(timezonetktbl[*pindex].type == DTZ); (*pindex)++; --- 4632,4696 ---- funcctx = SRF_PERCALL_SETUP(); pindex = (int *) funcctx->user_fctx; ! if (zoneabbrevtbl == NULL || ! *pindex >= zoneabbrevtbl->numabbrevs) SRF_RETURN_DONE(funcctx); + tp = zoneabbrevtbl->abbrevs + *pindex; + + switch (tp->type) + { + case TZ: + gmtoffset = tp->value; + is_dst = false; + break; + case DTZ: + gmtoffset = tp->value; + is_dst = true; + break; + case DYNTZ: + { + /* Determine the current meaning of the abbrev */ + pg_tz *tzp; + TimestampTz now; + int isdst; + + tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp); + now = GetCurrentTransactionStartTimestamp(); + gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now, + tp->token, + tzp, + &isdst); + is_dst = (bool) isdst; + break; + } + default: + elog(ERROR, "unrecognized timezone type %d", (int) tp->type); + gmtoffset = 0; /* keep compiler quiet */ + is_dst = false; + break; + } + MemSet(nulls, 0, sizeof(nulls)); /* * Convert name to text, using upcasing conversion that is the inverse of * what ParseDateTime() uses. */ ! strlcpy(buffer, tp->token, sizeof(buffer)); for (p = (unsigned char *) buffer; *p; p++) *p = pg_toupper(*p); values[0] = CStringGetTextDatum(buffer); + /* Convert offset (in seconds) to an interval */ MemSet(&tm, 0, sizeof(struct pg_tm)); ! tm.tm_sec = gmtoffset; resInterval = (Interval *) palloc(sizeof(Interval)); tm2interval(&tm, 0, resInterval); values[1] = IntervalPGetDatum(resInterval); ! values[2] = BoolGetDatum(is_dst); (*pindex)++; diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 11007c6..410bf47 100644 *** a/src/backend/utils/adt/timestamp.c --- b/src/backend/utils/adt/timestamp.c *************** timestamptz_in(PG_FUNCTION_ARGS) *** 486,491 **** --- 486,494 ---- /* * Try to parse a timezone specification, and return its timezone offset value * if it's acceptable. Otherwise, an error is thrown. + * + * Note: some code paths update tm->tm_isdst, and some don't; current callers + * don't care, so we don't bother being consistent. */ static int parse_sane_timezone(struct pg_tm * tm, text *zone) *************** parse_sane_timezone(struct pg_tm * tm, t *** 499,510 **** /* * Look up the requested timezone. First we try to interpret it as a * numeric timezone specification; if DecodeTimezone decides it doesn't ! * like the format, we look in the date token table (to handle cases like ! * "EST"), and if that also fails, we look in the timezone database (to ! * handle cases like "America/New_York"). (This matches the order in ! * which timestamp input checks the cases; it's important because the ! * timezone database unwisely uses a few zone names that are identical to ! * offset abbreviations.) * * Note pg_tzset happily parses numeric input that DecodeTimezone would * reject. To avoid having it accept input that would otherwise be seen --- 502,513 ---- /* * Look up the requested timezone. First we try to interpret it as a * numeric timezone specification; if DecodeTimezone decides it doesn't ! * like the format, we look in the timezone abbreviation table (to handle ! * cases like "EST"), and if that also fails, we look in the timezone ! * database (to handle cases like "America/New_York"). (This matches the ! * order in which timestamp input checks the cases; it's important because ! * the timezone database unwisely uses a few zone names that are identical ! * to offset abbreviations.) * * Note pg_tzset happily parses numeric input that DecodeTimezone would * reject. To avoid having it accept input that would otherwise be seen *************** parse_sane_timezone(struct pg_tm * tm, t *** 524,529 **** --- 527,533 ---- char *lowzone; int type, val; + pg_tz *tzp; if (rt == DTERR_TZDISP_OVERFLOW) ereport(ERROR, *************** parse_sane_timezone(struct pg_tm * tm, t *** 534,552 **** (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("time zone \"%s\" not recognized", tzname))); lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeSpecial(0, lowzone, &val); if (type == TZ || type == DTZ) ! tz = val * MINS_PER_HOUR; else { ! pg_tz *tzp; ! tzp = pg_tzset(tzname); - if (tzp) tz = DetermineTimeZoneOffset(tm, tzp); else --- 538,563 ---- (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("time zone \"%s\" not recognized", tzname))); + /* DecodeTimezoneAbbrev requires lowercase input */ lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp); if (type == TZ || type == DTZ) ! { ! /* fixed-offset abbreviation */ ! tz = -val; ! } ! else if (type == DYNTZ) ! { ! /* dynamic-offset abbreviation, resolve using specified time */ ! tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp); ! } else { ! /* try it as a full zone name */ tzp = pg_tzset(tzname); if (tzp) tz = DetermineTimeZoneOffset(tm, tzp); else *************** timestamp_zone(PG_FUNCTION_ARGS) *** 4883,4921 **** int type, val; pg_tz *tzp; if (TIMESTAMP_NOT_FINITE(timestamp)) PG_RETURN_TIMESTAMPTZ(timestamp); /* ! * Look up the requested timezone. First we look in the date token table ! * (to handle cases like "EST"), and if that fails, we look in the ! * timezone database (to handle cases like "America/New_York"). (This ! * matches the order in which timestamp input checks the cases; it's ! * important because the timezone database unwisely uses a few zone names ! * that are identical to offset abbreviations.) */ text_to_cstring_buffer(zone, tzname, sizeof(tzname)); lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeSpecial(0, lowzone, &val); if (type == TZ || type == DTZ) { ! tz = -(val * MINS_PER_HOUR); result = dt2local(timestamp, tz); } else { tzp = pg_tzset(tzname); if (tzp) { /* Apply the timezone change */ - struct pg_tm tm; - fsec_t fsec; - if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), --- 4894,4945 ---- int type, val; pg_tz *tzp; + struct pg_tm tm; + fsec_t fsec; if (TIMESTAMP_NOT_FINITE(timestamp)) PG_RETURN_TIMESTAMPTZ(timestamp); /* ! * Look up the requested timezone. First we look in the timezone ! * abbreviation table (to handle cases like "EST"), and if that fails, we ! * look in the timezone database (to handle cases like ! * "America/New_York"). (This matches the order in which timestamp input ! * checks the cases; it's important because the timezone database unwisely ! * uses a few zone names that are identical to offset abbreviations.) */ text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + /* DecodeTimezoneAbbrev requires lowercase input */ lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp); if (type == TZ || type == DTZ) { ! /* fixed-offset abbreviation */ ! tz = val; ! result = dt2local(timestamp, tz); ! } ! else if (type == DYNTZ) ! { ! /* dynamic-offset abbreviation, resolve using specified time */ ! if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0) ! ereport(ERROR, ! (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), ! errmsg("timestamp out of range"))); ! tz = -DetermineTimeZoneAbbrevOffset(&tm, tzname, tzp); result = dt2local(timestamp, tz); } else { + /* try it as a full zone name */ tzp = pg_tzset(tzname); if (tzp) { /* Apply the timezone change */ if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), *************** timestamptz_zone(PG_FUNCTION_ARGS) *** 5061,5087 **** PG_RETURN_TIMESTAMP(timestamp); /* ! * Look up the requested timezone. First we look in the date token table ! * (to handle cases like "EST"), and if that fails, we look in the ! * timezone database (to handle cases like "America/New_York"). (This ! * matches the order in which timestamp input checks the cases; it's ! * important because the timezone database unwisely uses a few zone names ! * that are identical to offset abbreviations.) */ text_to_cstring_buffer(zone, tzname, sizeof(tzname)); lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeSpecial(0, lowzone, &val); if (type == TZ || type == DTZ) { ! tz = val * MINS_PER_HOUR; result = dt2local(timestamp, tz); } else { tzp = pg_tzset(tzname); if (tzp) { --- 5085,5123 ---- PG_RETURN_TIMESTAMP(timestamp); /* ! * Look up the requested timezone. First we look in the timezone ! * abbreviation table (to handle cases like "EST"), and if that fails, we ! * look in the timezone database (to handle cases like ! * "America/New_York"). (This matches the order in which timestamp input ! * checks the cases; it's important because the timezone database unwisely ! * uses a few zone names that are identical to offset abbreviations.) */ text_to_cstring_buffer(zone, tzname, sizeof(tzname)); + + /* DecodeTimezoneAbbrev requires lowercase input */ lowzone = downcase_truncate_identifier(tzname, strlen(tzname), false); ! type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp); if (type == TZ || type == DTZ) { ! /* fixed-offset abbreviation */ ! tz = -val; ! result = dt2local(timestamp, tz); ! } ! else if (type == DYNTZ) ! { ! /* dynamic-offset abbreviation, resolve using specified time */ ! int isdst; ! ! tz = DetermineTimeZoneAbbrevOffsetTS(timestamp, tzname, tzp, &isdst); result = dt2local(timestamp, tz); } else { + /* try it as a full zone name */ tzp = pg_tzset(tzname); if (tzp) { diff --git a/src/backend/utils/misc/tzparser.c b/src/backend/utils/misc/tzparser.c index 6a5a7b3..a6a12ff 100644 *** a/src/backend/utils/misc/tzparser.c --- b/src/backend/utils/misc/tzparser.c *************** validateTzEntry(tzEntry *tzentry) *** 63,75 **** tzentry->filename, tzentry->lineno); return false; } - if (tzentry->offset % 900 != 0) - { - GUC_check_errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line %d", - tzentry->offset, - tzentry->filename, tzentry->lineno); - return false; - } /* * Sanity-check the offset: shouldn't exceed 14 hours --- 63,68 ---- *************** validateTzEntry(tzEntry *tzentry) *** 93,99 **** } /* ! * Attempt to parse the line as a timezone abbrev spec (name, offset, dst) * * Returns TRUE if OK, else false; data is stored in *tzentry */ --- 86,96 ---- } /* ! * Attempt to parse the line as a timezone abbrev spec ! * ! * Valid formats are: ! * name zone ! * name offset dst * * Returns TRUE if OK, else false; data is stored in *tzentry */ *************** splitTzLine(const char *filename, int li *** 116,122 **** filename, lineno); return false; } ! tzentry->abbrev = abbrev; offset = strtok(NULL, WHITESPACE); if (!offset) --- 113,119 ---- filename, lineno); return false; } ! tzentry->abbrev = pstrdup(abbrev); offset = strtok(NULL, WHITESPACE); if (!offset) *************** splitTzLine(const char *filename, int li *** 125,149 **** filename, lineno); return false; } - tzentry->offset = strtol(offset, &offset_endptr, 10); - if (offset_endptr == offset || *offset_endptr != '\0') - { - GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d", - filename, lineno); - return false; - } ! is_dst = strtok(NULL, WHITESPACE); ! if (is_dst && pg_strcasecmp(is_dst, "D") == 0) { ! tzentry->is_dst = true; ! remain = strtok(NULL, WHITESPACE); } else { ! /* there was no 'D' dst specifier */ tzentry->is_dst = false; ! remain = is_dst; } if (!remain) /* no more non-whitespace chars */ --- 122,164 ---- filename, lineno); return false; } ! /* We assume zone names don't begin with a digit or sign */ ! if (isdigit((unsigned char) *offset) || *offset == '+' || *offset == '-') { ! tzentry->zone = NULL; ! tzentry->offset = strtol(offset, &offset_endptr, 10); ! if (offset_endptr == offset || *offset_endptr != '\0') ! { ! GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d", ! filename, lineno); ! return false; ! } ! ! is_dst = strtok(NULL, WHITESPACE); ! if (is_dst && pg_strcasecmp(is_dst, "D") == 0) ! { ! tzentry->is_dst = true; ! remain = strtok(NULL, WHITESPACE); ! } ! else ! { ! /* there was no 'D' dst specifier */ ! tzentry->is_dst = false; ! remain = is_dst; ! } } else { ! /* ! * Assume entry is a zone name. We do not try to validate it by ! * looking up the zone, because that would force loading of a lot of ! * zones that probably will never be used in the current session. ! */ ! tzentry->zone = pstrdup(offset); ! tzentry->offset = 0; tzentry->is_dst = false; ! remain = strtok(NULL, WHITESPACE); } if (!remain) /* no more non-whitespace chars */ *************** addToArray(tzEntry **base, int *arraysiz *** 201,208 **** /* * Found a duplicate entry; complain unless it's the same. */ ! if (midptr->offset == entry->offset && ! midptr->is_dst == entry->is_dst) { /* return unchanged array */ return n; --- 216,226 ---- /* * Found a duplicate entry; complain unless it's the same. */ ! if ((midptr->zone == NULL && entry->zone == NULL && ! midptr->offset == entry->offset && ! midptr->is_dst == entry->is_dst) || ! (midptr->zone != NULL && entry->zone != NULL && ! strcmp(midptr->zone, entry->zone) == 0)) { /* return unchanged array */ return n; *************** addToArray(tzEntry **base, int *arraysiz *** 210,215 **** --- 228,234 ---- if (override) { /* same abbrev but something is different, override */ + midptr->zone = entry->zone; midptr->offset = entry->offset; midptr->is_dst = entry->is_dst; return n; *************** addToArray(tzEntry **base, int *arraysiz *** 239,247 **** memcpy(arrayptr, entry, sizeof(tzEntry)); - /* Must dup the abbrev to ensure it survives */ - arrayptr->abbrev = pstrdup(entry->abbrev); - return n + 1; } --- 258,263 ---- *************** load_tzoffsets(const char *filename) *** 446,460 **** /* Parse the file(s) */ n = ParseTzFile(filename, 0, &array, &arraysize, 0); ! /* If no errors so far, allocate result and let datetime.c convert data */ if (n >= 0) { ! result = malloc(offsetof(TimeZoneAbbrevTable, abbrevs) + ! n * sizeof(datetkn)); if (!result) GUC_check_errmsg("out of memory"); - else - ConvertTimeZoneAbbrevs(result, array, n); } /* Clean up */ --- 462,473 ---- /* Parse the file(s) */ n = ParseTzFile(filename, 0, &array, &arraysize, 0); ! /* If no errors so far, let datetime.c allocate memory & convert format */ if (n >= 0) { ! result = ConvertTimeZoneAbbrevs(array, n); if (!result) GUC_check_errmsg("out of memory"); } /* Clean up */ diff --git a/src/include/pgtime.h b/src/include/pgtime.h index b3c867a..a85bc27 100644 *** a/src/include/pgtime.h --- b/src/include/pgtime.h *************** extern int pg_next_dst_boundary(const pg *** 54,66 **** long int *after_gmtoff, int *after_isdst, const pg_tz *tz); ! extern size_t pg_strftime(char *s, size_t max, const char *format, ! const struct pg_tm * tm); ! extern bool pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff); extern const char *pg_get_timezone_name(pg_tz *tz); extern bool pg_tz_acceptable(pg_tz *tz); /* these functions and variables are in pgtz.c */ extern pg_tz *session_timezone; --- 54,73 ---- long int *after_gmtoff, int *after_isdst, const pg_tz *tz); ! extern bool pg_interpret_timezone_abbrev(const char *abbrev, ! const pg_time_t *timep, ! long int *gmtoff, ! int *isdst, ! const pg_tz *tz); extern bool pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff); extern const char *pg_get_timezone_name(pg_tz *tz); extern bool pg_tz_acceptable(pg_tz *tz); + /* these functions are in strftime.c */ + + extern size_t pg_strftime(char *s, size_t max, const char *format, + const struct pg_tm * tm); + /* these functions and variables are in pgtz.c */ extern pg_tz *session_timezone; diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 2e69503..9b53ee3 100644 *** a/src/include/utils/datetime.h --- b/src/include/utils/datetime.h *************** struct tzEntry; *** 77,83 **** #define BC 1 /* ! * Fields for time decoding. * * Can't have more of these than there are bits in an unsigned int * since these are turned into bit masks during parsing and decoding. --- 77,83 ---- #define BC 1 /* ! * Field types for time decoding. * * Can't have more of these than there are bits in an unsigned int * since these are turned into bit masks during parsing and decoding. *************** struct tzEntry; *** 93,101 **** #define YEAR 2 #define DAY 3 #define JULIAN 4 ! #define TZ 5 ! #define DTZ 6 ! #define DTZMOD 7 #define IGNORE_DTF 8 #define AMPM 9 #define HOUR 10 --- 93,101 ---- #define YEAR 2 #define DAY 3 #define JULIAN 4 ! #define TZ 5 /* fixed-offset timezone abbreviation */ ! #define DTZ 6 /* fixed-offset timezone abbrev, DST */ ! #define DYNTZ 7 /* dynamic timezone abbreviation */ #define IGNORE_DTF 8 #define AMPM 9 #define HOUR 10 *************** struct tzEntry; *** 119,136 **** #define DECADE 25 #define CENTURY 26 #define MILLENNIUM 27 /* reserved for unrecognized string values */ #define UNKNOWN_FIELD 31 /* * Token field definitions for time parsing and decoding. ! * These need to fit into the datetkn table type. ! * At the moment, that means keep them within [-127,127]. ! * These are also used for bit masks in DecodeDateDelta() * so actually restrict them to within [0,31] for now. * - thomas 97/06/19 ! * Not all of these fields are used for masks in DecodeDateDelta * so allow some larger than 31. - thomas 1997-11-17 */ #define DTK_NUMBER 0 --- 119,142 ---- #define DECADE 25 #define CENTURY 26 #define MILLENNIUM 27 + /* hack for parsing two-word timezone specs "MET DST" etc */ + #define DTZMOD 28 /* "DST" as a separate word */ /* reserved for unrecognized string values */ #define UNKNOWN_FIELD 31 /* * Token field definitions for time parsing and decoding. ! * ! * Some field type codes (see above) use these as the "value" in datetktbl[]. ! * These are also used for bit masks in DecodeDateTime and friends * so actually restrict them to within [0,31] for now. * - thomas 97/06/19 ! * Not all of these fields are used for masks in DecodeDateTime * so allow some larger than 31. - thomas 1997-11-17 + * + * Caution: there are undocumented assumptions in the code that most of these + * values are not equal to IGNORE_DTF nor RESERV. Be very careful when + * renumbering values in either of these apparently-independent lists :-( */ #define DTK_NUMBER 0 *************** struct tzEntry; *** 203,220 **** /* keep this struct small; it gets used a lot */ typedef struct { ! char token[TOKMAXLEN]; ! char type; ! char value; /* this may be unsigned, alas */ } datetkn; /* one of its uses is in tables of time zone abbreviations */ typedef struct TimeZoneAbbrevTable { ! int numabbrevs; datetkn abbrevs[1]; /* VARIABLE LENGTH ARRAY */ } TimeZoneAbbrevTable; /* FMODULO() * Macro to replace modf(), which is broken on some platforms. --- 209,235 ---- /* keep this struct small; it gets used a lot */ typedef struct { ! char token[TOKMAXLEN + 1]; /* always NUL-terminated */ ! char type; /* see field type codes above */ ! int32 value; /* meaning depends on type */ } datetkn; /* one of its uses is in tables of time zone abbreviations */ typedef struct TimeZoneAbbrevTable { ! Size tblsize; /* size in bytes of TimeZoneAbbrevTable */ ! int numabbrevs; /* number of entries in abbrevs[] array */ datetkn abbrevs[1]; /* VARIABLE LENGTH ARRAY */ + /* DynamicZoneAbbrev(s) may follow the abbrevs[] array */ } TimeZoneAbbrevTable; + /* auxiliary data for a dynamic time zone abbreviation (non-fixed-offset) */ + typedef struct DynamicZoneAbbrev + { + pg_tz *tz; /* NULL if not yet looked up */ + char zone[1]; /* zone name (var length, NUL-terminated) */ + } DynamicZoneAbbrev; + /* FMODULO() * Macro to replace modf(), which is broken on some platforms. *************** extern void DateTimeParseError(int dterr *** 296,301 **** --- 311,319 ---- const char *datatype) __attribute__((noreturn)); extern int DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp); + extern int DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp); + extern int DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr, + pg_tz *tzp, int *isdst); extern void EncodeDateOnly(struct pg_tm * tm, int style, char *str); extern void EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str); *************** extern void EncodeInterval(struct pg_tm *** 305,310 **** --- 323,330 ---- extern int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, struct pg_tm * tm); + extern int DecodeTimezoneAbbrev(int field, char *lowtoken, + int *offset, pg_tz **tz); extern int DecodeSpecial(int field, char *lowtoken, int *val); extern int DecodeUnits(int field, char *lowtoken, int *val); *************** extern Node *TemporalTransform(int32 max *** 314,321 **** extern bool CheckDateTokenTables(void); ! extern void ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl, ! struct tzEntry *abbrevs, int n); extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS); --- 334,341 ---- extern bool CheckDateTokenTables(void); ! extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, ! int n); extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl); extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS); diff --git a/src/include/utils/tzparser.h b/src/include/utils/tzparser.h index 82728a6..637f449 100644 *** a/src/include/utils/tzparser.h --- b/src/include/utils/tzparser.h *************** *** 22,31 **** */ typedef struct tzEntry { ! /* the actual data: TZ abbrev (downcased), offset, DST flag */ ! char *abbrev; ! int offset; /* in seconds from UTC */ ! bool is_dst; /* source information (for error messages) */ int lineno; const char *filename; --- 22,33 ---- */ typedef struct tzEntry { ! /* the actual data */ ! char *abbrev; /* TZ abbreviation (downcased) */ ! char *zone; /* zone name if dynamic abbrev, else NULL */ ! /* for a dynamic abbreviation, offset/is_dst are not used */ ! int offset; /* offset in seconds from UTC */ ! bool is_dst; /* true if a DST abbreviation */ /* source information (for error messages) */ int lineno; const char *filename; diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h index c2635c7..145e2b7 100644 *** a/src/interfaces/ecpg/pgtypeslib/dt.h --- b/src/interfaces/ecpg/pgtypeslib/dt.h *************** typedef double fsec_t; *** 42,47 **** --- 42,48 ---- #define DAGO "ago" + #define DCURRENT "current" #define EPOCH "epoch" #define INVALID "invalid" #define EARLY "-infinity" *************** typedef double fsec_t; *** 68,74 **** #define DA_D "ad" #define DB_C "bc" #define DTIMEZONE "timezone" - #define DCURRENT "current" /* * Fundamental time field definitions for parsing. --- 69,74 ---- *************** typedef double fsec_t; *** 85,91 **** #define BC 1 /* ! * Fields for time decoding. * * Can't have more of these than there are bits in an unsigned int * since these are turned into bit masks during parsing and decoding. --- 85,91 ---- #define BC 1 /* ! * Field types for time decoding. * * Can't have more of these than there are bits in an unsigned int * since these are turned into bit masks during parsing and decoding. *************** typedef double fsec_t; *** 103,111 **** #define YEAR 2 #define DAY 3 #define JULIAN 4 ! #define TZ 5 ! #define DTZ 6 ! #define DTZMOD 7 #define IGNORE_DTF 8 #define AMPM 9 #define HOUR 10 --- 103,111 ---- #define YEAR 2 #define DAY 3 #define JULIAN 4 ! #define TZ 5 /* fixed-offset timezone abbreviation */ ! #define DTZ 6 /* fixed-offset timezone abbrev, DST */ ! #define DYNTZ 7 /* dynamic timezone abbr (unimplemented) */ #define IGNORE_DTF 8 #define AMPM 9 #define HOUR 10 *************** typedef double fsec_t; *** 124,142 **** /* generic fields to help with parsing */ #define ISODATE 22 #define ISOTIME 23 /* reserved for unrecognized string values */ #define UNKNOWN_FIELD 31 /* * Token field definitions for time parsing and decoding. ! * These need to fit into the datetkn table type. ! * At the moment, that means keep them within [-127,127]. ! * These are also used for bit masks in DecodeDateDelta() * so actually restrict them to within [0,31] for now. * - thomas 97/06/19 ! * Not all of these fields are used for masks in DecodeDateDelta * so allow some larger than 31. - thomas 1997-11-17 */ #define DTK_NUMBER 0 --- 124,148 ---- /* generic fields to help with parsing */ #define ISODATE 22 #define ISOTIME 23 + /* hack for parsing two-word timezone specs "MET DST" etc */ + #define DTZMOD 28 /* "DST" as a separate word */ /* reserved for unrecognized string values */ #define UNKNOWN_FIELD 31 /* * Token field definitions for time parsing and decoding. ! * ! * Some field type codes (see above) use these as the "value" in datetktbl[]. ! * These are also used for bit masks in DecodeDateTime and friends * so actually restrict them to within [0,31] for now. * - thomas 97/06/19 ! * Not all of these fields are used for masks in DecodeDateTime * so allow some larger than 31. - thomas 1997-11-17 + * + * Caution: there are undocumented assumptions in the code that most of these + * values are not equal to IGNORE_DTF nor RESERV. Be very careful when + * renumbering values in either of these apparently-independent lists :-( */ #define DTK_NUMBER 0 *************** typedef double fsec_t; *** 207,219 **** /* keep this struct small; it gets used a lot */ typedef struct { ! #if defined(_AIX) ! char *token; ! #else ! char token[TOKMAXLEN]; ! #endif /* _AIX */ ! char type; ! char value; /* this may be unsigned, alas */ } datetkn; --- 213,221 ---- /* keep this struct small; it gets used a lot */ typedef struct { ! char token[TOKMAXLEN + 1]; /* always NUL-terminated */ ! char type; /* see field type codes above */ ! int32 value; /* meaning depends on type */ } datetkn; diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c index 2286acd..7fdd09b 100644 *** a/src/interfaces/ecpg/pgtypeslib/dt_common.c --- b/src/interfaces/ecpg/pgtypeslib/dt_common.c *************** int day_tab[2][13] = { *** 16,53 **** typedef long AbsoluteTime; - #define ABS_SIGNBIT ((char) 0200) - #define POS(n) (n) - #define NEG(n) ((n)|ABS_SIGNBIT) - #define FROMVAL(tp) (-SIGNEDCHAR((tp)->value) * 15) /* uncompress */ - #define VALMASK ((char) 0177) - #define SIGNEDCHAR(c) ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c)) - static datetkn datetktbl[] = { /* text, token, lexval */ {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */ ! {"acsst", DTZ, POS(42)}, /* Cent. Australia */ ! {"acst", DTZ, NEG(16)}, /* Atlantic/Porto Acre */ ! {"act", TZ, NEG(20)}, /* Atlantic/Porto Acre */ {DA_D, ADBC, AD}, /* "ad" for years >= 0 */ ! {"adt", DTZ, NEG(12)}, /* Atlantic Daylight Time */ ! {"aesst", DTZ, POS(44)}, /* E. Australia */ ! {"aest", TZ, POS(40)}, /* Australia Eastern Std Time */ ! {"aft", TZ, POS(18)}, /* Kabul */ ! {"ahst", TZ, NEG(40)}, /* Alaska-Hawaii Std Time */ ! {"akdt", DTZ, NEG(32)}, /* Alaska Daylight Time */ ! {"akst", DTZ, NEG(36)}, /* Alaska Standard Time */ {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */ ! {"almst", TZ, POS(28)}, /* Almaty Savings Time */ ! {"almt", TZ, POS(24)}, /* Almaty Time */ {"am", AMPM, AM}, ! {"amst", DTZ, POS(20)}, /* Armenia Summer Time (Yerevan) */ #if 0 ! {"amst", DTZ, NEG(12)}, /* Porto Velho */ #endif ! {"amt", TZ, POS(16)}, /* Armenia Time (Yerevan) */ ! {"anast", DTZ, POS(52)}, /* Anadyr Summer Time (Russia) */ ! {"anat", TZ, POS(48)}, /* Anadyr Time (Russia) */ {"apr", MONTH, 4}, {"april", MONTH, 4}, #if 0 --- 16,46 ---- typedef long AbsoluteTime; static datetkn datetktbl[] = { /* text, token, lexval */ {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */ ! {"acsst", DTZ, 37800}, /* Cent. Australia */ ! {"acst", DTZ, -14400}, /* Atlantic/Porto Acre */ ! {"act", TZ, -18000}, /* Atlantic/Porto Acre */ {DA_D, ADBC, AD}, /* "ad" for years >= 0 */ ! {"adt", DTZ, -10800}, /* Atlantic Daylight Time */ ! {"aesst", DTZ, 39600}, /* E. Australia */ ! {"aest", TZ, 36000}, /* Australia Eastern Std Time */ ! {"aft", TZ, 16200}, /* Kabul */ ! {"ahst", TZ, -36000}, /* Alaska-Hawaii Std Time */ ! {"akdt", DTZ, -28800}, /* Alaska Daylight Time */ ! {"akst", DTZ, -32400}, /* Alaska Standard Time */ {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */ ! {"almst", TZ, 25200}, /* Almaty Savings Time */ ! {"almt", TZ, 21600}, /* Almaty Time */ {"am", AMPM, AM}, ! {"amst", DTZ, 18000}, /* Armenia Summer Time (Yerevan) */ #if 0 ! {"amst", DTZ, -10800}, /* Porto Velho */ #endif ! {"amt", TZ, 14400}, /* Armenia Time (Yerevan) */ ! {"anast", DTZ, 46800}, /* Anadyr Summer Time (Russia) */ ! {"anat", TZ, 43200}, /* Anadyr Time (Russia) */ {"apr", MONTH, 4}, {"april", MONTH, 4}, #if 0 *************** static datetkn datetktbl[] = { *** 55,430 **** aqtt arst #endif ! {"art", TZ, NEG(12)}, /* Argentina Time */ #if 0 ashst ast /* Atlantic Standard Time, Arabia Standard * Time, Acre Standard Time */ #endif ! {"ast", TZ, NEG(16)}, /* Atlantic Std Time (Canada) */ {"at", IGNORE_DTF, 0}, /* "at" (throwaway) */ {"aug", MONTH, 8}, {"august", MONTH, 8}, ! {"awsst", DTZ, POS(36)}, /* W. Australia */ ! {"awst", TZ, POS(32)}, /* W. Australia */ ! {"awt", DTZ, NEG(12)}, ! {"azost", DTZ, POS(0)}, /* Azores Summer Time */ ! {"azot", TZ, NEG(4)}, /* Azores Time */ ! {"azst", DTZ, POS(20)}, /* Azerbaijan Summer Time */ ! {"azt", TZ, POS(16)}, /* Azerbaijan Time */ {DB_C, ADBC, BC}, /* "bc" for years < 0 */ ! {"bdst", TZ, POS(8)}, /* British Double Summer Time */ ! {"bdt", TZ, POS(24)}, /* Dacca */ ! {"bnt", TZ, POS(32)}, /* Brunei Darussalam Time */ ! {"bort", TZ, POS(32)}, /* Borneo Time (Indonesia) */ #if 0 bortst bost #endif ! {"bot", TZ, NEG(16)}, /* Bolivia Time */ ! {"bra", TZ, NEG(12)}, /* Brazil Time */ #if 0 brst brt #endif ! {"bst", DTZ, POS(4)}, /* British Summer Time */ #if 0 ! {"bst", TZ, NEG(12)}, /* Brazil Standard Time */ ! {"bst", DTZ, NEG(44)}, /* Bering Summer Time */ #endif ! {"bt", TZ, POS(12)}, /* Baghdad Time */ ! {"btt", TZ, POS(24)}, /* Bhutan Time */ ! {"cadt", DTZ, POS(42)}, /* Central Australian DST */ ! {"cast", TZ, POS(38)}, /* Central Australian ST */ ! {"cat", TZ, NEG(40)}, /* Central Alaska Time */ ! {"cct", TZ, POS(32)}, /* China Coast Time */ #if 0 ! {"cct", TZ, POS(26)}, /* Indian Cocos (Island) Time */ #endif ! {"cdt", DTZ, NEG(20)}, /* Central Daylight Time */ ! {"cest", DTZ, POS(8)}, /* Central European Dayl.Time */ ! {"cet", TZ, POS(4)}, /* Central European Time */ ! {"cetdst", DTZ, POS(8)}, /* Central European Dayl.Time */ ! {"chadt", DTZ, POS(55)}, /* Chatham Island Daylight Time (13:45) */ ! {"chast", TZ, POS(51)}, /* Chatham Island Time (12:45) */ #if 0 ckhst #endif ! {"ckt", TZ, POS(48)}, /* Cook Islands Time */ ! {"clst", DTZ, NEG(12)}, /* Chile Summer Time */ ! {"clt", TZ, NEG(16)}, /* Chile Time */ #if 0 cost #endif ! {"cot", TZ, NEG(20)}, /* Columbia Time */ ! {"cst", TZ, NEG(24)}, /* Central Standard Time */ {DCURRENT, RESERV, DTK_CURRENT}, /* "current" is always now */ #if 0 cvst #endif ! {"cvt", TZ, POS(28)}, /* Christmas Island Time (Indian Ocean) */ ! {"cxt", TZ, POS(28)}, /* Christmas Island Time (Indian Ocean) */ {"d", UNITS, DTK_DAY}, /* "day of month" for ISO input */ ! {"davt", TZ, POS(28)}, /* Davis Time (Antarctica) */ ! {"ddut", TZ, POS(40)}, /* Dumont-d'Urville Time (Antarctica) */ {"dec", MONTH, 12}, {"december", MONTH, 12}, ! {"dnt", TZ, POS(4)}, /* Dansk Normal Tid */ {"dow", RESERV, DTK_DOW}, /* day of week */ {"doy", RESERV, DTK_DOY}, /* day of year */ ! {"dst", DTZMOD, 6}, #if 0 ! {"dusst", DTZ, POS(24)}, /* Dushanbe Summer Time */ #endif ! {"easst", DTZ, NEG(20)}, /* Easter Island Summer Time */ ! {"east", TZ, NEG(24)}, /* Easter Island Time */ ! {"eat", TZ, POS(12)}, /* East Africa Time */ #if 0 ! {"east", DTZ, POS(16)}, /* Indian Antananarivo Savings Time */ ! {"eat", TZ, POS(12)}, /* Indian Antananarivo Time */ ! {"ect", TZ, NEG(16)}, /* Eastern Caribbean Time */ ! {"ect", TZ, NEG(20)}, /* Ecuador Time */ #endif ! {"edt", DTZ, NEG(16)}, /* Eastern Daylight Time */ ! {"eest", DTZ, POS(12)}, /* Eastern Europe Summer Time */ ! {"eet", TZ, POS(8)}, /* East. Europe, USSR Zone 1 */ ! {"eetdst", DTZ, POS(12)}, /* Eastern Europe Daylight Time */ ! {"egst", DTZ, POS(0)}, /* East Greenland Summer Time */ ! {"egt", TZ, NEG(4)}, /* East Greenland Time */ #if 0 ehdt #endif {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */ ! {"est", TZ, NEG(20)}, /* Eastern Standard Time */ {"feb", MONTH, 2}, {"february", MONTH, 2}, ! {"fjst", DTZ, NEG(52)}, /* Fiji Summer Time (13 hour offset!) */ ! {"fjt", TZ, NEG(48)}, /* Fiji Time */ ! {"fkst", DTZ, NEG(12)}, /* Falkland Islands Summer Time */ ! {"fkt", TZ, NEG(8)}, /* Falkland Islands Time */ #if 0 fnst fnt #endif {"fri", DOW, 5}, {"friday", DOW, 5}, ! {"fst", TZ, POS(4)}, /* French Summer Time */ ! {"fwt", DTZ, POS(8)}, /* French Winter Time */ ! {"galt", TZ, NEG(24)}, /* Galapagos Time */ ! {"gamt", TZ, NEG(36)}, /* Gambier Time */ ! {"gest", DTZ, POS(20)}, /* Georgia Summer Time */ ! {"get", TZ, POS(16)}, /* Georgia Time */ ! {"gft", TZ, NEG(12)}, /* French Guiana Time */ #if 0 ghst #endif ! {"gilt", TZ, POS(48)}, /* Gilbert Islands Time */ ! {"gmt", TZ, POS(0)}, /* Greenwish Mean Time */ ! {"gst", TZ, POS(40)}, /* Guam Std Time, USSR Zone 9 */ ! {"gyt", TZ, NEG(16)}, /* Guyana Time */ {"h", UNITS, DTK_HOUR}, /* "hour" */ #if 0 hadt hast #endif ! {"hdt", DTZ, NEG(36)}, /* Hawaii/Alaska Daylight Time */ #if 0 hkst #endif ! {"hkt", TZ, POS(32)}, /* Hong Kong Time */ #if 0 ! {"hmt", TZ, POS(12)}, /* Hellas ? ? */ hovst hovt #endif ! {"hst", TZ, NEG(40)}, /* Hawaii Std Time */ #if 0 hwt #endif ! {"ict", TZ, POS(28)}, /* Indochina Time */ ! {"idle", TZ, POS(48)}, /* Intl. Date Line, East */ ! {"idlw", TZ, NEG(48)}, /* Intl. Date Line, West */ #if 0 idt /* Israeli, Iran, Indian Daylight Time */ #endif {LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */ {INVALID, RESERV, DTK_INVALID}, /* "invalid" reserved for bad time */ ! {"iot", TZ, POS(20)}, /* Indian Chagos Time */ ! {"irkst", DTZ, POS(36)}, /* Irkutsk Summer Time */ ! {"irkt", TZ, POS(32)}, /* Irkutsk Time */ ! {"irt", TZ, POS(14)}, /* Iran Time */ {"isodow", RESERV, DTK_ISODOW}, /* ISO day of week, Sunday == 7 */ #if 0 isst #endif ! {"ist", TZ, POS(8)}, /* Israel */ ! {"it", TZ, POS(14)}, /* Iran Time */ {"j", UNITS, DTK_JULIAN}, {"jan", MONTH, 1}, {"january", MONTH, 1}, ! {"javt", TZ, POS(28)}, /* Java Time (07:00? see JT) */ ! {"jayt", TZ, POS(36)}, /* Jayapura Time (Indonesia) */ {"jd", UNITS, DTK_JULIAN}, ! {"jst", TZ, POS(36)}, /* Japan Std Time,USSR Zone 8 */ ! {"jt", TZ, POS(30)}, /* Java Time (07:30? see JAVT) */ {"jul", MONTH, 7}, {"julian", UNITS, DTK_JULIAN}, {"july", MONTH, 7}, {"jun", MONTH, 6}, {"june", MONTH, 6}, ! {"kdt", DTZ, POS(40)}, /* Korea Daylight Time */ ! {"kgst", DTZ, POS(24)}, /* Kyrgyzstan Summer Time */ ! {"kgt", TZ, POS(20)}, /* Kyrgyzstan Time */ ! {"kost", TZ, POS(48)}, /* Kosrae Time */ ! {"krast", DTZ, POS(28)}, /* Krasnoyarsk Summer Time */ ! {"krat", TZ, POS(32)}, /* Krasnoyarsk Standard Time */ ! {"kst", TZ, POS(36)}, /* Korea Standard Time */ ! {"lhdt", DTZ, POS(44)}, /* Lord Howe Daylight Time, Australia */ ! {"lhst", TZ, POS(42)}, /* Lord Howe Standard Time, Australia */ ! {"ligt", TZ, POS(40)}, /* From Melbourne, Australia */ ! {"lint", TZ, POS(56)}, /* Line Islands Time (Kiribati; +14 hours!) */ ! {"lkt", TZ, POS(24)}, /* Lanka Time */ {"m", UNITS, DTK_MONTH}, /* "month" for ISO input */ ! {"magst", DTZ, POS(48)}, /* Magadan Summer Time */ ! {"magt", TZ, POS(44)}, /* Magadan Time */ {"mar", MONTH, 3}, {"march", MONTH, 3}, ! {"mart", TZ, NEG(38)}, /* Marquesas Time */ ! {"mawt", TZ, POS(24)}, /* Mawson, Antarctica */ {"may", MONTH, 5}, ! {"mdt", DTZ, NEG(24)}, /* Mountain Daylight Time */ ! {"mest", DTZ, POS(8)}, /* Middle Europe Summer Time */ ! {"met", TZ, POS(4)}, /* Middle Europe Time */ ! {"metdst", DTZ, POS(8)}, /* Middle Europe Daylight Time */ ! {"mewt", TZ, POS(4)}, /* Middle Europe Winter Time */ ! {"mez", TZ, POS(4)}, /* Middle Europe Zone */ ! {"mht", TZ, POS(48)}, /* Kwajalein */ {"mm", UNITS, DTK_MINUTE}, /* "minute" for ISO input */ ! {"mmt", TZ, POS(26)}, /* Myannar Time */ {"mon", DOW, 1}, {"monday", DOW, 1}, #if 0 most #endif ! {"mpt", TZ, POS(40)}, /* North Mariana Islands Time */ ! {"msd", DTZ, POS(16)}, /* Moscow Summer Time */ ! {"msk", TZ, POS(12)}, /* Moscow Time */ ! {"mst", TZ, NEG(28)}, /* Mountain Standard Time */ ! {"mt", TZ, POS(34)}, /* Moluccas Time */ ! {"mut", TZ, POS(16)}, /* Mauritius Island Time */ ! {"mvt", TZ, POS(20)}, /* Maldives Island Time */ ! {"myt", TZ, POS(32)}, /* Malaysia Time */ #if 0 ncst #endif ! {"nct", TZ, POS(44)}, /* New Caledonia Time */ ! {"ndt", DTZ, NEG(10)}, /* Nfld. Daylight Time */ ! {"nft", TZ, NEG(14)}, /* Newfoundland Standard Time */ ! {"nor", TZ, POS(4)}, /* Norway Standard Time */ {"nov", MONTH, 11}, {"november", MONTH, 11}, ! {"novst", DTZ, POS(28)}, /* Novosibirsk Summer Time */ ! {"novt", TZ, POS(24)}, /* Novosibirsk Standard Time */ {NOW, RESERV, DTK_NOW}, /* current transaction time */ ! {"npt", TZ, POS(23)}, /* Nepal Standard Time (GMT-5:45) */ ! {"nst", TZ, NEG(14)}, /* Nfld. Standard Time */ ! {"nt", TZ, NEG(44)}, /* Nome Time */ ! {"nut", TZ, NEG(44)}, /* Niue Time */ ! {"nzdt", DTZ, POS(52)}, /* New Zealand Daylight Time */ ! {"nzst", TZ, POS(48)}, /* New Zealand Standard Time */ ! {"nzt", TZ, POS(48)}, /* New Zealand Time */ {"oct", MONTH, 10}, {"october", MONTH, 10}, ! {"omsst", DTZ, POS(28)}, /* Omsk Summer Time */ ! {"omst", TZ, POS(24)}, /* Omsk Time */ {"on", IGNORE_DTF, 0}, /* "on" (throwaway) */ ! {"pdt", DTZ, NEG(28)}, /* Pacific Daylight Time */ #if 0 pest #endif ! {"pet", TZ, NEG(20)}, /* Peru Time */ ! {"petst", DTZ, POS(52)}, /* Petropavlovsk-Kamchatski Summer Time */ ! {"pett", TZ, POS(48)}, /* Petropavlovsk-Kamchatski Time */ ! {"pgt", TZ, POS(40)}, /* Papua New Guinea Time */ ! {"phot", TZ, POS(52)}, /* Phoenix Islands (Kiribati) Time */ #if 0 phst #endif ! {"pht", TZ, POS(32)}, /* Philippine Time */ ! {"pkt", TZ, POS(20)}, /* Pakistan Time */ {"pm", AMPM, PM}, ! {"pmdt", DTZ, NEG(8)}, /* Pierre & Miquelon Daylight Time */ #if 0 pmst #endif ! {"pont", TZ, POS(44)}, /* Ponape Time (Micronesia) */ ! {"pst", TZ, NEG(32)}, /* Pacific Standard Time */ ! {"pwt", TZ, POS(36)}, /* Palau Time */ ! {"pyst", DTZ, NEG(12)}, /* Paraguay Summer Time */ ! {"pyt", TZ, NEG(16)}, /* Paraguay Time */ ! {"ret", DTZ, POS(16)}, /* Reunion Island Time */ {"s", UNITS, DTK_SECOND}, /* "seconds" for ISO input */ ! {"sadt", DTZ, POS(42)}, /* S. Australian Dayl. Time */ #if 0 samst samt #endif ! {"sast", TZ, POS(38)}, /* South Australian Std Time */ {"sat", DOW, 6}, {"saturday", DOW, 6}, #if 0 sbt #endif ! {"sct", DTZ, POS(16)}, /* Mahe Island Time */ {"sep", MONTH, 9}, {"sept", MONTH, 9}, {"september", MONTH, 9}, ! {"set", TZ, NEG(4)}, /* Seychelles Time ?? */ #if 0 sgt #endif ! {"sst", DTZ, POS(8)}, /* Swedish Summer Time */ {"sun", DOW, 0}, {"sunday", DOW, 0}, ! {"swt", TZ, POS(4)}, /* Swedish Winter Time */ #if 0 syot #endif {"t", ISOTIME, DTK_TIME}, /* Filler for ISO time fields */ ! {"tft", TZ, POS(20)}, /* Kerguelen Time */ ! {"that", TZ, NEG(40)}, /* Tahiti Time */ {"thu", DOW, 4}, {"thur", DOW, 4}, {"thurs", DOW, 4}, {"thursday", DOW, 4}, ! {"tjt", TZ, POS(20)}, /* Tajikistan Time */ ! {"tkt", TZ, NEG(40)}, /* Tokelau Time */ ! {"tmt", TZ, POS(20)}, /* Turkmenistan Time */ {TODAY, RESERV, DTK_TODAY}, /* midnight */ {TOMORROW, RESERV, DTK_TOMORROW}, /* tomorrow midnight */ #if 0 tost #endif ! {"tot", TZ, POS(52)}, /* Tonga Time */ #if 0 tpt #endif ! {"truk", TZ, POS(40)}, /* Truk Time */ {"tue", DOW, 2}, {"tues", DOW, 2}, {"tuesday", DOW, 2}, ! {"tvt", TZ, POS(48)}, /* Tuvalu Time */ #if 0 uct #endif ! {"ulast", DTZ, POS(36)}, /* Ulan Bator Summer Time */ ! {"ulat", TZ, POS(32)}, /* Ulan Bator Time */ {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */ ! {"ut", TZ, POS(0)}, ! {"utc", TZ, POS(0)}, ! {"uyst", DTZ, NEG(8)}, /* Uruguay Summer Time */ ! {"uyt", TZ, NEG(12)}, /* Uruguay Time */ ! {"uzst", DTZ, POS(24)}, /* Uzbekistan Summer Time */ ! {"uzt", TZ, POS(20)}, /* Uzbekistan Time */ ! {"vet", TZ, NEG(16)}, /* Venezuela Time */ ! {"vlast", DTZ, POS(44)}, /* Vladivostok Summer Time */ ! {"vlat", TZ, POS(40)}, /* Vladivostok Time */ #if 0 vust #endif ! {"vut", TZ, POS(44)}, /* Vanuata Time */ ! {"wadt", DTZ, POS(32)}, /* West Australian DST */ ! {"wakt", TZ, POS(48)}, /* Wake Time */ #if 0 warst #endif ! {"wast", TZ, POS(28)}, /* West Australian Std Time */ ! {"wat", TZ, NEG(4)}, /* West Africa Time */ ! {"wdt", DTZ, POS(36)}, /* West Australian DST */ {"wed", DOW, 3}, {"wednesday", DOW, 3}, {"weds", DOW, 3}, ! {"west", DTZ, POS(4)}, /* Western Europe Summer Time */ ! {"wet", TZ, POS(0)}, /* Western Europe */ ! {"wetdst", DTZ, POS(4)}, /* Western Europe Daylight Savings Time */ ! {"wft", TZ, POS(48)}, /* Wallis and Futuna Time */ ! {"wgst", DTZ, NEG(8)}, /* West Greenland Summer Time */ ! {"wgt", TZ, NEG(12)}, /* West Greenland Time */ ! {"wst", TZ, POS(32)}, /* West Australian Standard Time */ {"y", UNITS, DTK_YEAR}, /* "year" for ISO input */ ! {"yakst", DTZ, POS(40)}, /* Yakutsk Summer Time */ ! {"yakt", TZ, POS(36)}, /* Yakutsk Time */ ! {"yapt", TZ, POS(40)}, /* Yap Time (Micronesia) */ ! {"ydt", DTZ, NEG(32)}, /* Yukon Daylight Time */ ! {"yekst", DTZ, POS(24)}, /* Yekaterinburg Summer Time */ ! {"yekt", TZ, POS(20)}, /* Yekaterinburg Time */ {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */ ! {"yst", TZ, NEG(36)}, /* Yukon Standard Time */ ! {"z", TZ, POS(0)}, /* time zone tag per ISO-8601 */ ! {"zp4", TZ, NEG(16)}, /* UTC +4 hours. */ ! {"zp5", TZ, NEG(20)}, /* UTC +5 hours. */ ! {"zp6", TZ, NEG(24)}, /* UTC +6 hours. */ ! {ZULU, TZ, POS(0)}, /* UTC */ }; static datetkn deltatktbl[] = { --- 48,423 ---- aqtt arst #endif ! {"art", TZ, -10800}, /* Argentina Time */ #if 0 ashst ast /* Atlantic Standard Time, Arabia Standard * Time, Acre Standard Time */ #endif ! {"ast", TZ, -14400}, /* Atlantic Std Time (Canada) */ {"at", IGNORE_DTF, 0}, /* "at" (throwaway) */ {"aug", MONTH, 8}, {"august", MONTH, 8}, ! {"awsst", DTZ, 32400}, /* W. Australia */ ! {"awst", TZ, 28800}, /* W. Australia */ ! {"awt", DTZ, -10800}, ! {"azost", DTZ, 0}, /* Azores Summer Time */ ! {"azot", TZ, -3600}, /* Azores Time */ ! {"azst", DTZ, 18000}, /* Azerbaijan Summer Time */ ! {"azt", TZ, 14400}, /* Azerbaijan Time */ {DB_C, ADBC, BC}, /* "bc" for years < 0 */ ! {"bdst", TZ, 7200}, /* British Double Summer Time */ ! {"bdt", TZ, 21600}, /* Dacca */ ! {"bnt", TZ, 28800}, /* Brunei Darussalam Time */ ! {"bort", TZ, 28800}, /* Borneo Time (Indonesia) */ #if 0 bortst bost #endif ! {"bot", TZ, -14400}, /* Bolivia Time */ ! {"bra", TZ, -10800}, /* Brazil Time */ #if 0 brst brt #endif ! {"bst", DTZ, 3600}, /* British Summer Time */ #if 0 ! {"bst", TZ, -10800}, /* Brazil Standard Time */ ! {"bst", DTZ, -39600}, /* Bering Summer Time */ #endif ! {"bt", TZ, 10800}, /* Baghdad Time */ ! {"btt", TZ, 21600}, /* Bhutan Time */ ! {"cadt", DTZ, 37800}, /* Central Australian DST */ ! {"cast", TZ, 34200}, /* Central Australian ST */ ! {"cat", TZ, -36000}, /* Central Alaska Time */ ! {"cct", TZ, 28800}, /* China Coast Time */ #if 0 ! {"cct", TZ, 23400}, /* Indian Cocos (Island) Time */ #endif ! {"cdt", DTZ, -18000}, /* Central Daylight Time */ ! {"cest", DTZ, 7200}, /* Central European Dayl.Time */ ! {"cet", TZ, 3600}, /* Central European Time */ ! {"cetdst", DTZ, 7200}, /* Central European Dayl.Time */ ! {"chadt", DTZ, 49500}, /* Chatham Island Daylight Time (13:45) */ ! {"chast", TZ, 45900}, /* Chatham Island Time (12:45) */ #if 0 ckhst #endif ! {"ckt", TZ, 43200}, /* Cook Islands Time */ ! {"clst", DTZ, -10800}, /* Chile Summer Time */ ! {"clt", TZ, -14400}, /* Chile Time */ #if 0 cost #endif ! {"cot", TZ, -18000}, /* Columbia Time */ ! {"cst", TZ, -21600}, /* Central Standard Time */ {DCURRENT, RESERV, DTK_CURRENT}, /* "current" is always now */ #if 0 cvst #endif ! {"cvt", TZ, 25200}, /* Christmas Island Time (Indian Ocean) */ ! {"cxt", TZ, 25200}, /* Christmas Island Time (Indian Ocean) */ {"d", UNITS, DTK_DAY}, /* "day of month" for ISO input */ ! {"davt", TZ, 25200}, /* Davis Time (Antarctica) */ ! {"ddut", TZ, 36000}, /* Dumont-d'Urville Time (Antarctica) */ {"dec", MONTH, 12}, {"december", MONTH, 12}, ! {"dnt", TZ, 3600}, /* Dansk Normal Tid */ {"dow", RESERV, DTK_DOW}, /* day of week */ {"doy", RESERV, DTK_DOY}, /* day of year */ ! {"dst", DTZMOD, SECS_PER_HOUR}, #if 0 ! {"dusst", DTZ, 21600}, /* Dushanbe Summer Time */ #endif ! {"easst", DTZ, -18000}, /* Easter Island Summer Time */ ! {"east", TZ, -21600}, /* Easter Island Time */ ! {"eat", TZ, 10800}, /* East Africa Time */ #if 0 ! {"east", DTZ, 14400}, /* Indian Antananarivo Savings Time */ ! {"eat", TZ, 10800}, /* Indian Antananarivo Time */ ! {"ect", TZ, -14400}, /* Eastern Caribbean Time */ ! {"ect", TZ, -18000}, /* Ecuador Time */ #endif ! {"edt", DTZ, -14400}, /* Eastern Daylight Time */ ! {"eest", DTZ, 10800}, /* Eastern Europe Summer Time */ ! {"eet", TZ, 7200}, /* East. Europe, USSR Zone 1 */ ! {"eetdst", DTZ, 10800}, /* Eastern Europe Daylight Time */ ! {"egst", DTZ, 0}, /* East Greenland Summer Time */ ! {"egt", TZ, -3600}, /* East Greenland Time */ #if 0 ehdt #endif {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */ ! {"est", TZ, -18000}, /* Eastern Standard Time */ {"feb", MONTH, 2}, {"february", MONTH, 2}, ! {"fjst", DTZ, -46800}, /* Fiji Summer Time (13 hour offset!) */ ! {"fjt", TZ, -43200}, /* Fiji Time */ ! {"fkst", DTZ, -10800}, /* Falkland Islands Summer Time */ ! {"fkt", TZ, -7200}, /* Falkland Islands Time */ #if 0 fnst fnt #endif {"fri", DOW, 5}, {"friday", DOW, 5}, ! {"fst", TZ, 3600}, /* French Summer Time */ ! {"fwt", DTZ, 7200}, /* French Winter Time */ ! {"galt", TZ, -21600}, /* Galapagos Time */ ! {"gamt", TZ, -32400}, /* Gambier Time */ ! {"gest", DTZ, 18000}, /* Georgia Summer Time */ ! {"get", TZ, 14400}, /* Georgia Time */ ! {"gft", TZ, -10800}, /* French Guiana Time */ #if 0 ghst #endif ! {"gilt", TZ, 43200}, /* Gilbert Islands Time */ ! {"gmt", TZ, 0}, /* Greenwish Mean Time */ ! {"gst", TZ, 36000}, /* Guam Std Time, USSR Zone 9 */ ! {"gyt", TZ, -14400}, /* Guyana Time */ {"h", UNITS, DTK_HOUR}, /* "hour" */ #if 0 hadt hast #endif ! {"hdt", DTZ, -32400}, /* Hawaii/Alaska Daylight Time */ #if 0 hkst #endif ! {"hkt", TZ, 28800}, /* Hong Kong Time */ #if 0 ! {"hmt", TZ, 10800}, /* Hellas ? ? */ hovst hovt #endif ! {"hst", TZ, -36000}, /* Hawaii Std Time */ #if 0 hwt #endif ! {"ict", TZ, 25200}, /* Indochina Time */ ! {"idle", TZ, 43200}, /* Intl. Date Line, East */ ! {"idlw", TZ, -43200}, /* Intl. Date Line, West */ #if 0 idt /* Israeli, Iran, Indian Daylight Time */ #endif {LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */ {INVALID, RESERV, DTK_INVALID}, /* "invalid" reserved for bad time */ ! {"iot", TZ, 18000}, /* Indian Chagos Time */ ! {"irkst", DTZ, 32400}, /* Irkutsk Summer Time */ ! {"irkt", TZ, 28800}, /* Irkutsk Time */ ! {"irt", TZ, 12600}, /* Iran Time */ {"isodow", RESERV, DTK_ISODOW}, /* ISO day of week, Sunday == 7 */ #if 0 isst #endif ! {"ist", TZ, 7200}, /* Israel */ ! {"it", TZ, 12600}, /* Iran Time */ {"j", UNITS, DTK_JULIAN}, {"jan", MONTH, 1}, {"january", MONTH, 1}, ! {"javt", TZ, 25200}, /* Java Time (07:00? see JT) */ ! {"jayt", TZ, 32400}, /* Jayapura Time (Indonesia) */ {"jd", UNITS, DTK_JULIAN}, ! {"jst", TZ, 32400}, /* Japan Std Time,USSR Zone 8 */ ! {"jt", TZ, 27000}, /* Java Time (07:30? see JAVT) */ {"jul", MONTH, 7}, {"julian", UNITS, DTK_JULIAN}, {"july", MONTH, 7}, {"jun", MONTH, 6}, {"june", MONTH, 6}, ! {"kdt", DTZ, 36000}, /* Korea Daylight Time */ ! {"kgst", DTZ, 21600}, /* Kyrgyzstan Summer Time */ ! {"kgt", TZ, 18000}, /* Kyrgyzstan Time */ ! {"kost", TZ, 43200}, /* Kosrae Time */ ! {"krast", DTZ, 25200}, /* Krasnoyarsk Summer Time */ ! {"krat", TZ, 28800}, /* Krasnoyarsk Standard Time */ ! {"kst", TZ, 32400}, /* Korea Standard Time */ ! {"lhdt", DTZ, 39600}, /* Lord Howe Daylight Time, Australia */ ! {"lhst", TZ, 37800}, /* Lord Howe Standard Time, Australia */ ! {"ligt", TZ, 36000}, /* From Melbourne, Australia */ ! {"lint", TZ, 50400}, /* Line Islands Time (Kiribati; +14 hours!) */ ! {"lkt", TZ, 21600}, /* Lanka Time */ {"m", UNITS, DTK_MONTH}, /* "month" for ISO input */ ! {"magst", DTZ, 43200}, /* Magadan Summer Time */ ! {"magt", TZ, 39600}, /* Magadan Time */ {"mar", MONTH, 3}, {"march", MONTH, 3}, ! {"mart", TZ, -34200}, /* Marquesas Time */ ! {"mawt", TZ, 21600}, /* Mawson, Antarctica */ {"may", MONTH, 5}, ! {"mdt", DTZ, -21600}, /* Mountain Daylight Time */ ! {"mest", DTZ, 7200}, /* Middle Europe Summer Time */ ! {"met", TZ, 3600}, /* Middle Europe Time */ ! {"metdst", DTZ, 7200}, /* Middle Europe Daylight Time */ ! {"mewt", TZ, 3600}, /* Middle Europe Winter Time */ ! {"mez", TZ, 3600}, /* Middle Europe Zone */ ! {"mht", TZ, 43200}, /* Kwajalein */ {"mm", UNITS, DTK_MINUTE}, /* "minute" for ISO input */ ! {"mmt", TZ, 23400}, /* Myannar Time */ {"mon", DOW, 1}, {"monday", DOW, 1}, #if 0 most #endif ! {"mpt", TZ, 36000}, /* North Mariana Islands Time */ ! {"msd", DTZ, 14400}, /* Moscow Summer Time */ ! {"msk", TZ, 10800}, /* Moscow Time */ ! {"mst", TZ, -25200}, /* Mountain Standard Time */ ! {"mt", TZ, 30600}, /* Moluccas Time */ ! {"mut", TZ, 14400}, /* Mauritius Island Time */ ! {"mvt", TZ, 18000}, /* Maldives Island Time */ ! {"myt", TZ, 28800}, /* Malaysia Time */ #if 0 ncst #endif ! {"nct", TZ, 39600}, /* New Caledonia Time */ ! {"ndt", DTZ, -9000}, /* Nfld. Daylight Time */ ! {"nft", TZ, -12600}, /* Newfoundland Standard Time */ ! {"nor", TZ, 3600}, /* Norway Standard Time */ {"nov", MONTH, 11}, {"november", MONTH, 11}, ! {"novst", DTZ, 25200}, /* Novosibirsk Summer Time */ ! {"novt", TZ, 21600}, /* Novosibirsk Standard Time */ {NOW, RESERV, DTK_NOW}, /* current transaction time */ ! {"npt", TZ, 20700}, /* Nepal Standard Time (GMT-5:45) */ ! {"nst", TZ, -12600}, /* Nfld. Standard Time */ ! {"nt", TZ, -39600}, /* Nome Time */ ! {"nut", TZ, -39600}, /* Niue Time */ ! {"nzdt", DTZ, 46800}, /* New Zealand Daylight Time */ ! {"nzst", TZ, 43200}, /* New Zealand Standard Time */ ! {"nzt", TZ, 43200}, /* New Zealand Time */ {"oct", MONTH, 10}, {"october", MONTH, 10}, ! {"omsst", DTZ, 25200}, /* Omsk Summer Time */ ! {"omst", TZ, 21600}, /* Omsk Time */ {"on", IGNORE_DTF, 0}, /* "on" (throwaway) */ ! {"pdt", DTZ, -25200}, /* Pacific Daylight Time */ #if 0 pest #endif ! {"pet", TZ, -18000}, /* Peru Time */ ! {"petst", DTZ, 46800}, /* Petropavlovsk-Kamchatski Summer Time */ ! {"pett", TZ, 43200}, /* Petropavlovsk-Kamchatski Time */ ! {"pgt", TZ, 36000}, /* Papua New Guinea Time */ ! {"phot", TZ, 46800}, /* Phoenix Islands (Kiribati) Time */ #if 0 phst #endif ! {"pht", TZ, 28800}, /* Philippine Time */ ! {"pkt", TZ, 18000}, /* Pakistan Time */ {"pm", AMPM, PM}, ! {"pmdt", DTZ, -7200}, /* Pierre & Miquelon Daylight Time */ #if 0 pmst #endif ! {"pont", TZ, 39600}, /* Ponape Time (Micronesia) */ ! {"pst", TZ, -28800}, /* Pacific Standard Time */ ! {"pwt", TZ, 32400}, /* Palau Time */ ! {"pyst", DTZ, -10800}, /* Paraguay Summer Time */ ! {"pyt", TZ, -14400}, /* Paraguay Time */ ! {"ret", DTZ, 14400}, /* Reunion Island Time */ {"s", UNITS, DTK_SECOND}, /* "seconds" for ISO input */ ! {"sadt", DTZ, 37800}, /* S. Australian Dayl. Time */ #if 0 samst samt #endif ! {"sast", TZ, 34200}, /* South Australian Std Time */ {"sat", DOW, 6}, {"saturday", DOW, 6}, #if 0 sbt #endif ! {"sct", DTZ, 14400}, /* Mahe Island Time */ {"sep", MONTH, 9}, {"sept", MONTH, 9}, {"september", MONTH, 9}, ! {"set", TZ, -3600}, /* Seychelles Time ?? */ #if 0 sgt #endif ! {"sst", DTZ, 7200}, /* Swedish Summer Time */ {"sun", DOW, 0}, {"sunday", DOW, 0}, ! {"swt", TZ, 3600}, /* Swedish Winter Time */ #if 0 syot #endif {"t", ISOTIME, DTK_TIME}, /* Filler for ISO time fields */ ! {"tft", TZ, 18000}, /* Kerguelen Time */ ! {"that", TZ, -36000}, /* Tahiti Time */ {"thu", DOW, 4}, {"thur", DOW, 4}, {"thurs", DOW, 4}, {"thursday", DOW, 4}, ! {"tjt", TZ, 18000}, /* Tajikistan Time */ ! {"tkt", TZ, -36000}, /* Tokelau Time */ ! {"tmt", TZ, 18000}, /* Turkmenistan Time */ {TODAY, RESERV, DTK_TODAY}, /* midnight */ {TOMORROW, RESERV, DTK_TOMORROW}, /* tomorrow midnight */ #if 0 tost #endif ! {"tot", TZ, 46800}, /* Tonga Time */ #if 0 tpt #endif ! {"truk", TZ, 36000}, /* Truk Time */ {"tue", DOW, 2}, {"tues", DOW, 2}, {"tuesday", DOW, 2}, ! {"tvt", TZ, 43200}, /* Tuvalu Time */ #if 0 uct #endif ! {"ulast", DTZ, 32400}, /* Ulan Bator Summer Time */ ! {"ulat", TZ, 28800}, /* Ulan Bator Time */ {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */ ! {"ut", TZ, 0}, ! {"utc", TZ, 0}, ! {"uyst", DTZ, -7200}, /* Uruguay Summer Time */ ! {"uyt", TZ, -10800}, /* Uruguay Time */ ! {"uzst", DTZ, 21600}, /* Uzbekistan Summer Time */ ! {"uzt", TZ, 18000}, /* Uzbekistan Time */ ! {"vet", TZ, -14400}, /* Venezuela Time */ ! {"vlast", DTZ, 39600}, /* Vladivostok Summer Time */ ! {"vlat", TZ, 36000}, /* Vladivostok Time */ #if 0 vust #endif ! {"vut", TZ, 39600}, /* Vanuata Time */ ! {"wadt", DTZ, 28800}, /* West Australian DST */ ! {"wakt", TZ, 43200}, /* Wake Time */ #if 0 warst #endif ! {"wast", TZ, 25200}, /* West Australian Std Time */ ! {"wat", TZ, -3600}, /* West Africa Time */ ! {"wdt", DTZ, 32400}, /* West Australian DST */ {"wed", DOW, 3}, {"wednesday", DOW, 3}, {"weds", DOW, 3}, ! {"west", DTZ, 3600}, /* Western Europe Summer Time */ ! {"wet", TZ, 0}, /* Western Europe */ ! {"wetdst", DTZ, 3600}, /* Western Europe Daylight Savings Time */ ! {"wft", TZ, 43200}, /* Wallis and Futuna Time */ ! {"wgst", DTZ, -7200}, /* West Greenland Summer Time */ ! {"wgt", TZ, -10800}, /* West Greenland Time */ ! {"wst", TZ, 28800}, /* West Australian Standard Time */ {"y", UNITS, DTK_YEAR}, /* "year" for ISO input */ ! {"yakst", DTZ, 36000}, /* Yakutsk Summer Time */ ! {"yakt", TZ, 32400}, /* Yakutsk Time */ ! {"yapt", TZ, 36000}, /* Yap Time (Micronesia) */ ! {"ydt", DTZ, -28800}, /* Yukon Daylight Time */ ! {"yekst", DTZ, 21600}, /* Yekaterinburg Summer Time */ ! {"yekt", TZ, 18000}, /* Yekaterinburg Time */ {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */ ! {"yst", TZ, -32400}, /* Yukon Standard Time */ ! {"z", TZ, 0}, /* time zone tag per ISO-8601 */ ! {"zp4", TZ, -14400}, /* UTC +4 hours. */ ! {"zp5", TZ, -18000}, /* UTC +5 hours. */ ! {"zp6", TZ, -21600}, /* UTC +6 hours. */ ! {ZULU, TZ, 0}, /* UTC */ }; static datetkn deltatktbl[] = { *************** datebsearch(char *key, datetkn *base, un *** 521,529 **** while (last >= base) { position = base + ((last - base) >> 1); ! result = key[0] - position->token[0]; if (result == 0) { result = strncmp(key, position->token, TOKMAXLEN); if (result == 0) return position; --- 514,524 ---- while (last >= base) { position = base + ((last - base) >> 1); ! /* precheck the first character for a bit of extra speed */ ! result = (int) key[0] - (int) position->token[0]; if (result == 0) { + /* use strncmp so that we match truncated tokens */ result = strncmp(key, position->token, TOKMAXLEN); if (result == 0) return position; *************** DecodeUnits(int field, char *lowtoken, i *** 547,552 **** --- 542,548 ---- int type; datetkn *tp; + /* use strncmp so that we match truncated tokens */ if (deltacache[field] != NULL && strncmp(lowtoken, deltacache[field]->token, TOKMAXLEN) == 0) tp = deltacache[field]; *************** DecodeUnits(int field, char *lowtoken, i *** 561,570 **** else { type = tp->type; ! if (type == TZ || type == DTZ) ! *val = FROMVAL(tp); ! else ! *val = tp->value; } return type; --- 557,563 ---- else { type = tp->type; ! *val = tp->value; } return type; *************** DecodeSpecial(int field, char *lowtoken, *** 650,655 **** --- 643,649 ---- int type; datetkn *tp; + /* use strncmp so that we match truncated tokens */ if (datecache[field] != NULL && strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0) tp = datecache[field]; *************** DecodeSpecial(int field, char *lowtoken, *** 668,685 **** else { type = tp->type; ! switch (type) ! { ! case TZ: ! case DTZ: ! case DTZMOD: ! *val = FROMVAL(tp); ! break; ! ! default: ! *val = tp->value; ! break; ! } } return type; --- 662,668 ---- else { type = tp->type; ! *val = tp->value; } return type; *************** DecodePosixTimezone(char *str, int *tzp) *** 1656,1662 **** { case DTZ: case TZ: ! *tzp = (val * MINS_PER_HOUR) - tz; break; default: --- 1639,1645 ---- { case DTZ: case TZ: ! *tzp = -(val + tz); break; default: *************** DecodeDateTime(char **field, int *ftype, *** 2308,2314 **** tm->tm_isdst = 1; if (tzp == NULL) return -1; ! *tzp += val * MINS_PER_HOUR; break; case DTZ: --- 2291,2297 ---- tm->tm_isdst = 1; if (tzp == NULL) return -1; ! *tzp -= val; break; case DTZ: *************** DecodeDateTime(char **field, int *ftype, *** 2321,2327 **** tm->tm_isdst = 1; if (tzp == NULL) return -1; ! *tzp = val * MINS_PER_HOUR; ftype[i] = DTK_TZ; break; --- 2304,2310 ---- tm->tm_isdst = 1; if (tzp == NULL) return -1; ! *tzp = -val; ftype[i] = DTK_TZ; break; *************** DecodeDateTime(char **field, int *ftype, *** 2329,2335 **** tm->tm_isdst = 0; if (tzp == NULL) return -1; ! *tzp = val * MINS_PER_HOUR; ftype[i] = DTK_TZ; break; --- 2312,2318 ---- tm->tm_isdst = 0; if (tzp == NULL) return -1; ! *tzp = -val; ftype[i] = DTK_TZ; break; *************** PGTYPEStimestamp_defmt_scan(char **str, *** 3000,3024 **** pfmt++; scan_type = PGTYPES_TYPE_STRING_MALLOCED; err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt); ! ! /* ! * XXX use DecodeSpecial instead ? - it's declared static but ! * the arrays as well. :-( ! */ ! for (j = 0; !err && j < szdatetktbl; j++) { ! if (pg_strcasecmp(datetktbl[j].token, scan_val.str_val) == 0) { ! /* ! * tz calculates the offset for the seconds, the ! * timezone value of the datetktbl table is in quarter ! * hours ! */ ! *tz = -15 * MINS_PER_HOUR * datetktbl[j].value; ! break; } } - free(scan_val.str_val); break; case '+': /* XXX */ --- 2983,3008 ---- pfmt++; scan_type = PGTYPES_TYPE_STRING_MALLOCED; err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt); ! if (!err) { ! /* ! * XXX use DecodeSpecial instead? Do we need strcasecmp ! * here? ! */ ! err = 1; ! for (j = 0; j < szdatetktbl; j++) { ! if ((datetktbl[j].type == TZ || datetktbl[j].type == DTZ) && ! pg_strcasecmp(datetktbl[j].token, ! scan_val.str_val) == 0) ! { ! *tz = -datetktbl[j].value; ! err = 0; ! break; ! } } + free(scan_val.str_val); } break; case '+': /* XXX */ diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index f95617b..552f3d1 100644 *** a/src/test/regress/expected/timestamptz.out --- b/src/test/regress/expected/timestamptz.out *************** SELECT make_timestamptz(2014, 12, 10, 10 *** 1805,1807 **** --- 1805,2487 ---- (1 row) RESET TimeZone; + -- + -- Test behavior with a dynamic (time-varying) timezone abbreviation. + -- These tests rely on the knowledge that MSK (Europe/Moscow standard time) + -- changed meaning in Mar 2011 and back again in Oct 2014. + -- + SET TimeZone to 'UTC'; + SELECT '2011-03-27 00:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 21:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:59:59 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:01 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 02:59:59 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:01 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 04:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 00:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 00:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 21:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:59:59 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:01 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 02:59:59 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 22:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:01 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Mar 26 23:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 04:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 00:00:00 2011 UTC + (1 row) + + SELECT '2014-10-26 00:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 20:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 00:59:59 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 20:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 22:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:01 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 22:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 01:59:59 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 22:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 23:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:01 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 23:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 03:00:00 Europe/Moscow'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 00:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 00:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 20:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 00:59:59 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 20:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 22:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:01 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 22:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 01:59:59 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 22:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 23:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:01 MSK'::timestamptz; + timestamptz + ------------------------------ + Sat Oct 25 23:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 03:00:00 MSK'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 00:00:00 2014 UTC + (1 row) + + SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 21:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 22:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 22:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 23:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 23:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 23:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 23:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Mar 26 23:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sun Mar 27 00:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 21:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 22:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 22:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 22:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 22:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 22:59:59 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 23:00:00 2011 UTC + (1 row) + + SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Mar 26 23:00:01 2011 UTC + (1 row) + + SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sun Mar 27 00:00:00 2011 UTC + (1 row) + + SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 20:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 20:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 22:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 22:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 22:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 23:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sat Oct 25 23:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + timezone + ------------------------------ + Sun Oct 26 00:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 20:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 20:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 22:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 22:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 22:59:59 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 23:00:00 2014 UTC + (1 row) + + SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sat Oct 25 23:00:01 2014 UTC + (1 row) + + SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'MSK'; + timezone + ------------------------------ + Sun Oct 26 00:00:00 2014 UTC + (1 row) + + SELECT make_timestamptz(2014, 10, 26, 0, 0, 0, 'MSK'); + make_timestamptz + ------------------------------ + Sat Oct 25 20:00:00 2014 UTC + (1 row) + + SELECT make_timestamptz(2014, 10, 26, 3, 0, 0, 'MSK'); + make_timestamptz + ------------------------------ + Sun Oct 26 00:00:00 2014 UTC + (1 row) + + SET TimeZone to 'Europe/Moscow'; + SELECT '2011-03-26 21:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 00:00:00 2011 MSK + (1 row) + + SELECT '2011-03-26 22:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 01:00:00 2011 MSK + (1 row) + + SELECT '2011-03-26 22:59:59 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 01:59:59 2011 MSK + (1 row) + + SELECT '2011-03-26 23:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 03:00:00 2011 MSK + (1 row) + + SELECT '2011-03-26 23:00:01 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 03:00:01 2011 MSK + (1 row) + + SELECT '2011-03-26 23:59:59 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 03:59:59 2011 MSK + (1 row) + + SELECT '2011-03-27 00:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Mar 27 04:00:00 2011 MSK + (1 row) + + SELECT '2014-10-25 20:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 00:00:00 2014 MSK + (1 row) + + SELECT '2014-10-25 21:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 01:00:00 2014 MSK + (1 row) + + SELECT '2014-10-25 21:59:59 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 01:59:59 2014 MSK + (1 row) + + SELECT '2014-10-25 22:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 01:00:00 2014 MSK + (1 row) + + SELECT '2014-10-25 22:00:01 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 01:00:01 2014 MSK + (1 row) + + SELECT '2014-10-25 22:59:59 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 01:59:59 2014 MSK + (1 row) + + SELECT '2014-10-25 23:00:00 UTC'::timestamptz; + timestamptz + ------------------------------ + Sun Oct 26 02:00:00 2014 MSK + (1 row) + + RESET TimeZone; + SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 00:00:00 2011 + (1 row) + + SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 01:00:00 2011 + (1 row) + + SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 01:59:59 2011 + (1 row) + + SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 03:00:00 2011 + (1 row) + + SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 03:00:01 2011 + (1 row) + + SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 03:59:59 2011 + (1 row) + + SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Mar 27 04:00:00 2011 + (1 row) + + SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 00:00:00 2014 + (1 row) + + SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 01:00:00 2014 + (1 row) + + SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 01:59:59 2014 + (1 row) + + SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 01:00:00 2014 + (1 row) + + SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 01:00:01 2014 + (1 row) + + SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 01:59:59 2014 + (1 row) + + SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + timezone + -------------------------- + Sun Oct 26 02:00:00 2014 + (1 row) + + SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 00:00:00 2011 + (1 row) + + SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 01:00:00 2011 + (1 row) + + SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 01:59:59 2011 + (1 row) + + SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 03:00:00 2011 + (1 row) + + SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 03:00:01 2011 + (1 row) + + SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 03:59:59 2011 + (1 row) + + SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Mar 27 04:00:00 2011 + (1 row) + + SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 00:00:00 2014 + (1 row) + + SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 01:00:00 2014 + (1 row) + + SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 01:59:59 2014 + (1 row) + + SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 01:00:00 2014 + (1 row) + + SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 01:00:01 2014 + (1 row) + + SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 01:59:59 2014 + (1 row) + + SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + timezone + -------------------------- + Sun Oct 26 02:00:00 2014 + (1 row) + diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql index ec001f8..92b5bbc 100644 *** a/src/test/regress/sql/timestamptz.sql --- b/src/test/regress/sql/timestamptz.sql *************** SELECT make_timestamptz(2008, 12, 10, 10 *** 290,292 **** --- 290,431 ---- SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, 'PST8PDT'); RESET TimeZone; + + -- + -- Test behavior with a dynamic (time-varying) timezone abbreviation. + -- These tests rely on the knowledge that MSK (Europe/Moscow standard time) + -- changed meaning in Mar 2011 and back again in Oct 2014. + -- + + SET TimeZone to 'UTC'; + + SELECT '2011-03-27 00:00:00 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 01:00:00 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 01:59:59 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 02:00:00 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 02:00:01 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 02:59:59 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 03:00:00 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 03:00:01 Europe/Moscow'::timestamptz; + SELECT '2011-03-27 04:00:00 Europe/Moscow'::timestamptz; + + SELECT '2011-03-27 00:00:00 MSK'::timestamptz; + SELECT '2011-03-27 01:00:00 MSK'::timestamptz; + SELECT '2011-03-27 01:59:59 MSK'::timestamptz; + SELECT '2011-03-27 02:00:00 MSK'::timestamptz; + SELECT '2011-03-27 02:00:01 MSK'::timestamptz; + SELECT '2011-03-27 02:59:59 MSK'::timestamptz; + SELECT '2011-03-27 03:00:00 MSK'::timestamptz; + SELECT '2011-03-27 03:00:01 MSK'::timestamptz; + SELECT '2011-03-27 04:00:00 MSK'::timestamptz; + + SELECT '2014-10-26 00:00:00 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 00:59:59 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 01:00:00 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 01:00:01 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 01:59:59 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 02:00:00 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 02:00:01 Europe/Moscow'::timestamptz; + SELECT '2014-10-26 03:00:00 Europe/Moscow'::timestamptz; + + SELECT '2014-10-26 00:00:00 MSK'::timestamptz; + SELECT '2014-10-26 00:59:59 MSK'::timestamptz; + SELECT '2014-10-26 01:00:00 MSK'::timestamptz; + SELECT '2014-10-26 01:00:01 MSK'::timestamptz; + SELECT '2014-10-26 01:59:59 MSK'::timestamptz; + SELECT '2014-10-26 02:00:00 MSK'::timestamptz; + SELECT '2014-10-26 02:00:01 MSK'::timestamptz; + SELECT '2014-10-26 03:00:00 MSK'::timestamptz; + + SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + + SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'MSK'; + SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'MSK'; + + SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow'; + + SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'MSK'; + SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'MSK'; + + SELECT make_timestamptz(2014, 10, 26, 0, 0, 0, 'MSK'); + SELECT make_timestamptz(2014, 10, 26, 3, 0, 0, 'MSK'); + + SET TimeZone to 'Europe/Moscow'; + + SELECT '2011-03-26 21:00:00 UTC'::timestamptz; + SELECT '2011-03-26 22:00:00 UTC'::timestamptz; + SELECT '2011-03-26 22:59:59 UTC'::timestamptz; + SELECT '2011-03-26 23:00:00 UTC'::timestamptz; + SELECT '2011-03-26 23:00:01 UTC'::timestamptz; + SELECT '2011-03-26 23:59:59 UTC'::timestamptz; + SELECT '2011-03-27 00:00:00 UTC'::timestamptz; + + SELECT '2014-10-25 20:00:00 UTC'::timestamptz; + SELECT '2014-10-25 21:00:00 UTC'::timestamptz; + SELECT '2014-10-25 21:59:59 UTC'::timestamptz; + SELECT '2014-10-25 22:00:00 UTC'::timestamptz; + SELECT '2014-10-25 22:00:01 UTC'::timestamptz; + SELECT '2014-10-25 22:59:59 UTC'::timestamptz; + SELECT '2014-10-25 23:00:00 UTC'::timestamptz; + + RESET TimeZone; + + SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + + SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow'; + + SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + + SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK'; + SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK'; diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c index 85b227c..80bb856 100644 *** a/src/timezone/localtime.c --- b/src/timezone/localtime.c *************** increment_overflow(int *number, int delt *** 1292,1300 **** } /* ! * Find the next DST transition time after the given time * ! * *timep is the input value, the other parameters are output values. * * When the function result is 1, *boundary is set to the time_t * representation of the next DST transition time after *timep, --- 1292,1300 ---- } /* ! * Find the next DST transition time in the given zone after the given time * ! * *timep and *tz are input arguments, the other parameters are output values. * * When the function result is 1, *boundary is set to the time_t * representation of the next DST transition time after *timep, *************** pg_next_dst_boundary(const pg_time_t *ti *** 1445,1450 **** --- 1445,1518 ---- } /* + * Identify a timezone abbreviation's meaning in the given zone + * + * Determine the GMT offset and DST flag associated with the abbreviation. + * This is generally used only when the abbreviation has actually changed + * meaning over time; therefore, we also take a UTC cutoff time, and return + * the meaning in use at or most recently before that time, or the meaning + * in first use after that time if the abbrev was never used before that. + * + * On success, returns TRUE and sets *gmtoff and *isdst. If the abbreviation + * was never used at all in this zone, returns FALSE. + * + * Note: abbrev is matched case-sensitively; it should be all-upper-case. + */ + bool + pg_interpret_timezone_abbrev(const char *abbrev, + const pg_time_t *timep, + long int *gmtoff, + int *isdst, + const pg_tz *tz) + { + const struct state *sp; + const struct ttinfo *found_ttisp; + const struct ttinfo *ttisp; + int abbrind; + int i; + const pg_time_t t = *timep; + + sp = &tz->state; + + /* + * Locate the abbreviation in the zone's abbreviation list. We assume + * there are not duplicates in the list. + */ + abbrind = 0; + while (abbrind < sp->charcnt) + { + if (strcmp(abbrev, sp->chars + abbrind) == 0) + break; + abbrind += strlen(sp->chars + abbrind) + 1; + } + if (abbrind >= sp->charcnt) + return FALSE; /* not there! */ + + /* + * Iterate through the zone's transition times to find the latest interval + * using the given abbrev before the cutoff time, or the first one after. + * We could make this a bit faster, but a great deal more complex, by + * doing a binary search first; not clear it's worth it. + */ + found_ttisp = NULL; + for (i = 0; i < sp->timecnt; i++) + { + if (sp->ats[i] > t && found_ttisp != NULL) + break; + ttisp = &sp->ttis[sp->types[i]]; + if (ttisp->tt_abbrind == abbrind) + found_ttisp = ttisp; + } + + if (found_ttisp == NULL) + return FALSE; /* hm, not actually used in any interval? */ + + *gmtoff = found_ttisp->tt_gmtoff; + *isdst = found_ttisp->tt_isdst; + return TRUE; + } + + /* * If the given timezone uses only one GMT offset, store that offset * into *gmtoff and return TRUE, else return FALSE. */ diff --git a/src/timezone/tznames/Default b/src/timezone/tznames/Default index 9e5209e..6f2a0d7 100644 *** a/src/timezone/tznames/Default --- b/src/timezone/tznames/Default *************** MEST 7200 D # Middle Europe Summer *** 638,645 **** MET 3600 # Middle Europe Time (not in zic) METDST 7200 D # Middle Europe Summer Time (not in zic) MEZ 3600 # Mitteleuropaeische Zeit (German) (not in zic) ! MSD 14400 D # Moscow Daylight Time (obsolete) ! MSK 10800 # Moscow Time (caution: this used to mean 14400) # (Europe/Moscow) VOLT 14400 # Volgograd Time (obsolete) WET 0 # Western Europe Time --- 638,645 ---- MET 3600 # Middle Europe Time (not in zic) METDST 7200 D # Middle Europe Summer Time (not in zic) MEZ 3600 # Mitteleuropaeische Zeit (German) (not in zic) ! MSD Europe/Moscow # Moscow Daylight Time (obsolete) ! MSK Europe/Moscow # Moscow Time # (Europe/Moscow) VOLT 14400 # Volgograd Time (obsolete) WET 0 # Western Europe Time
pgsql-hackers by date: