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
