Thread: Proposal for better support of time-varying timezone abbreviations

Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
I got interested in the problem discussed in
http://www.postgresql.org/message-id/20714.1412456604@sss.pgh.pa.us
to wit:

> It's becoming clear to me that our existing design whereby zone
> abbreviations represent fixed GMT offsets isn't really good enough.
> I've been wondering whether we could change things so that, for instance,
> "EDT" means "daylight time according to America/New_York" and the system
> would consult the zic database to find out what the prevailing GMT offset
> was in that zone on that date.  This would be a lot more robust in the
> face of the kind of foolishness we now see actually goes on.

Here is a fairly detailed design sketch for a solution:

1. Allow tznames entries to consist of an abbreviation and the name of
a zic timezone, for example
MSK    Europe/Moscow

instead of the current scheme whereby an abbreviation is defined by a
daylight-savings flag and a numeric GMT offset.  When an abbreviation is
defined this way, the implied offset and DST flag are looked up
dynamically as described below.  (In my message quoted above, I'd imagined
that we'd write a DST flag and a zone name, but it turns out this does not
work because there are cases where the DST property has changed over time.
Yes, really.  So this design mandates that we derive the DST flag by
looking into the zic timezone data.)  Note that we'll still allow the old
style of entries, and indeed prefer that way for cases where an
abbreviation has never changed meaning, because:

* We need that anyway for forwards compatibility of existing custom
abbreviations files.

* It's a lot cheaper to interpret a fixed-meaning zone abbreviation using
the existing logic than to do it as I propose here, so we shouldn't spend
the extra cycles unless necessary.

* Converting every one of the existing abbreviation-file entries would be
really tedious, so I don't want to do it where not necessary.

Also note that this doesn't touch the aspect of the existing design
whereby there are multiple potential abbreviations files.  We still have
the problem that the same abbreviation can be in use in different
timezones, so we have to let users configure which zone they mean by a
given abbreviation.

2. To interpret such an abbreviation in the context of timestamptz input,
look up the referenced zic timezone, and use the meaning of the
abbreviation that prevailed at or most recently before the local time
indicated by the rest of the timestamptz string.  If the abbreviation was
never used before that time in the given zone, use its earliest later
interpretation; or if it was never used at all (ie bad configuration file)
throw error.  Note that this is different from what happens if you give
the underlying zone name directly.  It's always been the case that you
could say, for instance, "EST" to force interpretation of a datetime as
standard time even when DST is in force, or "EDT" to force the opposite
interpretation, and this definition preserves that behavior.

3. In the context of timetz input, we only have a time of day not a full
datetime to look at, so it's not entirely clear what to do.  We could
throw an error, but that would result in rejecting some inputs currently
considered valid.  Perhaps we don't really care, since we consider timetz
a deprecated type anyway.  If that doesn't seem OK, we could assume
today's date and the given time-of-day and look up the abbreviation's
meaning as described above.  This would mean that the meaning of, say,
'15:00 MSK'::timetz would change over time --- but that happens now,
whenever we change the contents of the abbreviations file entry for MSK,
so maybe this isn't as horrid as it sounds.

4. I've eyeballed the relevant code a bit, and it seems that the only
implementation aspect that isn't perfectly straightforward is figuring
out how to cram a zic timezone reference into a datetkn table entry.
I suggest that before tackling this feature proper, we bring struct
datetkn into the 21st century by widening it from 12 to 16 bytes, along
the lines of

typedef struct
{   char        token[TOKMAXLEN + 1];  /* now always null-terminated */   char        type;   int32       value;
} datetkn;

and getting rid of all of the very crufty code that deals with
non-null-terminated token strings and cramming values that don't really
fit into a char-sized field into "value".  (We might save more code bytes
that way than we spend on the wider token-table entries :-( ... and we'll
certainly make the code less ugly.)  Having done that, the "value" can be
large enough to be an index into additional storage appended to a
TimeZoneAbbrevTable.  I imagine it pointing at a struct like this:

struct DynamicTimeZoneAbbrev
{   const pg_tz *tz;      /* zic timezone, or NULL if not yet looked up */   char        name[1];  /* zone name
(variablelength string)
 
};

We'd resolve the timezone name into a pg_tz pointer only upon first use of
a dynamic abbreviation, since we don't want to force loading of every zone
referenced in the configuration file at startup; many sessions wouldn't
ever use them.

(I also considered just allowing struct datetkn to contain a pointer; but
adding a union would make initialization of constant datetkn arrays more
notationally painful, and perhaps impossible with older C compilers.)

5. It's worth debating whether we should back-patch such a change.
It certainly is a feature addition, and as such not something we'd
normally consider back-patching, but:

* Those time-varying zone abbreviations are out there whether we like
it or not.  As Bruce noted in the other thread, this is going to be
a pain point for a lot of people, particularly in Russia.

* Our maintenance processes for the timezone data files assume that we
can back-patch the same change into all active branches.  It'll be a lot
more tedious and error-prone if we can only use this feature in the most
recent branches.

So I'm inclined to propose not merely doing this, but back-patching
into all supported branches.  I can see that there might be consensus
against that though.

Thoughts, objections, better ideas?
        regards, tom lane



Re: Proposal for better support of time-varying timezone abbreviations

From
Gavin Flower
Date:
<div class="moz-cite-prefix">On 06/10/14 10:33, Tom Lane wrote:<br /></div><blockquote
cite="mid:27083.1412544800@sss.pgh.pa.us"type="cite"><pre wrap="">I got interested in the problem discussed in
 
<a class="moz-txt-link-freetext"
href="http://www.postgresql.org/message-id/20714.1412456604@sss.pgh.pa.us">http://www.postgresql.org/message-id/20714.1412456604@sss.pgh.pa.us</a>
to wit:

</pre><blockquote type="cite"><pre wrap="">It's becoming clear to me that our existing design whereby zone
abbreviations represent fixed GMT offsets isn't really good enough.
I've been wondering whether we could change things so that, for instance,
"EDT" means "daylight time according to America/New_York" and the system
would consult the zic database to find out what the prevailing GMT offset
was in that zone on that date.  This would be a lot more robust in the
face of the kind of foolishness we now see actually goes on.
</pre></blockquote><pre wrap="">
Here is a fairly detailed design sketch for a solution:

1. Allow tznames entries to consist of an abbreviation and the name of
a zic timezone, for example
MSK    Europe/Moscow

instead of the current scheme whereby an abbreviation is defined by a
daylight-savings flag and a numeric GMT offset.  When an abbreviation is
defined this way, the implied offset and DST flag are looked up
dynamically as described below.  (In my message quoted above, I'd imagined
that we'd write a DST flag and a zone name, but it turns out this does not
work because there are cases where the DST property has changed over time.
Yes, really.  So this design mandates that we derive the DST flag by
looking into the zic timezone data.)  Note that we'll still allow the old
style of entries, and indeed prefer that way for cases where an
abbreviation has never changed meaning, because:

* We need that anyway for forwards compatibility of existing custom
abbreviations files.

* It's a lot cheaper to interpret a fixed-meaning zone abbreviation using
the existing logic than to do it as I propose here, so we shouldn't spend
the extra cycles unless necessary.

* Converting every one of the existing abbreviation-file entries would be
really tedious, so I don't want to do it where not necessary.

Also note that this doesn't touch the aspect of the existing design
whereby there are multiple potential abbreviations files.  We still have
the problem that the same abbreviation can be in use in different
timezones, so we have to let users configure which zone they mean by a
given abbreviation.

2. To interpret such an abbreviation in the context of timestamptz input,
look up the referenced zic timezone, and use the meaning of the
abbreviation that prevailed at or most recently before the local time
indicated by the rest of the timestamptz string.  If the abbreviation was
never used before that time in the given zone, use its earliest later
interpretation; or if it was never used at all (ie bad configuration file)
throw error.  Note that this is different from what happens if you give
the underlying zone name directly.  It's always been the case that you
could say, for instance, "EST" to force interpretation of a datetime as
standard time even when DST is in force, or "EDT" to force the opposite
interpretation, and this definition preserves that behavior.

3. In the context of timetz input, we only have a time of day not a full
datetime to look at, so it's not entirely clear what to do.  We could
throw an error, but that would result in rejecting some inputs currently
considered valid.  Perhaps we don't really care, since we consider timetz
a deprecated type anyway.  If that doesn't seem OK, we could assume
today's date and the given time-of-day and look up the abbreviation's
meaning as described above.  This would mean that the meaning of, say,
'15:00 MSK'::timetz would change over time --- but that happens now,
whenever we change the contents of the abbreviations file entry for MSK,
so maybe this isn't as horrid as it sounds.

4. I've eyeballed the relevant code a bit, and it seems that the only
implementation aspect that isn't perfectly straightforward is figuring
out how to cram a zic timezone reference into a datetkn table entry.
I suggest that before tackling this feature proper, we bring struct
datetkn into the 21st century by widening it from 12 to 16 bytes, along
the lines of

typedef struct
{   char        token[TOKMAXLEN + 1];  /* now always null-terminated */   char        type;   int32       value;
} datetkn;

and getting rid of all of the very crufty code that deals with
non-null-terminated token strings and cramming values that don't really
fit into a char-sized field into "value".  (We might save more code bytes
that way than we spend on the wider token-table entries :-( ... and we'll
certainly make the code less ugly.)  Having done that, the "value" can be
large enough to be an index into additional storage appended to a
TimeZoneAbbrevTable.  I imagine it pointing at a struct like this:

struct DynamicTimeZoneAbbrev
{   const pg_tz *tz;      /* zic timezone, or NULL if not yet looked up */   char        name[1];  /* zone name
(variablelength string)
 
};

We'd resolve the timezone name into a pg_tz pointer only upon first use of
a dynamic abbreviation, since we don't want to force loading of every zone
referenced in the configuration file at startup; many sessions wouldn't
ever use them.

(I also considered just allowing struct datetkn to contain a pointer; but
adding a union would make initialization of constant datetkn arrays more
notationally painful, and perhaps impossible with older C compilers.)

5. It's worth debating whether we should back-patch such a change.
It certainly is a feature addition, and as such not something we'd
normally consider back-patching, but:

* Those time-varying zone abbreviations are out there whether we like
it or not.  As Bruce noted in the other thread, this is going to be
a pain point for a lot of people, particularly in Russia.

* Our maintenance processes for the timezone data files assume that we
can back-patch the same change into all active branches.  It'll be a lot
more tedious and error-prone if we can only use this feature in the most
recent branches.

So I'm inclined to propose not merely doing this, but back-patching
into all supported branches.  I can see that there might be consensus
against that though.

Thoughts, objections, better ideas?
        regards, tom lane


</pre></blockquote> What I am going to discuss may be way too complicated to implement (or impractical for other
reasons!),Ibut I feel that I should at least mention it - because it might (does?) address real problems (I've been
bittenby this kind of problem in the past).<br /><br /> In a totally different context relating to insurance quotes, I
deviseda scheme to use both an <i>effective_date</i> & an <i>as_at_date</i>.  How these concepts might be
implementedin pg, in this instance, is likely to be very different from what I did originally.<br /><br /> I have not
checked,but I suspect that pg probably already uses an <i>effective_date</i> to control when changes to daylight saving
date/time'scome into affect (such as a change in the date that the transition to daylight saving takes effect).  If
not,then maybe this should be considered.  This could also be used, if it was desired to use the appropriate
abbreviationand offset valid at date/time where it was different to that defined at the current date/time.<br /><br />
Theuse of an <i>as_at_date</i> is far more problematic.  The idea relates to how existing date/times should be treated
withrespect to the date/time that a pg database is updated with new time zone data files.   In the simplest form: there
wouldbe a function in pg that would return the date/time a new time zone data file was entered into the system, so that
applicationsoftware can manually correct when the stored GMT date/time was stored incorrectly because the wring GMT
offsetwas used due to the updated time zone data files not being in place.  Alternatively, pg could offer to do the
correctionin a one-off action at the time the new zone data files were updated.<br /><br /><br /> Cheers,<br />
Gavin<br/> 

Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
Gavin Flower <GavinFlower@archidevsys.co.nz> writes:
> The use of an /as_at_date/ is far more problematic.  The idea relates to 
> how existing date/times should be treated with respect to the date/time 
> that a pg database is updated with new time zone data files.   In the 
> simplest form: there would be a function in pg that would return the 
> date/time a new time zone data file was entered into the system, so that 
> application software can manually correct when the stored GMT date/time 
> was stored incorrectly because the wring GMT offset was used due to the 
> updated time zone data files not being in place.  Alternatively, pg 
> could offer to do the correction in a one-off action at the time the new 
> zone data files were updated.

Right now there's basically no way to do something like that, since what
we store for timestamptz is just a UTC time instant, with no record of
what GMT offset was involved much less exactly how the offset was
specified in the input.  We'd probably have to (at least) double the
on-disk size of timestamptz values to record that ... which seems like a
mighty high price to pay to fix a corner case.  Not to mention that
nobody's going to be willing to break on-disk compatibility of timestamptz
for this.

In any case, my proposal is just about being able to correctly interpret
historical timezone abbreviations during input, not about changing what
we store as datetime values.
        regards, tom lane



Re: Proposal for better support of time-varying timezone abbreviations

From
Jim Nasby
Date:
On 10/5/14, 5:42 PM, Tom Lane wrote:
> Gavin Flower <GavinFlower@archidevsys.co.nz> writes:
>> The use of an /as_at_date/ is far more problematic.  The idea relates to
>> how existing date/times should be treated with respect to the date/time
>> that a pg database is updated with new time zone data files.   In the
>> simplest form: there would be a function in pg that would return the
>> date/time a new time zone data file was entered into the system, so that
>> application software can manually correct when the stored GMT date/time
>> was stored incorrectly because the wring GMT offset was used due to the
>> updated time zone data files not being in place.  Alternatively, pg
>> could offer to do the correction in a one-off action at the time the new
>> zone data files were updated.
>
> Right now there's basically no way to do something like that, since what
> we store for timestamptz is just a UTC time instant, with no record of
> what GMT offset was involved much less exactly how the offset was
> specified in the input.  We'd probably have to (at least) double the
> on-disk size of timestamptz values to record that ... which seems like a
> mighty high price to pay to fix a corner case.  Not to mention that
> nobody's going to be willing to break on-disk compatibility of timestamptz
> for this.

FWIW, I agree for timestamptz, but I do wish we had a timestamp datatype that stored the exact timezone in effect when
thedata was entered. That can really, REALLY save your rear if you screw up either timezone in postgresql.conf, or the
server'stimezone. The part that seems hard (at least to me) is the question of how to actually store the timezone,
becauseI don't think storing the text string "America/Central" is going to cut it. :/
 
-- 
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com



Re: Proposal for better support of time-varying timezone abbreviations

From
Jim Nasby
Date:
On 10/6/14, 6:19 PM, Jim Nasby wrote:
> FWIW, I agree for timestamptz, but I do wish we had a timestamp datatype that stored the exact timezone in effect
whenthe data was entered. That can really, REALLY save your rear if you screw up either timezone in postgresql.conf, or
theserver's timezone. The part that seems hard (at least to me) is the question of how to actually store the timezone,
becauseI don't think storing the text string "America/Central" is going to cut it. :/
 

For the archives... there's an extension that does what I'd been talking about: http://pgxn.org/dist/timestampandtz/.
-- 
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com



Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
I wrote:
> 4. I've eyeballed the relevant code a bit, and it seems that the only
> implementation aspect that isn't perfectly straightforward is figuring
> out how to cram a zic timezone reference into a datetkn table entry.
> I suggest that before tackling this feature proper, we bring struct
> datetkn into the 21st century by widening it from 12 to 16 bytes, along
> the lines of

> typedef struct
> {
>     char        token[TOKMAXLEN + 1];  /* now always null-terminated */
>     char        type;
>     int32       value;
> } datetkn;

> and getting rid of all of the very crufty code that deals with
> non-null-terminated token strings and cramming values that don't really
> fit into a char-sized field into "value".  (We might save more code bytes
> that way than we spend on the wider token-table entries :-( ... and we'll
> certainly make the code less ugly.)

Attached is a proposed patch for this part.  It turned out to be quite
a bit more exciting (not in a good way) than I'd expected.  Somewhere
along the way, somebody decided that the easiest way to get from point A
to point B was to insert a minus sign into the FROMVAL() macro, meaning
that FROMVAL() and TOVAL() were not inverses as one would rationally
expect.  There were various ways that one could deal with this bit of
dirty laundry; in particular I considered flipping the sign definition
of TZ/DTZ entry values.  It seemed better in the end to keep the sign
the same (matching the IANA code's convention for timezone offset sign)
and negate the results at the call sites where needed.  Since, in another
bit of weird design, all those call sites already had a multiplication
by MINS_PER_HOUR (the wrong spelling of "60", btw, but I digress) that
needed to be got rid of, this didn't result in touching more places
than I would have had to anyway.

Much worse for the present effort: I was reminded that ecpg contains
a *hard wired* list of known zone abbreviations and their GMT offsets;
a list that doesn't seem to have been updated since around 2003.  I'm
not sure what a reasonable fix would be for that.  ecpg can't assume it
has access to the timezone database, probably, so bringing it up to
speed with what I propose to do in the backend doesn't seem feasible.
For the moment I didn't change the data there, even though a lot of
the entries are obsolete.

The attached patch also fixes a possible free()-of-uninitialized-pointer
problem in PGTYPEStimestamp_defmt_scan().

            regards, tom lane

diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 073104d..195ac6e 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** timetz_zone(PG_FUNCTION_ARGS)
*** 2710,2718 ****
      type = DecodeSpecial(0, lowzone, &val);

      if (type == TZ || type == DTZ)
!         tz = val * MINS_PER_HOUR;
      else
      {
          tzp = pg_tzset(tzname);
          if (tzp)
          {
--- 2710,2722 ----
      type = DecodeSpecial(0, lowzone, &val);

      if (type == TZ || type == DTZ)
!     {
!         /* abbreviation */
!         tz = -val;
!     }
      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..b159a08 100644
*** a/src/backend/utils/adt/datetime.c
--- b/src/backend/utils/adt/datetime.c
*************** const char *const days[] = {"Sunday", "M
*** 70,101 ****
   *****************************************************************************/

  /*
-  * 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
--- 70,82 ----
   *****************************************************************************/

  /*
   * 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 or DTZ entries, rather those are loaded
   * from configuration files and stored in timezonetktbl, which has the same
*************** 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},
--- 104,110 ----
      {"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 ****
--- 166,175 ----

  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 */
*************** 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:
--- 1271,1277 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp -= val;
                          break;

                      case DTZ:
*************** DecodeDateTime(char **field, int *ftype,
*** 1299,1312 ****
                          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:
--- 1284,1297 ----
                          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 IGNORE_DTF:
*************** 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:
--- 1963,1969 ----
                          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;

--- 1976,1982 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = -val;
                          ftype[i] = DTK_TZ;
                          break;

*************** DecodeTimeOnly(char **field, int *ftype,
*** 1999,2005 ****
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = val * MINS_PER_HOUR;
                          ftype[i] = DTK_TZ;
                          break;

--- 1984,1990 ----
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = -val;
                          ftype[i] = DTK_TZ;
                          break;

*************** DecodeSpecial(int field, char *lowtoken,
*** 2792,2797 ****
--- 2777,2783 ----
      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, timezonetktbl, sztimezonetktbl);
*************** 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;
--- 2793,2799 ----
      {
          datecache[field] = tp;
          type = tp->type;
!         *val = tp->value;
      }

      return type;
*************** DecodeUnits(int field, char *lowtoken, i
*** 3504,3509 ****
--- 3479,3485 ----
      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;
--- 3493,3499 ----
      {
          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;
--- 3566,3576 ----
          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;
          }
      }
--- 4117,4142 ----
      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;
          }
      }
*************** ConvertTimeZoneAbbrevs(TimeZoneAbbrevTab
*** 4221,4230 ****
      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 */
--- 4207,4216 ----
      tbl->numabbrevs = n;
      for (i = 0; i < n; i++)
      {
!         /* use strlcpy to truncate name if necessary */
!         strlcpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN + 1);
          newtbl[i].type = abbrevs[i].is_dst ? DTZ : TZ;
!         newtbl[i].value = abbrevs[i].offset;
      }

      /* Check the ordering, if testing */
*************** pg_timezone_abbrevs(PG_FUNCTION_ARGS)
*** 4315,4329 ****
       * 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);
--- 4301,4315 ----
       * Convert name to text, using upcasing conversion that is the inverse of
       * what ParseDateTime() uses.
       */
!     strlcpy(buffer, timezonetktbl[*pindex].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 = timezonetktbl[*pindex].value;
      resInterval = (Interval *) palloc(sizeof(Interval));
      tm2interval(&tm, 0, resInterval);
      values[1] = IntervalPGetDatum(resInterval);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 11007c6..de1e1fc 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** parse_sane_timezone(struct pg_tm * tm, t
*** 540,548 ****
          type = DecodeSpecial(0, lowzone, &val);

          if (type == TZ || type == DTZ)
!             tz = val * MINS_PER_HOUR;
          else
          {
              pg_tz       *tzp;

              tzp = pg_tzset(tzname);
--- 540,552 ----
          type = DecodeSpecial(0, lowzone, &val);

          if (type == TZ || type == DTZ)
!         {
!             /* abbreviation */
!             tz = -val;
!         }
          else
          {
+             /* try it as a full zone name */
              pg_tz       *tzp;

              tzp = pg_tzset(tzname);
*************** timestamp_zone(PG_FUNCTION_ARGS)
*** 4904,4914 ****

      if (type == TZ || type == DTZ)
      {
!         tz = -(val * MINS_PER_HOUR);
          result = dt2local(timestamp, tz);
      }
      else
      {
          tzp = pg_tzset(tzname);
          if (tzp)
          {
--- 4908,4920 ----

      if (type == TZ || type == DTZ)
      {
!         /* abbreviation */
!         tz = val;
          result = dt2local(timestamp, tz);
      }
      else
      {
+         /* try it as a full zone name */
          tzp = pg_tzset(tzname);
          if (tzp)
          {
*************** timestamptz_zone(PG_FUNCTION_ARGS)
*** 5077,5087 ****

      if (type == TZ || type == DTZ)
      {
!         tz = val * MINS_PER_HOUR;
          result = dt2local(timestamp, tz);
      }
      else
      {
          tzp = pg_tzset(tzname);
          if (tzp)
          {
--- 5083,5095 ----

      if (type == TZ || type == DTZ)
      {
!         /* abbreviation */
!         tz = -val;
          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..f947668 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 ----
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 2e69503..2a26bf7 100644
*** a/src/include/utils/datetime.h
--- b/src/include/utils/datetime.h
*************** struct tzEntry;
*** 124,131 ****

  /*
   * 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
--- 124,131 ----

  /*
   * Token field definitions for time parsing and decoding.
!  * These need to fit into the datetkn struct's type field.
!  * At the moment, that means keep them within [0,127].
   * These are also used for bit masks in DecodeDateDelta()
   *    so actually restrict them to within [0,31] for now.
   * - thomas 97/06/19
*************** struct tzEntry;
*** 203,211 ****
  /* 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 */
--- 203,211 ----
  /* keep this struct small; it gets used a lot */
  typedef struct
  {
!     char        token[TOKMAXLEN + 1];    /* now always null-terminated */
      char        type;
!     int32        value;
  } datetkn;

  /* one of its uses is in tables of time zone abbreviations */
diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h
index c2635c7..8ece8df 100644
*** a/src/interfaces/ecpg/pgtypeslib/dt.h
--- b/src/interfaces/ecpg/pgtypeslib/dt.h
*************** typedef double fsec_t;
*** 130,137 ****

  /*
   * 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
--- 130,137 ----

  /*
   * Token field definitions for time parsing and decoding.
!  * These need to fit into the datetkn struct's type field.
!  * At the moment, that means keep them within [0,127].
   * These are also used for bit masks in DecodeDateDelta()
   *    so actually restrict them to within [0,31] for now.
   * - thomas 97/06/19
*************** 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;


--- 207,215 ----
  /* keep this struct small; it gets used a lot */
  typedef struct
  {
!     char        token[TOKMAXLEN + 1];    /* now always null-terminated */
      char        type;
!     int32        value;
  } 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 */

Re: Proposal for better support of time-varying timezone abbreviations

From
Chris Bandy
Date:
<div dir="ltr"><div class="gmail_extra"><div class="gmail_quote">On Tue, Oct 7, 2014 at 5:05 PM, Tom Lane <span
dir="ltr"><<ahref="mailto:tgl@sss.pgh.pa.us" target="_blank">tgl@sss.pgh.pa.us</a>></span> wrote:</div><div
class="gmail_quote"><br/></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px
0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"> typedef
struct<br/>  {<br />! <span class="" style="white-space:pre"> </span>char<span class="" style="white-space:pre">
</span>token[TOKMAXLEN+ 1];<span class="" style="white-space:pre"> </span>/* now always null-terminated */<br />  <span
class=""style="white-space:pre"> </span>char<span class="" style="white-space:pre"> </span>type;<br />! <span class=""
style="white-space:pre"></span>int32<span class="" style="white-space:pre"> </span>value;<br />  }
datetkn;</blockquote></div><divclass="gmail_quote"><br /></div><div class="gmail_quote">Being entirely new to this
code,"now" makes me think of the "current timestamp". I think this word can be removed to reduce ambiguity.</div><div
class="gmail_quote"><br/></div><div class="gmail_quote"><br /></div><blockquote class="gmail_quote" style="margin:0px
0px0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">+ <span
class=""style="white-space:pre"> </span>/* use strncmp so that we match truncated tokens */<br />  <span class=""
style="white-space:pre"></span>result = strncmp(key, position->token, TOKMAXLEN); </blockquote><div
class="gmail_quote"><br/></div><div class="gmail_quote">In your proposal you wanted to remove "crufty code that deals
withnon-null-terminated token strings". Is this some of that crufty code? Can it be removed?</div><div
class="gmail_quote"><br/></div><div class="gmail_quote"><br /></div><div class="gmail_quote">-- Chris</div></div></div> 

Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
Chris Bandy <bandy.chris@gmail.com> writes:
> On Tue, Oct 7, 2014 at 5:05 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> + /* use strncmp so that we match truncated tokens */
>> result = strncmp(key, position->token, TOKMAXLEN);

> In your proposal you wanted to remove "crufty code that deals with
> non-null-terminated token strings". Is this some of that crufty code? Can
> it be removed?

Yeah, I had hoped to simplify these things to just strcmp, but on closer
inspection that didn't work, because some of the keywords in the table are
truncated at TOKMAXLEN (10 characters).  If we just made these strcmp then
the code would stop recognizing e.g. "milliseconds".

I thought briefly about widening TOKMAXLEN so that there were no truncated
keywords in the table, but that seems risky from a backwards-compatibility
standpoint: as it stands, the code will accept "milliseconds",
"millisecond", or "millisecon", and there might possibly be applications
out there that depend on that.  In any case I'd much rather keep the array
stride at 16 bytes for speed reasons; and who's to say we might not put in
some even-longer keywords in the future?

Another alternative we should maybe consider is leaving the definition
of the token field alone (ie, still not guaranteed null terminated)
which'd leave us with one free byte per datetkn entry.  I can't think
of a likely reason to need another 1-byte field though, and the existing
definition is not without risk.  That comment that was there about "don't
change this to strlcpy" was there because somebody broke it awhile back,
or at least submitted a patch that would've broken it if it'd been
accepted.  People are too used to null-terminated strings in C; a field
definition that violates that norm is just trouble waiting to happen.
        regards, tom lane



Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
I wrote:
> I got interested in the problem discussed in
> http://www.postgresql.org/message-id/20714.1412456604@sss.pgh.pa.us
> to wit:
>> It's becoming clear to me that our existing design whereby zone
>> abbreviations represent fixed GMT offsets isn't really good enough.
>> I've been wondering whether we could change things so that, for instance,
>> "EDT" means "daylight time according to America/New_York" and the system
>> would consult the zic database to find out what the prevailing GMT offset
>> was in that zone on that date.  This would be a lot more robust in the
>> face of the kind of foolishness we now see actually goes on.

Attached is an updated patch for this, incorporating my previous work on
changing the representation of datetkn.  The code changes are complete
I believe, but I've not touched the user documentation yet, nor updated
the tznames data files except for changing MSK for testing purposes.

Some notes:

* There wasn't any reasonable way to return the required information about
a dynamic zone abbreviation from DecodeSpecial().  However, on looking
closely I found that of the existing callers of DecodeSpecial(), only two
actually relied on it to find both timezone abbreviations and regular
keywords.  The other callers only wanted one case or the other.  So it
seemed to me that it'd be best to split the abbreviation-lookup behavior
apart from keyword-lookup.  DecodeSpecial() now does only the latter, and
there's a new function DecodeTimezoneAbbrev() to do the former.  This
avoids touching any code that doesn't care about timezone abbreviations,
and should be a bit faster for those cases too (but about the same speed
otherwise).

* I found that there were pre-existing bugs in DetermineTimeZoneOffset()
for cases in which a zone changed offset but neither the preceding nor
following time segment was marked as DST time.  This caused strange
behaviors for cases like Europe/Moscow's 2011 and 2014 time changes.
This is not terribly surprising because we never thought about zone
changes other than simple DST spring-forward/fall-back changes when
that code was written.

* I've not touched ecpg except for cosmetic changes to keep the struct
definitions in sync, and to fix the previously-mentioned bogus free()
attempt.  I doubt that it would be worth teaching ecpg how to access the
zic timezone database --- the problem of configuring where to find those
files seems like more trouble than it's worth given the lack of
complaints.  I'm not sure what we should do about the obsolete timezone
abbreviations in its table.

* timetz_zone() and DecodeTimeOnly() are not terribly consistent about
how they resolve timezones when not given a date.  The former resolves
based on the value of time(NULL), which is a moving target even within
a transaction (which is why the function is marked volatile I guess).
The latter gets today's date as of start of transaction (so at least
it's stable) and then merges that m/d/y with the h/m/s from the time
value.  That behavior does not seem terribly well thought out either:
on the day of, or day before or after, a DST change it's likely to
produce strange results, especially if the session timezone is different
from the zone specified in the input.  I think we ought to consider
changing one or both of these, though it's not material for back-patching.

* In HEAD, we might want to extend the pg_timezone_abbrevs view to have
a column that shows the underlying zone for a dynamic abbreviation.
The attached patch just shows the abbreviation's behavior as of current
time (defined as now()), which is sort of good enough but certainly not
the whole truth.

Comments?

            regards, tom lane

diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index a13dcc8..b9c2b49 100644
*** a/contrib/btree_gist/btree_ts.c
--- b/contrib/btree_gist/btree_ts.c
*************** tstz_dist(PG_FUNCTION_ARGS)
*** 200,226 ****
   **************************************************/


! static Timestamp
  tstz_to_ts_gmt(TimestampTz ts)
  {
!     Timestamp    gmt;
!     int            val,
!                 tz;
!
!     gmt = ts;
!     DecodeSpecial(0, "gmt", &val);
!
!     if (ts < DT_NOEND && ts > DT_NOBEGIN)
!     {
!         tz = val * 60;
!
! #ifdef HAVE_INT64_TIMESTAMP
!         gmt -= (tz * INT64CONST(1000000));
! #else
!         gmt -= tz;
! #endif
!     }
!     return gmt;
  }


--- 200,210 ----
   **************************************************/


! static inline Timestamp
  tstz_to_ts_gmt(TimestampTz ts)
  {
!     /* No timezone correction is needed, since GMT is offset 0 by definition */
!     return (Timestamp) ts;
  }


diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 073104d..bb23b12 100644
*** a/src/backend/utils/adt/date.c
--- b/src/backend/utils/adt/date.c
*************** timetz_zone(PG_FUNCTION_ARGS)
*** 2695,2718 ****
      pg_tz       *tzp;

      /*
!      * Look up the requested timezone.  First we look in the date token table
!      * (to handle cases like "EST"), and if that fails, we look in the
!      * timezone database (to handle cases like "America/New_York").  (This
!      * matches the order in which timestamp input checks the cases; it's
!      * important because the timezone database unwisely uses a few zone names
!      * that are identical to offset abbreviations.)
       */
      text_to_cstring_buffer(zone, tzname, sizeof(tzname));
      lowzone = downcase_truncate_identifier(tzname,
                                             strlen(tzname),
                                             false);

!     type = DecodeSpecial(0, lowzone, &val);

      if (type == TZ || type == DTZ)
!         tz = val * MINS_PER_HOUR;
      else
      {
          tzp = pg_tzset(tzname);
          if (tzp)
          {
--- 2695,2733 ----
      pg_tz       *tzp;

      /*
!      * Look up the requested timezone.  First we look in the timezone
!      * abbreviation table (to handle cases like "EST"), and if that fails, we
!      * look in the timezone database (to handle cases like
!      * "America/New_York").  (This matches the order in which timestamp input
!      * checks the cases; it's important because the timezone database unwisely
!      * uses a few zone names that are identical to offset abbreviations.)
       */
      text_to_cstring_buffer(zone, tzname, sizeof(tzname));
+
+     /* DecodeTimezoneAbbrev requires lowercase input */
      lowzone = downcase_truncate_identifier(tzname,
                                             strlen(tzname),
                                             false);

!     type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);

      if (type == TZ || type == DTZ)
!     {
!         /* fixed-offset abbreviation */
!         tz = -val;
!     }
!     else if (type == DYNTZ)
!     {
!         /* dynamic-offset abbreviation, resolve using current time */
!         pg_time_t    now = (pg_time_t) time(NULL);
!         struct pg_tm *tm;
!
!         tm = pg_localtime(&now, tzp);
!         tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp);
!     }
      else
      {
+         /* try it as a full zone name */
          tzp = pg_tzset(tzname);
          if (tzp)
          {
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7632d11..4381a5a 100644
*** a/src/backend/utils/adt/datetime.c
--- b/src/backend/utils/adt/datetime.c
*************** static void AdjustFractSeconds(double fr
*** 50,55 ****
--- 50,60 ----
                     int scale);
  static void AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec,
                  int scale);
+ static int DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp,
+                                 pg_time_t *tp);
+ static int DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
+                                       pg_tz *tzp, int *isdst);
+ static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);


  const int    day_tab[2][13] =
*************** const char *const days[] = {"Sunday", "M
*** 70,110 ****
   *****************************************************************************/

  /*
-  * Definitions for squeezing values into "value"
-  * We set aside a high bit for a sign, and scale the timezone offsets
-  * in minutes by a factor of 15 (so can represent quarter-hour increments).
-  */
- #define ABS_SIGNBIT        ((char) 0200)
- #define VALMASK            ((char) 0177)
- #define POS(n)            (n)
- #define NEG(n)            ((n)|ABS_SIGNBIT)
- #define SIGNEDCHAR(c)    ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c))
- #define FROMVAL(tp)        (-SIGNEDCHAR((tp)->value) * 15) /* uncompress */
- #define TOVAL(tp, v)    ((tp)->value = ((v) < 0? NEG((-(v))/15): POS(v)/15))
-
- /*
   * datetktbl holds date/time keywords.
   *
   * Note that this table must be strictly alphabetically ordered to allow an
   * O(ln(N)) search algorithm to be used.
   *
!  * The token field is NOT guaranteed to be NULL-terminated.
!  *
!  * To keep this table reasonably small, we divide the value for TZ and DTZ
!  * entries by 15 (so they are on 15 minute boundaries) and truncate the token
!  * field at TOKMAXLEN characters.
!  * Formerly, we divided by 10 rather than 15 but there are a few time zones
!  * which are 30 or 45 minutes away from an even hour, most are on an hour
!  * boundary, and none on other boundaries.
   *
!  * The static table contains no TZ or DTZ entries, rather those are loaded
!  * from configuration files and stored in timezonetktbl, which has the same
!  * format as the static datetktbl.
   */
- static datetkn *timezonetktbl = NULL;
-
- static int    sztimezonetktbl = 0;
-
  static const datetkn datetktbl[] = {
      /* token, type, value */
      {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
--- 75,92 ----
   *****************************************************************************/

  /*
   * datetktbl holds date/time keywords.
   *
   * Note that this table must be strictly alphabetically ordered to allow an
   * O(ln(N)) search algorithm to be used.
   *
!  * The token field must be NUL-terminated; we truncate entries to TOKMAXLEN
!  * characters to fit.
   *
!  * The static table contains no TZ, DTZ, or DYNTZ entries; rather those
!  * are loaded from configuration files and stored in zoneabbrevtbl, whose
!  * abbrevs[] field has the same format as the static datetktbl.
   */
  static const datetkn datetktbl[] = {
      /* token, type, value */
      {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
*************** static const datetkn datetktbl[] = {
*** 123,129 ****
      {"december", MONTH, 12},
      {"dow", RESERV, DTK_DOW},    /* day of week */
      {"doy", RESERV, DTK_DOY},    /* day of year */
!     {"dst", DTZMOD, 6},
      {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
      {"feb", MONTH, 2},
      {"february", MONTH, 2},
--- 105,111 ----
      {"december", MONTH, 12},
      {"dow", RESERV, DTK_DOW},    /* day of week */
      {"doy", RESERV, DTK_DOY},    /* day of year */
!     {"dst", DTZMOD, SECS_PER_HOUR},
      {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
      {"feb", MONTH, 2},
      {"february", MONTH, 2},
*************** static const datetkn datetktbl[] = {
*** 185,190 ****
--- 167,176 ----

  static int    szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];

+ /*
+  * deltatktbl: same format as datetktbl, but holds keywords used to represent
+  * time units (eg, for intervals, and for EXTRACT).
+  */
  static const datetkn deltatktbl[] = {
      /* token, type, value */
      {"@", IGNORE_DTF, 0},        /* postgres relative prefix */
*************** static const datetkn deltatktbl[] = {
*** 254,263 ****
--- 240,255 ----

  static int    szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];

+ static TimeZoneAbbrevTable *zoneabbrevtbl = NULL;
+
+ /* Caches of recent lookup results in the above tables */
+
  static const datetkn *datecache[MAXDATEFIELDS] = {NULL};

  static const datetkn *deltacache[MAXDATEFIELDS] = {NULL};

+ static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL};
+

  /*
   * strtoi --- just like strtol, but returns int not long
*************** DecodeDateTime(char **field, int *ftype,
*** 798,803 ****
--- 790,798 ----
      bool        is2digits = FALSE;
      bool        bc = FALSE;
      pg_tz       *namedTz = NULL;
+     pg_tz       *abbrevTz = NULL;
+     pg_tz       *valtz;
+     char       *abbrev = NULL;
      struct pg_tm cur_tm;

      /*
*************** DecodeDateTime(char **field, int *ftype,
*** 1194,1200 ****

              case DTK_STRING:
              case DTK_SPECIAL:
!                 type = DecodeSpecial(i, field[i], &val);
                  if (type == IGNORE_DTF)
                      continue;

--- 1189,1198 ----

              case DTK_STRING:
              case DTK_SPECIAL:
!                 /* timezone abbrevs take precedence over built-in tokens */
!                 type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
!                 if (type == UNKNOWN_FIELD)
!                     type = DecodeSpecial(i, field[i], &val);
                  if (type == IGNORE_DTF)
                      continue;

*************** DecodeDateTime(char **field, int *ftype,
*** 1286,1292 ****
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp += val * MINS_PER_HOUR;
                          break;

                      case DTZ:
--- 1284,1290 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp -= val;
                          break;

                      case DTZ:
*************** DecodeDateTime(char **field, int *ftype,
*** 1299,1315 ****
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = val * MINS_PER_HOUR;
                          break;

                      case TZ:
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = val * MINS_PER_HOUR;
                          break;

!                     case IGNORE_DTF:
                          break;

                      case AMPM:
--- 1297,1319 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = -val;
                          break;

                      case TZ:
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = -val;
                          break;

!                     case DYNTZ:
!                         tmask |= DTK_M(TZ);
!                         if (tzp == NULL)
!                             return DTERR_BAD_FORMAT;
!                         /* we'll determine the actual offset later */
!                         abbrevTz = valtz;
!                         abbrev = field[i];
                          break;

                      case AMPM:
*************** DecodeDateTime(char **field, int *ftype,
*** 1419,1425 ****
              *tzp = DetermineTimeZoneOffset(tm, namedTz);
          }

!         /* timezone not specified? then find local timezone if possible */
          if (tzp != NULL && !(fmask & DTK_M(TZ)))
          {
              /*
--- 1423,1442 ----
              *tzp = DetermineTimeZoneOffset(tm, namedTz);
          }

!         /*
!          * Likewise, if we had a dynamic timezone abbreviation, resolve it
!          * now.
!          */
!         if (abbrevTz != NULL)
!         {
!             /* daylight savings time modifier disallowed with dynamic TZ */
!             if (fmask & DTK_M(DTZMOD))
!                 return DTERR_BAD_FORMAT;
!
!             *tzp = DetermineTimeZoneAbbrevOffset(tm, abbrev, abbrevTz);
!         }
!
!         /* timezone not specified? then use session timezone */
          if (tzp != NULL && !(fmask & DTK_M(TZ)))
          {
              /*
*************** DecodeDateTime(char **field, int *ftype,
*** 1439,1455 ****

  /* DetermineTimeZoneOffset()
   *
!  * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
!  * tm_sec fields are set, attempt to determine the applicable time zone
!  * (ie, regular or daylight-savings time) at that time.  Set the struct pg_tm's
!  * tm_isdst field accordingly, and return the actual timezone offset.
   *
   * Note: it might seem that we should use mktime() for this, but bitter
   * experience teaches otherwise.  This code is much faster than most versions
   * of mktime(), anyway.
   */
! int
! DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
  {
      int            date,
                  sec;
--- 1456,1495 ----

  /* DetermineTimeZoneOffset()
   *
!  * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min,
!  * and tm_sec fields are set, and a zic-style time zone definition, determine
!  * the applicable GMT offset and daylight-savings status at that time.
!  * Set the struct pg_tm's tm_isdst field accordingly, and return the GMT
!  * offset as the function result.
!  *
!  * Note: if the date is out of the range we can deal with, we return zero
!  * as the GMT offset and set tm_isdst = 0.  We don't throw an error here,
!  * though probably some higher-level code will.
!  */
! int
! DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
! {
!     pg_time_t    t;
!
!     return DetermineTimeZoneOffsetInternal(tm, tzp, &t);
! }
!
!
! /* DetermineTimeZoneOffsetInternal()
!  *
!  * As above, but also return the actual UTC time imputed to the date/time
!  * into *tp.
!  *
!  * In event of an out-of-range date, we punt by returning zero into *tp.
!  * This is okay for the immediate callers but is a good reason for not
!  * exposing this worker function globally.
   *
   * Note: it might seem that we should use mktime() for this, but bitter
   * experience teaches otherwise.  This code is much faster than most versions
   * of mktime(), anyway.
   */
! static int
! DetermineTimeZoneOffsetInternal(struct pg_tm * tm, pg_tz *tzp, pg_time_t *tp)
  {
      int            date,
                  sec;
*************** DetermineTimeZoneOffset(struct pg_tm * t
*** 1468,1475 ****
      /*
       * First, generate the pg_time_t value corresponding to the given
       * y/m/d/h/m/s taken as GMT time.  If this overflows, punt and decide the
!      * timezone is GMT.  (We only need to worry about overflow on machines
!      * where pg_time_t is 32 bits.)
       */
      if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
          goto overflow;
--- 1508,1515 ----
      /*
       * First, generate the pg_time_t value corresponding to the given
       * y/m/d/h/m/s taken as GMT time.  If this overflows, punt and decide the
!      * timezone is GMT.  (For a valid Julian date, integer overflow should be
!      * impossible with 64-bit pg_time_t, but let's check for safety.)
       */
      if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
          goto overflow;
*************** DetermineTimeZoneOffset(struct pg_tm * t
*** 1506,1511 ****
--- 1546,1552 ----
      {
          /* Non-DST zone, life is simple */
          tm->tm_isdst = before_isdst;
+         *tp = mytime - before_gmtoff;
          return -(int) before_gmtoff;
      }

*************** DetermineTimeZoneOffset(struct pg_tm * t
*** 1526,1563 ****
          goto overflow;

      /*
!      * If both before or both after the boundary time, we know what to do
       */
!     if (beforetime <= boundary && aftertime < boundary)
      {
          tm->tm_isdst = before_isdst;
          return -(int) before_gmtoff;
      }
      if (beforetime > boundary && aftertime >= boundary)
      {
          tm->tm_isdst = after_isdst;
          return -(int) after_gmtoff;
      }

      /*
!      * It's an invalid or ambiguous time due to timezone transition. Prefer
!      * the standard-time interpretation.
       */
!     if (after_isdst == 0)
      {
!         tm->tm_isdst = after_isdst;
!         return -(int) after_gmtoff;
      }
!     tm->tm_isdst = before_isdst;
!     return -(int) before_gmtoff;

  overflow:
      /* Given date is out of range, so assume UTC */
      tm->tm_isdst = 0;
      return 0;
  }


  /* DecodeTimeOnly()
   * Interpret parsed string as time fields only.
   * Returns 0 if successful, DTERR code if bogus input detected.
--- 1567,1690 ----
          goto overflow;

      /*
!      * If both before or both after the boundary time, we know what to do. The
!      * boundary time itself is considered to be after the transition, which
!      * means we can accept aftertime == boundary in the second case.
       */
!     if (beforetime < boundary && aftertime < boundary)
      {
          tm->tm_isdst = before_isdst;
+         *tp = beforetime;
          return -(int) before_gmtoff;
      }
      if (beforetime > boundary && aftertime >= boundary)
      {
          tm->tm_isdst = after_isdst;
+         *tp = aftertime;
          return -(int) after_gmtoff;
      }

      /*
!      * It's an invalid or ambiguous time due to timezone transition.  In a
!      * spring-forward transition, prefer the "before" interpretation; in a
!      * fall-back transition, prefer "after".  (We used to define and implement
!      * this test as "prefer the standard-time interpretation", but that rule
!      * does not help to resolve the behavior when both times are reported as
!      * standard time; which does happen, eg Europe/Moscow in Oct 2014.)
       */
!     if (beforetime > aftertime)
      {
!         tm->tm_isdst = before_isdst;
!         *tp = beforetime;
!         return -(int) before_gmtoff;
      }
!     tm->tm_isdst = after_isdst;
!     *tp = aftertime;
!     return -(int) after_gmtoff;

  overflow:
      /* Given date is out of range, so assume UTC */
      tm->tm_isdst = 0;
+     *tp = 0;
      return 0;
  }


+ /* DetermineTimeZoneAbbrevOffset()
+  *
+  * Determine the GMT offset and DST flag to be attributed to a dynamic
+  * time zone abbreviation, that is one whose meaning has changed over time.
+  * *tm contains the local time at which the meaning should be determined,
+  * and tm->tm_isdst receives the DST flag.
+  *
+  * This differs from the behavior of DetermineTimeZoneOffset() in that a
+  * standard-time or daylight-time abbreviation forces use of the corresponding
+  * GMT offset even when the zone was then in DS or standard time respectively.
+  */
+ int
+ DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp)
+ {
+     pg_time_t    t;
+
+     /*
+      * Compute the UTC time we want to probe at.  (In event of overflow, we'll
+      * probe at the epoch, which is a bit random but probably doesn't matter.)
+      */
+     (void) DetermineTimeZoneOffsetInternal(tm, tzp, &t);
+
+     return DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, &tm->tm_isdst);
+ }
+
+
+ /* DetermineTimeZoneAbbrevOffsetTS()
+  *
+  * As above but the probe time is specified as a TimestampTz (hence, UTC time),
+  * and DST status is returned into *isdst rather than into tm->tm_isdst.
+  */
+ int
+ DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
+                                 pg_tz *tzp, int *isdst)
+ {
+     pg_time_t    t = timestamptz_to_time_t(ts);
+
+     return DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp, isdst);
+ }
+
+
+ /* DetermineTimeZoneAbbrevOffsetInternal()
+  *
+  * Workhorse for above two functions: work from a pg_time_t probe instant.
+  * DST status is returned into *isdst.
+  */
+ static int
+ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr,
+                                       pg_tz *tzp, int *isdst)
+ {
+     char        upabbr[TZ_STRLEN_MAX + 1];
+     unsigned char *p;
+     long int    gmtoff;
+
+     /* We need to force the abbrev to upper case */
+     strlcpy(upabbr, abbr, sizeof(upabbr));
+     for (p = (unsigned char *) upabbr; *p; p++)
+         *p = pg_toupper(*p);
+
+     /* Look up the abbrev's meaning at this time in this zone */
+     if (!pg_interpret_timezone_abbrev(upabbr,
+                                       &t,
+                                       &gmtoff,
+                                       isdst,
+                                       tzp))
+         ereport(ERROR,
+                 (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                  errmsg("time zone abbreviation \"%s\" is not used in time zone \"%s\"",
+                         abbr, pg_get_timezone_name(tzp))));
+
+     /* Change sign to agree with DetermineTimeZoneOffset() */
+     return (int) -gmtoff;
+ }
+
+
  /* DecodeTimeOnly()
   * Interpret parsed string as time fields only.
   * Returns 0 if successful, DTERR code if bogus input detected.
*************** DecodeTimeOnly(char **field, int *ftype,
*** 1586,1591 ****
--- 1713,1721 ----
      bool        bc = FALSE;
      int            mer = HR24;
      pg_tz       *namedTz = NULL;
+     pg_tz       *abbrevTz = NULL;
+     char       *abbrev = NULL;
+     pg_tz       *valtz;

      *dtype = DTK_TIME;
      tm->tm_hour = 0;
*************** DecodeTimeOnly(char **field, int *ftype,
*** 1930,1936 ****

              case DTK_STRING:
              case DTK_SPECIAL:
!                 type = DecodeSpecial(i, field[i], &val);
                  if (type == IGNORE_DTF)
                      continue;

--- 2060,2069 ----

              case DTK_STRING:
              case DTK_SPECIAL:
!                 /* timezone abbrevs take precedence over built-in tokens */
!                 type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
!                 if (type == UNKNOWN_FIELD)
!                     type = DecodeSpecial(i, field[i], &val);
                  if (type == IGNORE_DTF)
                      continue;

*************** DecodeTimeOnly(char **field, int *ftype,
*** 1978,1984 ****
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp += val * MINS_PER_HOUR;
                          break;

                      case DTZ:
--- 2111,2117 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp -= val;
                          break;

                      case DTZ:
*************** DecodeTimeOnly(char **field, int *ftype,
*** 1991,1997 ****
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = val * MINS_PER_HOUR;
                          ftype[i] = DTK_TZ;
                          break;

--- 2124,2130 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = -val;
                          ftype[i] = DTK_TZ;
                          break;

*************** DecodeTimeOnly(char **field, int *ftype,
*** 1999,2009 ****
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = val * MINS_PER_HOUR;
                          ftype[i] = DTK_TZ;
                          break;

!                     case IGNORE_DTF:
                          break;

                      case AMPM:
--- 2132,2149 ----
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return DTERR_BAD_FORMAT;
!                         *tzp = -val;
                          ftype[i] = DTK_TZ;
                          break;

!                     case DYNTZ:
!                         tmask |= DTK_M(TZ);
!                         if (tzp == NULL)
!                             return DTERR_BAD_FORMAT;
!                         /* we'll determine the actual offset later */
!                         abbrevTz = valtz;
!                         abbrev = field[i];
!                         ftype[i] = DTK_TZ;
                          break;

                      case AMPM:
*************** DecodeTimeOnly(char **field, int *ftype,
*** 2123,2129 ****
          }
      }

!     /* timezone not specified? then find local timezone if possible */
      if (tzp != NULL && !(fmask & DTK_M(TZ)))
      {
          struct pg_tm tt,
--- 2263,2298 ----
          }
      }

!     /*
!      * Likewise, if we had a dynamic timezone abbreviation, resolve it now.
!      */
!     if (abbrevTz != NULL)
!     {
!         struct pg_tm tt,
!                    *tmp = &tt;
!
!         /*
!          * daylight savings time modifier but no standard timezone? then error
!          */
!         if (fmask & DTK_M(DTZMOD))
!             return DTERR_BAD_FORMAT;
!
!         if ((fmask & DTK_DATE_M) == 0)
!             GetCurrentDateTime(tmp);
!         else
!         {
!             tmp->tm_year = tm->tm_year;
!             tmp->tm_mon = tm->tm_mon;
!             tmp->tm_mday = tm->tm_mday;
!         }
!         tmp->tm_hour = tm->tm_hour;
!         tmp->tm_min = tm->tm_min;
!         tmp->tm_sec = tm->tm_sec;
!         *tzp = DetermineTimeZoneAbbrevOffset(tmp, abbrev, abbrevTz);
!         tm->tm_isdst = tmp->tm_isdst;
!     }
!
!     /* timezone not specified? then use session timezone */
      if (tzp != NULL && !(fmask & DTK_M(TZ)))
      {
          struct pg_tm tt,
*************** DecodeNumberField(int len, char *str, in
*** 2710,2717 ****
   * Interpret string as a numeric timezone.
   *
   * Return 0 if okay (and set *tzp), a DTERR code if not okay.
-  *
-  * NB: this must *not* ereport on failure; see commands/variable.c.
   */
  int
  DecodeTimezone(char *str, int *tzp)
--- 2879,2884 ----
*************** DecodeTimezone(char *str, int *tzp)
*** 2776,2789 ****
      return 0;
  }

  /* DecodeSpecial()
   * Decode text string using lookup table.
   *
   * Implement a cache lookup since it is likely that dates
   *    will be related in format.
-  *
-  * NB: this must *not* ereport on failure;
-  * see commands/variable.c.
   */
  int
  DecodeSpecial(int field, char *lowtoken, int *val)
--- 2943,3017 ----
      return 0;
  }

+
+ /* DecodeTimezoneAbbrev()
+  * Interpret string as a timezone abbreviation, if possible.
+  *
+  * Returns an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if
+  * string is not any known abbreviation.  On success, set *offset and *tz to
+  * represent the UTC offset (for TZ or DTZ) or underlying zone (for DYNTZ).
+  * Note that full timezone names (such as America/New_York) are not handled
+  * here, mostly for historical reasons.
+  *
+  * Given string must be lowercased already.
+  *
+  * Implement a cache lookup since it is likely that dates
+  *    will be related in format.
+  */
+ int
+ DecodeTimezoneAbbrev(int field, char *lowtoken,
+                      int *offset, pg_tz **tz)
+ {
+     int            type;
+     const datetkn *tp;
+
+     tp = abbrevcache[field];
+     /* use strncmp so that we match truncated tokens */
+     if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
+     {
+         if (zoneabbrevtbl)
+             tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
+                              zoneabbrevtbl->numabbrevs);
+         else
+             tp = NULL;
+     }
+     if (tp == NULL)
+     {
+         type = UNKNOWN_FIELD;
+         *offset = 0;
+         *tz = NULL;
+     }
+     else
+     {
+         abbrevcache[field] = tp;
+         type = tp->type;
+         if (type == DYNTZ)
+         {
+             *offset = 0;
+             *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp);
+         }
+         else
+         {
+             *offset = tp->value;
+             *tz = NULL;
+         }
+     }
+
+     return type;
+ }
+
+
  /* DecodeSpecial()
   * Decode text string using lookup table.
   *
+  * Recognizes the keywords listed in datetktbl.
+  * Note: at one time this would also recognize timezone abbreviations,
+  * but no more; use DecodeTimezoneAbbrev for that.
+  *
+  * Given string must be lowercased already.
+  *
   * Implement a cache lookup since it is likely that dates
   *    will be related in format.
   */
  int
  DecodeSpecial(int field, char *lowtoken, int *val)
*************** DecodeSpecial(int field, char *lowtoken,
*** 2792,2802 ****
      const datetkn *tp;

      tp = datecache[field];
      if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
      {
!         tp = datebsearch(lowtoken, timezonetktbl, sztimezonetktbl);
!         if (tp == NULL)
!             tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
      }
      if (tp == NULL)
      {
--- 3020,3029 ----
      const datetkn *tp;

      tp = datecache[field];
+     /* use strncmp so that we match truncated tokens */
      if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
      {
!         tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
      }
      if (tp == NULL)
      {
*************** DecodeSpecial(int field, char *lowtoken,
*** 2807,2824 ****
      {
          datecache[field] = tp;
          type = tp->type;
!         switch (type)
!         {
!             case TZ:
!             case DTZ:
!             case DTZMOD:
!                 *val = FROMVAL(tp);
!                 break;
!
!             default:
!                 *val = tp->value;
!                 break;
!         }
      }

      return type;
--- 3034,3040 ----
      {
          datecache[field] = tp;
          type = tp->type;
!         *val = tp->value;
      }

      return type;
*************** DecodeISO8601Interval(char *str,
*** 3494,3501 ****

  /* DecodeUnits()
   * Decode text string using lookup table.
!  * This routine supports time interval decoding
!  * (hence, it need not recognize timezone names).
   */
  int
  DecodeUnits(int field, char *lowtoken, int *val)
--- 3710,3722 ----

  /* DecodeUnits()
   * Decode text string using lookup table.
!  *
!  * This routine recognizes keywords associated with time interval units.
!  *
!  * Given string must be lowercased already.
!  *
!  * Implement a cache lookup since it is likely that dates
!  *    will be related in format.
   */
  int
  DecodeUnits(int field, char *lowtoken, int *val)
*************** DecodeUnits(int field, char *lowtoken, i
*** 3504,3509 ****
--- 3725,3731 ----
      const datetkn *tp;

      tp = deltacache[field];
+     /* use strncmp so that we match truncated tokens */
      if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
      {
          tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl);
*************** DecodeUnits(int field, char *lowtoken, i
*** 3517,3526 ****
      {
          deltacache[field] = tp;
          type = tp->type;
!         if (type == TZ || type == DTZ)
!             *val = FROMVAL(tp);
!         else
!             *val = tp->value;
      }

      return type;
--- 3739,3745 ----
      {
          deltacache[field] = tp;
          type = tp->type;
!         *val = tp->value;
      }

      return type;
*************** datebsearch(const char *key, const datet
*** 3593,3601 ****
          while (last >= base)
          {
              position = base + ((last - base) >> 1);
!             result = key[0] - position->token[0];
              if (result == 0)
              {
                  result = strncmp(key, position->token, TOKMAXLEN);
                  if (result == 0)
                      return position;
--- 3812,3822 ----
          while (last >= base)
          {
              position = base + ((last - base) >> 1);
!             /* precheck the first character for a bit of extra speed */
!             result = (int) key[0] - (int) position->token[0];
              if (result == 0)
              {
+                 /* use strncmp so that we match truncated tokens */
                  result = strncmp(key, position->token, TOKMAXLEN);
                  if (result == 0)
                      return position;
*************** CheckDateTokenTable(const char *tablenam
*** 4142,4156 ****
      bool        ok = true;
      int            i;

!     for (i = 1; i < nel; i++)
      {
!         if (strncmp(base[i - 1].token, base[i].token, TOKMAXLEN) >= 0)
          {
              /* %.*s is safe since all our tokens are ASCII */
!             elog(LOG, "ordering error in %s table: \"%.*s\" >= \"%.*s\"",
                   tablename,
!                  TOKMAXLEN, base[i - 1].token,
!                  TOKMAXLEN, base[i].token);
              ok = false;
          }
      }
--- 4363,4388 ----
      bool        ok = true;
      int            i;

!     for (i = 0; i < nel; i++)
      {
!         /* check for token strings that don't fit */
!         if (strlen(base[i].token) > TOKMAXLEN)
          {
              /* %.*s is safe since all our tokens are ASCII */
!             elog(LOG, "token too long in %s table: \"%.*s\"",
                   tablename,
!                  TOKMAXLEN + 1, base[i].token);
!             ok = false;
!             break;                /* don't risk applying strcmp */
!         }
!         /* check for out of order */
!         if (i > 0 &&
!             strcmp(base[i - 1].token, base[i].token) >= 0)
!         {
!             elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"",
!                  tablename,
!                  base[i - 1].token,
!                  base[i].token);
              ok = false;
          }
      }
*************** TemporalTransform(int32 max_precis, Node
*** 4208,4234 ****
  /*
   * This function gets called during timezone config file load or reload
   * to create the final array of timezone tokens.  The argument array
!  * is already sorted in name order.  The data is converted to datetkn
!  * format and installed in *tbl, which must be allocated by the caller.
   */
! void
! ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl,
!                        struct tzEntry *abbrevs, int n)
  {
!     datetkn    *newtbl = tbl->abbrevs;
      int            i;

      tbl->numabbrevs = n;
      for (i = 0; i < n; i++)
      {
!         /* do NOT use strlcpy here; token field need not be null-terminated */
!         strncpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN);
!         newtbl[i].type = abbrevs[i].is_dst ? DTZ : TZ;
!         TOVAL(&newtbl[i], abbrevs[i].offset / MINS_PER_HOUR);
      }

      /* Check the ordering, if testing */
!     Assert(CheckDateTokenTable("timezone offset", newtbl, n));
  }

  /*
--- 4440,4527 ----
  /*
   * This function gets called during timezone config file load or reload
   * to create the final array of timezone tokens.  The argument array
!  * is already sorted in name order.
!  *
!  * The result is a TimeZoneAbbrevTable (which must be a single malloc'd chunk)
!  * or NULL on malloc failure.  No other error conditions are defined.
   */
! TimeZoneAbbrevTable *
! ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
  {
!     TimeZoneAbbrevTable *tbl;
!     Size        tbl_size;
      int            i;

+     /* Space for fixed fields and datetkn array */
+     tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
+         n * sizeof(datetkn);
+     tbl_size = MAXALIGN(tbl_size);
+     /* Count up space for dynamic abbreviations */
+     for (i = 0; i < n; i++)
+     {
+         struct tzEntry *abbr = abbrevs + i;
+
+         if (abbr->zone != NULL)
+         {
+             Size        dsize;
+
+             dsize = offsetof(DynamicZoneAbbrev, zone) +
+                 strlen(abbr->zone) + 1;
+             tbl_size += MAXALIGN(dsize);
+         }
+     }
+
+     /* Alloc the result ... */
+     tbl = malloc(tbl_size);
+     if (!tbl)
+         return NULL;
+
+     /* ... and fill it in */
+     tbl->tblsize = tbl_size;
      tbl->numabbrevs = n;
+     /* in this loop, tbl_size reprises the space calculation above */
+     tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
+         n * sizeof(datetkn);
+     tbl_size = MAXALIGN(tbl_size);
      for (i = 0; i < n; i++)
      {
!         struct tzEntry *abbr = abbrevs + i;
!         datetkn    *dtoken = tbl->abbrevs + i;
!
!         /* use strlcpy to truncate name if necessary */
!         strlcpy(dtoken->token, abbr->abbrev, TOKMAXLEN + 1);
!         if (abbr->zone != NULL)
!         {
!             /* Allocate a DynamicZoneAbbrev for this abbreviation */
!             DynamicZoneAbbrev *dtza;
!             Size        dsize;
!
!             dtza = (DynamicZoneAbbrev *) ((char *) tbl + tbl_size);
!             dtza->tz = NULL;
!             strcpy(dtza->zone, abbr->zone);
!
!             dtoken->type = DYNTZ;
!             /* value is offset from table start to DynamicZoneAbbrev */
!             dtoken->value = (int32) tbl_size;
!
!             dsize = offsetof(DynamicZoneAbbrev, zone) +
!                 strlen(abbr->zone) + 1;
!             tbl_size += MAXALIGN(dsize);
!         }
!         else
!         {
!             dtoken->type = abbr->is_dst ? DTZ : TZ;
!             dtoken->value = abbr->offset;
!         }
      }

+     /* Assert the two loops above agreed on size calculations */
+     Assert(tbl->tblsize == tbl_size);
+
      /* Check the ordering, if testing */
!     Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n));
!
!     return tbl;
  }

  /*
*************** ConvertTimeZoneAbbrevs(TimeZoneAbbrevTab
*** 4239,4254 ****
  void
  InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
  {
!     int            i;

!     timezonetktbl = tbl->abbrevs;
!     sztimezonetktbl = tbl->numabbrevs;

!     /* clear date cache in case it contains any stale timezone names */
!     for (i = 0; i < MAXDATEFIELDS; i++)
!         datecache[i] = NULL;
  }

  /*
   * This set-returning function reads all the available time zone abbreviations
   * and returns a set of (abbrev, utc_offset, is_dst).
--- 4532,4577 ----
  void
  InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
  {
!     zoneabbrevtbl = tbl;
!     /* reset abbrevcache, which may contain pointers into old table */
!     memset(abbrevcache, 0, sizeof(abbrevcache));
! }

! /*
!  * Helper subroutine to locate pg_tz timezone for a dynamic abbreviation.
!  */
! static pg_tz *
! FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
! {
!     DynamicZoneAbbrev *dtza;

!     /* Just some sanity checks to prevent indexing off into nowhere */
!     Assert(tp->type == DYNTZ);
!     Assert(tp->value > 0 && tp->value < tbl->tblsize);
!
!     dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value);
!
!     /* Look up the underlying zone if we haven't already */
!     if (dtza->tz == NULL)
!     {
!         dtza->tz = pg_tzset(dtza->zone);
!
!         /*
!          * Ideally we'd let the caller ereport instead of doing it here, but
!          * then there is no way to report the bad time zone name.
!          */
!         if (dtza->tz == NULL)
!             ereport(ERROR,
!                     (errcode(ERRCODE_CONFIG_FILE_ERROR),
!                      errmsg("time zone \"%s\" not recognized",
!                             dtza->zone),
!                      errdetail("This time zone name appears in the configuration file for time zone abbreviation
\"%s\".",
!                                tp->token)));
!     }
!     return dtza->tz;
  }

+
  /*
   * This set-returning function reads all the available time zone abbreviations
   * and returns a set of (abbrev, utc_offset, is_dst).
*************** pg_timezone_abbrevs(PG_FUNCTION_ARGS)
*** 4262,4268 ****
--- 4585,4594 ----
      HeapTuple    tuple;
      Datum        values[3];
      bool        nulls[3];
+     const datetkn *tp;
      char        buffer[TOKMAXLEN + 1];
+     int            gmtoffset;
+     bool        is_dst;
      unsigned char *p;
      struct pg_tm tm;
      Interval   *resInterval;
*************** pg_timezone_abbrevs(PG_FUNCTION_ARGS)
*** 4306,4336 ****
      funcctx = SRF_PERCALL_SETUP();
      pindex = (int *) funcctx->user_fctx;

!     if (*pindex >= sztimezonetktbl)
          SRF_RETURN_DONE(funcctx);

      MemSet(nulls, 0, sizeof(nulls));

      /*
       * Convert name to text, using upcasing conversion that is the inverse of
       * what ParseDateTime() uses.
       */
!     strncpy(buffer, timezonetktbl[*pindex].token, TOKMAXLEN);
!     buffer[TOKMAXLEN] = '\0';    /* may not be null-terminated */
      for (p = (unsigned char *) buffer; *p; p++)
          *p = pg_toupper(*p);

      values[0] = CStringGetTextDatum(buffer);

      MemSet(&tm, 0, sizeof(struct pg_tm));
!     tm.tm_min = (-1) * FROMVAL(&timezonetktbl[*pindex]);
      resInterval = (Interval *) palloc(sizeof(Interval));
      tm2interval(&tm, 0, resInterval);
      values[1] = IntervalPGetDatum(resInterval);

!     Assert(timezonetktbl[*pindex].type == DTZ ||
!            timezonetktbl[*pindex].type == TZ);
!     values[2] = BoolGetDatum(timezonetktbl[*pindex].type == DTZ);

      (*pindex)++;

--- 4632,4696 ----
      funcctx = SRF_PERCALL_SETUP();
      pindex = (int *) funcctx->user_fctx;

!     if (zoneabbrevtbl == NULL ||
!         *pindex >= zoneabbrevtbl->numabbrevs)
          SRF_RETURN_DONE(funcctx);

+     tp = zoneabbrevtbl->abbrevs + *pindex;
+
+     switch (tp->type)
+     {
+         case TZ:
+             gmtoffset = tp->value;
+             is_dst = false;
+             break;
+         case DTZ:
+             gmtoffset = tp->value;
+             is_dst = true;
+             break;
+         case DYNTZ:
+             {
+                 /* Determine the current meaning of the abbrev */
+                 pg_tz       *tzp;
+                 TimestampTz now;
+                 int            isdst;
+
+                 tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp);
+                 now = GetCurrentTransactionStartTimestamp();
+                 gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now,
+                                                              tp->token,
+                                                              tzp,
+                                                              &isdst);
+                 is_dst = (bool) isdst;
+                 break;
+             }
+         default:
+             elog(ERROR, "unrecognized timezone type %d", (int) tp->type);
+             gmtoffset = 0;        /* keep compiler quiet */
+             is_dst = false;
+             break;
+     }
+
      MemSet(nulls, 0, sizeof(nulls));

      /*
       * Convert name to text, using upcasing conversion that is the inverse of
       * what ParseDateTime() uses.
       */
!     strlcpy(buffer, tp->token, sizeof(buffer));
      for (p = (unsigned char *) buffer; *p; p++)
          *p = pg_toupper(*p);

      values[0] = CStringGetTextDatum(buffer);

+     /* Convert offset (in seconds) to an interval */
      MemSet(&tm, 0, sizeof(struct pg_tm));
!     tm.tm_sec = gmtoffset;
      resInterval = (Interval *) palloc(sizeof(Interval));
      tm2interval(&tm, 0, resInterval);
      values[1] = IntervalPGetDatum(resInterval);

!     values[2] = BoolGetDatum(is_dst);

      (*pindex)++;

diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 11007c6..410bf47 100644
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
*************** timestamptz_in(PG_FUNCTION_ARGS)
*** 486,491 ****
--- 486,494 ----
  /*
   * Try to parse a timezone specification, and return its timezone offset value
   * if it's acceptable.  Otherwise, an error is thrown.
+  *
+  * Note: some code paths update tm->tm_isdst, and some don't; current callers
+  * don't care, so we don't bother being consistent.
   */
  static int
  parse_sane_timezone(struct pg_tm * tm, text *zone)
*************** parse_sane_timezone(struct pg_tm * tm, t
*** 499,510 ****
      /*
       * Look up the requested timezone.  First we try to interpret it as a
       * numeric timezone specification; if DecodeTimezone decides it doesn't
!      * like the format, we look in the date token table (to handle cases like
!      * "EST"), and if that also fails, we look in the timezone database (to
!      * handle cases like "America/New_York").  (This matches the order in
!      * which timestamp input checks the cases; it's important because the
!      * timezone database unwisely uses a few zone names that are identical to
!      * offset abbreviations.)
       *
       * Note pg_tzset happily parses numeric input that DecodeTimezone would
       * reject.  To avoid having it accept input that would otherwise be seen
--- 502,513 ----
      /*
       * Look up the requested timezone.  First we try to interpret it as a
       * numeric timezone specification; if DecodeTimezone decides it doesn't
!      * like the format, we look in the timezone abbreviation table (to handle
!      * cases like "EST"), and if that also fails, we look in the timezone
!      * database (to handle cases like "America/New_York").  (This matches the
!      * order in which timestamp input checks the cases; it's important because
!      * the timezone database unwisely uses a few zone names that are identical
!      * to offset abbreviations.)
       *
       * Note pg_tzset happily parses numeric input that DecodeTimezone would
       * reject.  To avoid having it accept input that would otherwise be seen
*************** parse_sane_timezone(struct pg_tm * tm, t
*** 524,529 ****
--- 527,533 ----
          char       *lowzone;
          int            type,
                      val;
+         pg_tz       *tzp;

          if (rt == DTERR_TZDISP_OVERFLOW)
              ereport(ERROR,
*************** parse_sane_timezone(struct pg_tm * tm, t
*** 534,552 ****
                      (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                       errmsg("time zone \"%s\" not recognized", tzname)));

          lowzone = downcase_truncate_identifier(tzname,
                                                 strlen(tzname),
                                                 false);
!         type = DecodeSpecial(0, lowzone, &val);

          if (type == TZ || type == DTZ)
!             tz = val * MINS_PER_HOUR;
          else
          {
!             pg_tz       *tzp;
!
              tzp = pg_tzset(tzname);
-
              if (tzp)
                  tz = DetermineTimeZoneOffset(tm, tzp);
              else
--- 538,563 ----
                      (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                       errmsg("time zone \"%s\" not recognized", tzname)));

+         /* DecodeTimezoneAbbrev requires lowercase input */
          lowzone = downcase_truncate_identifier(tzname,
                                                 strlen(tzname),
                                                 false);
!         type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);

          if (type == TZ || type == DTZ)
!         {
!             /* fixed-offset abbreviation */
!             tz = -val;
!         }
!         else if (type == DYNTZ)
!         {
!             /* dynamic-offset abbreviation, resolve using specified time */
!             tz = DetermineTimeZoneAbbrevOffset(tm, tzname, tzp);
!         }
          else
          {
!             /* try it as a full zone name */
              tzp = pg_tzset(tzname);
              if (tzp)
                  tz = DetermineTimeZoneOffset(tm, tzp);
              else
*************** timestamp_zone(PG_FUNCTION_ARGS)
*** 4883,4921 ****
      int            type,
                  val;
      pg_tz       *tzp;

      if (TIMESTAMP_NOT_FINITE(timestamp))
          PG_RETURN_TIMESTAMPTZ(timestamp);

      /*
!      * Look up the requested timezone.  First we look in the date token table
!      * (to handle cases like "EST"), and if that fails, we look in the
!      * timezone database (to handle cases like "America/New_York").  (This
!      * matches the order in which timestamp input checks the cases; it's
!      * important because the timezone database unwisely uses a few zone names
!      * that are identical to offset abbreviations.)
       */
      text_to_cstring_buffer(zone, tzname, sizeof(tzname));
      lowzone = downcase_truncate_identifier(tzname,
                                             strlen(tzname),
                                             false);

!     type = DecodeSpecial(0, lowzone, &val);

      if (type == TZ || type == DTZ)
      {
!         tz = -(val * MINS_PER_HOUR);
          result = dt2local(timestamp, tz);
      }
      else
      {
          tzp = pg_tzset(tzname);
          if (tzp)
          {
              /* Apply the timezone change */
-             struct pg_tm tm;
-             fsec_t        fsec;
-
              if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0)
                  ereport(ERROR,
                          (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
--- 4894,4945 ----
      int            type,
                  val;
      pg_tz       *tzp;
+     struct pg_tm tm;
+     fsec_t        fsec;

      if (TIMESTAMP_NOT_FINITE(timestamp))
          PG_RETURN_TIMESTAMPTZ(timestamp);

      /*
!      * Look up the requested timezone.  First we look in the timezone
!      * abbreviation table (to handle cases like "EST"), and if that fails, we
!      * look in the timezone database (to handle cases like
!      * "America/New_York").  (This matches the order in which timestamp input
!      * checks the cases; it's important because the timezone database unwisely
!      * uses a few zone names that are identical to offset abbreviations.)
       */
      text_to_cstring_buffer(zone, tzname, sizeof(tzname));
+
+     /* DecodeTimezoneAbbrev requires lowercase input */
      lowzone = downcase_truncate_identifier(tzname,
                                             strlen(tzname),
                                             false);

!     type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);

      if (type == TZ || type == DTZ)
      {
!         /* fixed-offset abbreviation */
!         tz = val;
!         result = dt2local(timestamp, tz);
!     }
!     else if (type == DYNTZ)
!     {
!         /* dynamic-offset abbreviation, resolve using specified time */
!         if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0)
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
!                      errmsg("timestamp out of range")));
!         tz = -DetermineTimeZoneAbbrevOffset(&tm, tzname, tzp);
          result = dt2local(timestamp, tz);
      }
      else
      {
+         /* try it as a full zone name */
          tzp = pg_tzset(tzname);
          if (tzp)
          {
              /* Apply the timezone change */
              if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, tzp) != 0)
                  ereport(ERROR,
                          (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
*************** timestamptz_zone(PG_FUNCTION_ARGS)
*** 5061,5087 ****
          PG_RETURN_TIMESTAMP(timestamp);

      /*
!      * Look up the requested timezone.  First we look in the date token table
!      * (to handle cases like "EST"), and if that fails, we look in the
!      * timezone database (to handle cases like "America/New_York").  (This
!      * matches the order in which timestamp input checks the cases; it's
!      * important because the timezone database unwisely uses a few zone names
!      * that are identical to offset abbreviations.)
       */
      text_to_cstring_buffer(zone, tzname, sizeof(tzname));
      lowzone = downcase_truncate_identifier(tzname,
                                             strlen(tzname),
                                             false);

!     type = DecodeSpecial(0, lowzone, &val);

      if (type == TZ || type == DTZ)
      {
!         tz = val * MINS_PER_HOUR;
          result = dt2local(timestamp, tz);
      }
      else
      {
          tzp = pg_tzset(tzname);
          if (tzp)
          {
--- 5085,5123 ----
          PG_RETURN_TIMESTAMP(timestamp);

      /*
!      * Look up the requested timezone.  First we look in the timezone
!      * abbreviation table (to handle cases like "EST"), and if that fails, we
!      * look in the timezone database (to handle cases like
!      * "America/New_York").  (This matches the order in which timestamp input
!      * checks the cases; it's important because the timezone database unwisely
!      * uses a few zone names that are identical to offset abbreviations.)
       */
      text_to_cstring_buffer(zone, tzname, sizeof(tzname));
+
+     /* DecodeTimezoneAbbrev requires lowercase input */
      lowzone = downcase_truncate_identifier(tzname,
                                             strlen(tzname),
                                             false);

!     type = DecodeTimezoneAbbrev(0, lowzone, &val, &tzp);

      if (type == TZ || type == DTZ)
      {
!         /* fixed-offset abbreviation */
!         tz = -val;
!         result = dt2local(timestamp, tz);
!     }
!     else if (type == DYNTZ)
!     {
!         /* dynamic-offset abbreviation, resolve using specified time */
!         int            isdst;
!
!         tz = DetermineTimeZoneAbbrevOffsetTS(timestamp, tzname, tzp, &isdst);
          result = dt2local(timestamp, tz);
      }
      else
      {
+         /* try it as a full zone name */
          tzp = pg_tzset(tzname);
          if (tzp)
          {
diff --git a/src/backend/utils/misc/tzparser.c b/src/backend/utils/misc/tzparser.c
index 6a5a7b3..a6a12ff 100644
*** a/src/backend/utils/misc/tzparser.c
--- b/src/backend/utils/misc/tzparser.c
*************** validateTzEntry(tzEntry *tzentry)
*** 63,75 ****
                           tzentry->filename, tzentry->lineno);
          return false;
      }
-     if (tzentry->offset % 900 != 0)
-     {
-         GUC_check_errmsg("time zone offset %d is not a multiple of 900 sec (15 min) in time zone file \"%s\", line
%d",
-                          tzentry->offset,
-                          tzentry->filename, tzentry->lineno);
-         return false;
-     }

      /*
       * Sanity-check the offset: shouldn't exceed 14 hours
--- 63,68 ----
*************** validateTzEntry(tzEntry *tzentry)
*** 93,99 ****
  }

  /*
!  * Attempt to parse the line as a timezone abbrev spec (name, offset, dst)
   *
   * Returns TRUE if OK, else false; data is stored in *tzentry
   */
--- 86,96 ----
  }

  /*
!  * Attempt to parse the line as a timezone abbrev spec
!  *
!  * Valid formats are:
!  *    name  zone
!  *    name  offset  dst
   *
   * Returns TRUE if OK, else false; data is stored in *tzentry
   */
*************** splitTzLine(const char *filename, int li
*** 116,122 ****
                           filename, lineno);
          return false;
      }
!     tzentry->abbrev = abbrev;

      offset = strtok(NULL, WHITESPACE);
      if (!offset)
--- 113,119 ----
                           filename, lineno);
          return false;
      }
!     tzentry->abbrev = pstrdup(abbrev);

      offset = strtok(NULL, WHITESPACE);
      if (!offset)
*************** splitTzLine(const char *filename, int li
*** 125,149 ****
                           filename, lineno);
          return false;
      }
-     tzentry->offset = strtol(offset, &offset_endptr, 10);
-     if (offset_endptr == offset || *offset_endptr != '\0')
-     {
-         GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d",
-                          filename, lineno);
-         return false;
-     }

!     is_dst = strtok(NULL, WHITESPACE);
!     if (is_dst && pg_strcasecmp(is_dst, "D") == 0)
      {
!         tzentry->is_dst = true;
!         remain = strtok(NULL, WHITESPACE);
      }
      else
      {
!         /* there was no 'D' dst specifier */
          tzentry->is_dst = false;
!         remain = is_dst;
      }

      if (!remain)                /* no more non-whitespace chars */
--- 122,164 ----
                           filename, lineno);
          return false;
      }

!     /* We assume zone names don't begin with a digit or sign */
!     if (isdigit((unsigned char) *offset) || *offset == '+' || *offset == '-')
      {
!         tzentry->zone = NULL;
!         tzentry->offset = strtol(offset, &offset_endptr, 10);
!         if (offset_endptr == offset || *offset_endptr != '\0')
!         {
!             GUC_check_errmsg("invalid number for time zone offset in time zone file \"%s\", line %d",
!                              filename, lineno);
!             return false;
!         }
!
!         is_dst = strtok(NULL, WHITESPACE);
!         if (is_dst && pg_strcasecmp(is_dst, "D") == 0)
!         {
!             tzentry->is_dst = true;
!             remain = strtok(NULL, WHITESPACE);
!         }
!         else
!         {
!             /* there was no 'D' dst specifier */
!             tzentry->is_dst = false;
!             remain = is_dst;
!         }
      }
      else
      {
!         /*
!          * Assume entry is a zone name.  We do not try to validate it by
!          * looking up the zone, because that would force loading of a lot of
!          * zones that probably will never be used in the current session.
!          */
!         tzentry->zone = pstrdup(offset);
!         tzentry->offset = 0;
          tzentry->is_dst = false;
!         remain = strtok(NULL, WHITESPACE);
      }

      if (!remain)                /* no more non-whitespace chars */
*************** addToArray(tzEntry **base, int *arraysiz
*** 201,208 ****
              /*
               * Found a duplicate entry; complain unless it's the same.
               */
!             if (midptr->offset == entry->offset &&
!                 midptr->is_dst == entry->is_dst)
              {
                  /* return unchanged array */
                  return n;
--- 216,226 ----
              /*
               * Found a duplicate entry; complain unless it's the same.
               */
!             if ((midptr->zone == NULL && entry->zone == NULL &&
!                  midptr->offset == entry->offset &&
!                  midptr->is_dst == entry->is_dst) ||
!                 (midptr->zone != NULL && entry->zone != NULL &&
!                  strcmp(midptr->zone, entry->zone) == 0))
              {
                  /* return unchanged array */
                  return n;
*************** addToArray(tzEntry **base, int *arraysiz
*** 210,215 ****
--- 228,234 ----
              if (override)
              {
                  /* same abbrev but something is different, override */
+                 midptr->zone = entry->zone;
                  midptr->offset = entry->offset;
                  midptr->is_dst = entry->is_dst;
                  return n;
*************** addToArray(tzEntry **base, int *arraysiz
*** 239,247 ****

      memcpy(arrayptr, entry, sizeof(tzEntry));

-     /* Must dup the abbrev to ensure it survives */
-     arrayptr->abbrev = pstrdup(entry->abbrev);
-
      return n + 1;
  }

--- 258,263 ----
*************** load_tzoffsets(const char *filename)
*** 446,460 ****
      /* Parse the file(s) */
      n = ParseTzFile(filename, 0, &array, &arraysize, 0);

!     /* If no errors so far, allocate result and let datetime.c convert data */
      if (n >= 0)
      {
!         result = malloc(offsetof(TimeZoneAbbrevTable, abbrevs) +
!                         n * sizeof(datetkn));
          if (!result)
              GUC_check_errmsg("out of memory");
-         else
-             ConvertTimeZoneAbbrevs(result, array, n);
      }

      /* Clean up */
--- 462,473 ----
      /* Parse the file(s) */
      n = ParseTzFile(filename, 0, &array, &arraysize, 0);

!     /* If no errors so far, let datetime.c allocate memory & convert format */
      if (n >= 0)
      {
!         result = ConvertTimeZoneAbbrevs(array, n);
          if (!result)
              GUC_check_errmsg("out of memory");
      }

      /* Clean up */
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index b3c867a..a85bc27 100644
*** a/src/include/pgtime.h
--- b/src/include/pgtime.h
*************** extern int pg_next_dst_boundary(const pg
*** 54,66 ****
                       long int *after_gmtoff,
                       int *after_isdst,
                       const pg_tz *tz);
! extern size_t pg_strftime(char *s, size_t max, const char *format,
!             const struct pg_tm * tm);
!
  extern bool pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff);
  extern const char *pg_get_timezone_name(pg_tz *tz);
  extern bool pg_tz_acceptable(pg_tz *tz);

  /* these functions and variables are in pgtz.c */

  extern pg_tz *session_timezone;
--- 54,73 ----
                       long int *after_gmtoff,
                       int *after_isdst,
                       const pg_tz *tz);
! extern bool pg_interpret_timezone_abbrev(const char *abbrev,
!                              const pg_time_t *timep,
!                              long int *gmtoff,
!                              int *isdst,
!                              const pg_tz *tz);
  extern bool pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff);
  extern const char *pg_get_timezone_name(pg_tz *tz);
  extern bool pg_tz_acceptable(pg_tz *tz);

+ /* these functions are in strftime.c */
+
+ extern size_t pg_strftime(char *s, size_t max, const char *format,
+             const struct pg_tm * tm);
+
  /* these functions and variables are in pgtz.c */

  extern pg_tz *session_timezone;
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 2e69503..9b53ee3 100644
*** a/src/include/utils/datetime.h
--- b/src/include/utils/datetime.h
*************** struct tzEntry;
*** 77,83 ****
  #define BC        1

  /*
!  * Fields for time decoding.
   *
   * Can't have more of these than there are bits in an unsigned int
   * since these are turned into bit masks during parsing and decoding.
--- 77,83 ----
  #define BC        1

  /*
!  * Field types for time decoding.
   *
   * Can't have more of these than there are bits in an unsigned int
   * since these are turned into bit masks during parsing and decoding.
*************** struct tzEntry;
*** 93,101 ****
  #define YEAR    2
  #define DAY        3
  #define JULIAN    4
! #define TZ        5
! #define DTZ        6
! #define DTZMOD    7
  #define IGNORE_DTF    8
  #define AMPM    9
  #define HOUR    10
--- 93,101 ----
  #define YEAR    2
  #define DAY        3
  #define JULIAN    4
! #define TZ        5                /* fixed-offset timezone abbreviation */
! #define DTZ        6                /* fixed-offset timezone abbrev, DST */
! #define DYNTZ    7                /* dynamic timezone abbreviation */
  #define IGNORE_DTF    8
  #define AMPM    9
  #define HOUR    10
*************** struct tzEntry;
*** 119,136 ****
  #define DECADE        25
  #define CENTURY        26
  #define MILLENNIUM    27
  /* reserved for unrecognized string values */
  #define UNKNOWN_FIELD    31

  /*
   * Token field definitions for time parsing and decoding.
!  * These need to fit into the datetkn table type.
!  * At the moment, that means keep them within [-127,127].
!  * These are also used for bit masks in DecodeDateDelta()
   *    so actually restrict them to within [0,31] for now.
   * - thomas 97/06/19
!  * Not all of these fields are used for masks in DecodeDateDelta
   *    so allow some larger than 31. - thomas 1997-11-17
   */

  #define DTK_NUMBER        0
--- 119,142 ----
  #define DECADE        25
  #define CENTURY        26
  #define MILLENNIUM    27
+ /* hack for parsing two-word timezone specs "MET DST" etc */
+ #define DTZMOD    28                /* "DST" as a separate word */
  /* reserved for unrecognized string values */
  #define UNKNOWN_FIELD    31

  /*
   * Token field definitions for time parsing and decoding.
!  *
!  * Some field type codes (see above) use these as the "value" in datetktbl[].
!  * These are also used for bit masks in DecodeDateTime and friends
   *    so actually restrict them to within [0,31] for now.
   * - thomas 97/06/19
!  * Not all of these fields are used for masks in DecodeDateTime
   *    so allow some larger than 31. - thomas 1997-11-17
+  *
+  * Caution: there are undocumented assumptions in the code that most of these
+  * values are not equal to IGNORE_DTF nor RESERV.  Be very careful when
+  * renumbering values in either of these apparently-independent lists :-(
   */

  #define DTK_NUMBER        0
*************** struct tzEntry;
*** 203,220 ****
  /* keep this struct small; it gets used a lot */
  typedef struct
  {
!     char        token[TOKMAXLEN];
!     char        type;
!     char        value;            /* this may be unsigned, alas */
  } datetkn;

  /* one of its uses is in tables of time zone abbreviations */
  typedef struct TimeZoneAbbrevTable
  {
!     int            numabbrevs;
      datetkn        abbrevs[1];        /* VARIABLE LENGTH ARRAY */
  } TimeZoneAbbrevTable;


  /* FMODULO()
   * Macro to replace modf(), which is broken on some platforms.
--- 209,235 ----
  /* keep this struct small; it gets used a lot */
  typedef struct
  {
!     char        token[TOKMAXLEN + 1];    /* always NUL-terminated */
!     char        type;            /* see field type codes above */
!     int32        value;            /* meaning depends on type */
  } datetkn;

  /* one of its uses is in tables of time zone abbreviations */
  typedef struct TimeZoneAbbrevTable
  {
!     Size        tblsize;        /* size in bytes of TimeZoneAbbrevTable */
!     int            numabbrevs;        /* number of entries in abbrevs[] array */
      datetkn        abbrevs[1];        /* VARIABLE LENGTH ARRAY */
+     /* DynamicZoneAbbrev(s) may follow the abbrevs[] array */
  } TimeZoneAbbrevTable;

+ /* auxiliary data for a dynamic time zone abbreviation (non-fixed-offset) */
+ typedef struct DynamicZoneAbbrev
+ {
+     pg_tz       *tz;                /* NULL if not yet looked up */
+     char        zone[1];        /* zone name (var length, NUL-terminated) */
+ } DynamicZoneAbbrev;
+

  /* FMODULO()
   * Macro to replace modf(), which is broken on some platforms.
*************** extern void DateTimeParseError(int dterr
*** 296,301 ****
--- 311,319 ----
                     const char *datatype) __attribute__((noreturn));

  extern int    DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp);
+ extern int    DetermineTimeZoneAbbrevOffset(struct pg_tm * tm, const char *abbr, pg_tz *tzp);
+ extern int DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
+                                 pg_tz *tzp, int *isdst);

  extern void EncodeDateOnly(struct pg_tm * tm, int style, char *str);
  extern void EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
*************** extern void EncodeInterval(struct pg_tm
*** 305,310 ****
--- 323,330 ----
  extern int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
               struct pg_tm * tm);

+ extern int DecodeTimezoneAbbrev(int field, char *lowtoken,
+                      int *offset, pg_tz **tz);
  extern int    DecodeSpecial(int field, char *lowtoken, int *val);
  extern int    DecodeUnits(int field, char *lowtoken, int *val);

*************** extern Node *TemporalTransform(int32 max
*** 314,321 ****

  extern bool CheckDateTokenTables(void);

! extern void ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl,
!                        struct tzEntry *abbrevs, int n);
  extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);

  extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS);
--- 334,341 ----

  extern bool CheckDateTokenTables(void);

! extern TimeZoneAbbrevTable *ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs,
!                        int n);
  extern void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl);

  extern Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/tzparser.h b/src/include/utils/tzparser.h
index 82728a6..637f449 100644
*** a/src/include/utils/tzparser.h
--- b/src/include/utils/tzparser.h
***************
*** 22,31 ****
   */
  typedef struct tzEntry
  {
!     /* the actual data: TZ abbrev (downcased), offset, DST flag */
!     char       *abbrev;
!     int            offset;            /* in seconds from UTC */
!     bool        is_dst;
      /* source information (for error messages) */
      int            lineno;
      const char *filename;
--- 22,33 ----
   */
  typedef struct tzEntry
  {
!     /* the actual data */
!     char       *abbrev;            /* TZ abbreviation (downcased) */
!     char       *zone;            /* zone name if dynamic abbrev, else NULL */
!     /* for a dynamic abbreviation, offset/is_dst are not used */
!     int            offset;            /* offset in seconds from UTC */
!     bool        is_dst;            /* true if a DST abbreviation */
      /* source information (for error messages) */
      int            lineno;
      const char *filename;
diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h
index c2635c7..145e2b7 100644
*** a/src/interfaces/ecpg/pgtypeslib/dt.h
--- b/src/interfaces/ecpg/pgtypeslib/dt.h
*************** typedef double fsec_t;
*** 42,47 ****
--- 42,48 ----


  #define DAGO            "ago"
+ #define DCURRENT        "current"
  #define EPOCH            "epoch"
  #define INVALID            "invalid"
  #define EARLY            "-infinity"
*************** typedef double fsec_t;
*** 68,74 ****
  #define DA_D            "ad"
  #define DB_C            "bc"
  #define DTIMEZONE        "timezone"
- #define DCURRENT           "current"

  /*
   * Fundamental time field definitions for parsing.
--- 69,74 ----
*************** typedef double fsec_t;
*** 85,91 ****
  #define BC        1

  /*
!  * Fields for time decoding.
   *
   * Can't have more of these than there are bits in an unsigned int
   * since these are turned into bit masks during parsing and decoding.
--- 85,91 ----
  #define BC        1

  /*
!  * Field types for time decoding.
   *
   * Can't have more of these than there are bits in an unsigned int
   * since these are turned into bit masks during parsing and decoding.
*************** typedef double fsec_t;
*** 103,111 ****
  #define YEAR    2
  #define DAY        3
  #define JULIAN    4
! #define TZ        5
! #define DTZ        6
! #define DTZMOD    7
  #define IGNORE_DTF    8
  #define AMPM    9
  #define HOUR    10
--- 103,111 ----
  #define YEAR    2
  #define DAY        3
  #define JULIAN    4
! #define TZ        5                /* fixed-offset timezone abbreviation */
! #define DTZ        6                /* fixed-offset timezone abbrev, DST */
! #define DYNTZ    7                /* dynamic timezone abbr (unimplemented) */
  #define IGNORE_DTF    8
  #define AMPM    9
  #define HOUR    10
*************** typedef double fsec_t;
*** 124,142 ****
  /* generic fields to help with parsing */
  #define ISODATE 22
  #define ISOTIME 23
  /* reserved for unrecognized string values */
  #define UNKNOWN_FIELD    31


  /*
   * Token field definitions for time parsing and decoding.
!  * These need to fit into the datetkn table type.
!  * At the moment, that means keep them within [-127,127].
!  * These are also used for bit masks in DecodeDateDelta()
   *    so actually restrict them to within [0,31] for now.
   * - thomas 97/06/19
!  * Not all of these fields are used for masks in DecodeDateDelta
   *    so allow some larger than 31. - thomas 1997-11-17
   */

  #define DTK_NUMBER        0
--- 124,148 ----
  /* generic fields to help with parsing */
  #define ISODATE 22
  #define ISOTIME 23
+ /* hack for parsing two-word timezone specs "MET DST" etc */
+ #define DTZMOD    28                /* "DST" as a separate word */
  /* reserved for unrecognized string values */
  #define UNKNOWN_FIELD    31


  /*
   * Token field definitions for time parsing and decoding.
!  *
!  * Some field type codes (see above) use these as the "value" in datetktbl[].
!  * These are also used for bit masks in DecodeDateTime and friends
   *    so actually restrict them to within [0,31] for now.
   * - thomas 97/06/19
!  * Not all of these fields are used for masks in DecodeDateTime
   *    so allow some larger than 31. - thomas 1997-11-17
+  *
+  * Caution: there are undocumented assumptions in the code that most of these
+  * values are not equal to IGNORE_DTF nor RESERV.  Be very careful when
+  * renumbering values in either of these apparently-independent lists :-(
   */

  #define DTK_NUMBER        0
*************** typedef double fsec_t;
*** 207,219 ****
  /* keep this struct small; it gets used a lot */
  typedef struct
  {
! #if defined(_AIX)
!     char       *token;
! #else
!     char        token[TOKMAXLEN];
! #endif   /* _AIX */
!     char        type;
!     char        value;            /* this may be unsigned, alas */
  } datetkn;


--- 213,221 ----
  /* keep this struct small; it gets used a lot */
  typedef struct
  {
!     char        token[TOKMAXLEN + 1];    /* always NUL-terminated */
!     char        type;            /* see field type codes above */
!     int32        value;            /* meaning depends on type */
  } datetkn;


diff --git a/src/interfaces/ecpg/pgtypeslib/dt_common.c b/src/interfaces/ecpg/pgtypeslib/dt_common.c
index 2286acd..7fdd09b 100644
*** a/src/interfaces/ecpg/pgtypeslib/dt_common.c
--- b/src/interfaces/ecpg/pgtypeslib/dt_common.c
*************** int            day_tab[2][13] = {
*** 16,53 ****

  typedef long AbsoluteTime;

- #define ABS_SIGNBIT                ((char) 0200)
- #define POS(n)                    (n)
- #define NEG(n)                    ((n)|ABS_SIGNBIT)
- #define FROMVAL(tp)                (-SIGNEDCHAR((tp)->value) * 15) /* uncompress */
- #define VALMASK                    ((char) 0177)
- #define SIGNEDCHAR(c)    ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c))
-
  static datetkn datetktbl[] = {
  /*    text, token, lexval */
      {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
!     {"acsst", DTZ, POS(42)},    /* Cent. Australia */
!     {"acst", DTZ, NEG(16)},        /* Atlantic/Porto Acre */
!     {"act", TZ, NEG(20)},        /* Atlantic/Porto Acre */
      {DA_D, ADBC, AD},            /* "ad" for years >= 0 */
!     {"adt", DTZ, NEG(12)},        /* Atlantic Daylight Time */
!     {"aesst", DTZ, POS(44)},    /* E. Australia */
!     {"aest", TZ, POS(40)},        /* Australia Eastern Std Time */
!     {"aft", TZ, POS(18)},        /* Kabul */
!     {"ahst", TZ, NEG(40)},        /* Alaska-Hawaii Std Time */
!     {"akdt", DTZ, NEG(32)},        /* Alaska Daylight Time */
!     {"akst", DTZ, NEG(36)},        /* Alaska Standard Time */
      {"allballs", RESERV, DTK_ZULU},        /* 00:00:00 */
!     {"almst", TZ, POS(28)},        /* Almaty Savings Time */
!     {"almt", TZ, POS(24)},        /* Almaty Time */
      {"am", AMPM, AM},
!     {"amst", DTZ, POS(20)},        /* Armenia Summer Time (Yerevan) */
  #if 0
!     {"amst", DTZ, NEG(12)},        /* Porto Velho */
  #endif
!     {"amt", TZ, POS(16)},        /* Armenia Time (Yerevan) */
!     {"anast", DTZ, POS(52)},    /* Anadyr Summer Time (Russia) */
!     {"anat", TZ, POS(48)},        /* Anadyr Time (Russia) */
      {"apr", MONTH, 4},
      {"april", MONTH, 4},
  #if 0
--- 16,46 ----

  typedef long AbsoluteTime;

  static datetkn datetktbl[] = {
  /*    text, token, lexval */
      {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
!     {"acsst", DTZ, 37800},        /* Cent. Australia */
!     {"acst", DTZ, -14400},        /* Atlantic/Porto Acre */
!     {"act", TZ, -18000},        /* Atlantic/Porto Acre */
      {DA_D, ADBC, AD},            /* "ad" for years >= 0 */
!     {"adt", DTZ, -10800},        /* Atlantic Daylight Time */
!     {"aesst", DTZ, 39600},        /* E. Australia */
!     {"aest", TZ, 36000},        /* Australia Eastern Std Time */
!     {"aft", TZ, 16200},            /* Kabul */
!     {"ahst", TZ, -36000},        /* Alaska-Hawaii Std Time */
!     {"akdt", DTZ, -28800},        /* Alaska Daylight Time */
!     {"akst", DTZ, -32400},        /* Alaska Standard Time */
      {"allballs", RESERV, DTK_ZULU},        /* 00:00:00 */
!     {"almst", TZ, 25200},        /* Almaty Savings Time */
!     {"almt", TZ, 21600},        /* Almaty Time */
      {"am", AMPM, AM},
!     {"amst", DTZ, 18000},        /* Armenia Summer Time (Yerevan) */
  #if 0
!     {"amst", DTZ, -10800},        /* Porto Velho */
  #endif
!     {"amt", TZ, 14400},            /* Armenia Time (Yerevan) */
!     {"anast", DTZ, 46800},        /* Anadyr Summer Time (Russia) */
!     {"anat", TZ, 43200},        /* Anadyr Time (Russia) */
      {"apr", MONTH, 4},
      {"april", MONTH, 4},
  #if 0
*************** static datetkn datetktbl[] = {
*** 55,430 ****
      aqtt
      arst
  #endif
!     {"art", TZ, NEG(12)},        /* Argentina Time */
  #if 0
      ashst
      ast                            /* Atlantic Standard Time, Arabia Standard
                                   * Time, Acre Standard Time */
  #endif
!     {"ast", TZ, NEG(16)},        /* Atlantic Std Time (Canada) */
      {"at", IGNORE_DTF, 0},        /* "at" (throwaway) */
      {"aug", MONTH, 8},
      {"august", MONTH, 8},
!     {"awsst", DTZ, POS(36)},    /* W. Australia */
!     {"awst", TZ, POS(32)},        /* W. Australia */
!     {"awt", DTZ, NEG(12)},
!     {"azost", DTZ, POS(0)},        /* Azores Summer Time */
!     {"azot", TZ, NEG(4)},        /* Azores Time */
!     {"azst", DTZ, POS(20)},        /* Azerbaijan Summer Time */
!     {"azt", TZ, POS(16)},        /* Azerbaijan Time */
      {DB_C, ADBC, BC},            /* "bc" for years < 0 */
!     {"bdst", TZ, POS(8)},        /* British Double Summer Time */
!     {"bdt", TZ, POS(24)},        /* Dacca */
!     {"bnt", TZ, POS(32)},        /* Brunei Darussalam Time */
!     {"bort", TZ, POS(32)},        /* Borneo Time (Indonesia) */
  #if 0
      bortst
      bost
  #endif
!     {"bot", TZ, NEG(16)},        /* Bolivia Time */
!     {"bra", TZ, NEG(12)},        /* Brazil Time */
  #if 0
      brst
      brt
  #endif
!     {"bst", DTZ, POS(4)},        /* British Summer Time */
  #if 0
!     {"bst", TZ, NEG(12)},        /* Brazil Standard Time */
!     {"bst", DTZ, NEG(44)},        /* Bering Summer Time */
  #endif
!     {"bt", TZ, POS(12)},        /* Baghdad Time */
!     {"btt", TZ, POS(24)},        /* Bhutan Time */
!     {"cadt", DTZ, POS(42)},        /* Central Australian DST */
!     {"cast", TZ, POS(38)},        /* Central Australian ST */
!     {"cat", TZ, NEG(40)},        /* Central Alaska Time */
!     {"cct", TZ, POS(32)},        /* China Coast Time */
  #if 0
!     {"cct", TZ, POS(26)},        /* Indian Cocos (Island) Time */
  #endif
!     {"cdt", DTZ, NEG(20)},        /* Central Daylight Time */
!     {"cest", DTZ, POS(8)},        /* Central European Dayl.Time */
!     {"cet", TZ, POS(4)},        /* Central European Time */
!     {"cetdst", DTZ, POS(8)},    /* Central European Dayl.Time */
!     {"chadt", DTZ, POS(55)},    /* Chatham Island Daylight Time (13:45) */
!     {"chast", TZ, POS(51)},        /* Chatham Island Time (12:45) */
  #if 0
      ckhst
  #endif
!     {"ckt", TZ, POS(48)},        /* Cook Islands Time */
!     {"clst", DTZ, NEG(12)},        /* Chile Summer Time */
!     {"clt", TZ, NEG(16)},        /* Chile Time */
  #if 0
      cost
  #endif
!     {"cot", TZ, NEG(20)},        /* Columbia Time */
!     {"cst", TZ, NEG(24)},        /* Central Standard Time */
      {DCURRENT, RESERV, DTK_CURRENT},    /* "current" is always now */
  #if 0
      cvst
  #endif
!     {"cvt", TZ, POS(28)},        /* Christmas Island Time (Indian Ocean) */
!     {"cxt", TZ, POS(28)},        /* Christmas Island Time (Indian Ocean) */
      {"d", UNITS, DTK_DAY},        /* "day of month" for ISO input */
!     {"davt", TZ, POS(28)},        /* Davis Time (Antarctica) */
!     {"ddut", TZ, POS(40)},        /* Dumont-d'Urville Time (Antarctica) */
      {"dec", MONTH, 12},
      {"december", MONTH, 12},
!     {"dnt", TZ, POS(4)},        /* Dansk Normal Tid */
      {"dow", RESERV, DTK_DOW},    /* day of week */
      {"doy", RESERV, DTK_DOY},    /* day of year */
!     {"dst", DTZMOD, 6},
  #if 0
!     {"dusst", DTZ, POS(24)},    /* Dushanbe Summer Time */
  #endif
!     {"easst", DTZ, NEG(20)},    /* Easter Island Summer Time */
!     {"east", TZ, NEG(24)},        /* Easter Island Time */
!     {"eat", TZ, POS(12)},        /* East Africa Time */
  #if 0
!     {"east", DTZ, POS(16)},        /* Indian Antananarivo Savings Time */
!     {"eat", TZ, POS(12)},        /* Indian Antananarivo Time */
!     {"ect", TZ, NEG(16)},        /* Eastern Caribbean Time */
!     {"ect", TZ, NEG(20)},        /* Ecuador Time */
  #endif
!     {"edt", DTZ, NEG(16)},        /* Eastern Daylight Time */
!     {"eest", DTZ, POS(12)},        /* Eastern Europe Summer Time */
!     {"eet", TZ, POS(8)},        /* East. Europe, USSR Zone 1 */
!     {"eetdst", DTZ, POS(12)},    /* Eastern Europe Daylight Time */
!     {"egst", DTZ, POS(0)},        /* East Greenland Summer Time */
!     {"egt", TZ, NEG(4)},        /* East Greenland Time */
  #if 0
      ehdt
  #endif
      {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
!     {"est", TZ, NEG(20)},        /* Eastern Standard Time */
      {"feb", MONTH, 2},
      {"february", MONTH, 2},
!     {"fjst", DTZ, NEG(52)},        /* Fiji Summer Time (13 hour offset!) */
!     {"fjt", TZ, NEG(48)},        /* Fiji Time */
!     {"fkst", DTZ, NEG(12)},        /* Falkland Islands Summer Time */
!     {"fkt", TZ, NEG(8)},        /* Falkland Islands Time */
  #if 0
      fnst
      fnt
  #endif
      {"fri", DOW, 5},
      {"friday", DOW, 5},
!     {"fst", TZ, POS(4)},        /* French Summer Time */
!     {"fwt", DTZ, POS(8)},        /* French Winter Time  */
!     {"galt", TZ, NEG(24)},        /* Galapagos Time */
!     {"gamt", TZ, NEG(36)},        /* Gambier Time */
!     {"gest", DTZ, POS(20)},        /* Georgia Summer Time */
!     {"get", TZ, POS(16)},        /* Georgia Time */
!     {"gft", TZ, NEG(12)},        /* French Guiana Time */
  #if 0
      ghst
  #endif
!     {"gilt", TZ, POS(48)},        /* Gilbert Islands Time */
!     {"gmt", TZ, POS(0)},        /* Greenwish Mean Time */
!     {"gst", TZ, POS(40)},        /* Guam Std Time, USSR Zone 9 */
!     {"gyt", TZ, NEG(16)},        /* Guyana Time */
      {"h", UNITS, DTK_HOUR},        /* "hour" */
  #if 0
      hadt
      hast
  #endif
!     {"hdt", DTZ, NEG(36)},        /* Hawaii/Alaska Daylight Time */
  #if 0
      hkst
  #endif
!     {"hkt", TZ, POS(32)},        /* Hong Kong Time */
  #if 0
!     {"hmt", TZ, POS(12)},        /* Hellas ? ? */
      hovst
      hovt
  #endif
!     {"hst", TZ, NEG(40)},        /* Hawaii Std Time */
  #if 0
      hwt
  #endif
!     {"ict", TZ, POS(28)},        /* Indochina Time */
!     {"idle", TZ, POS(48)},        /* Intl. Date Line, East */
!     {"idlw", TZ, NEG(48)},        /* Intl. Date Line, West */
  #if 0
      idt                            /* Israeli, Iran, Indian Daylight Time */
  #endif
      {LATE, RESERV, DTK_LATE},    /* "infinity" reserved for "late time" */
      {INVALID, RESERV, DTK_INVALID},        /* "invalid" reserved for bad time */
!     {"iot", TZ, POS(20)},        /* Indian Chagos Time */
!     {"irkst", DTZ, POS(36)},    /* Irkutsk Summer Time */
!     {"irkt", TZ, POS(32)},        /* Irkutsk Time */
!     {"irt", TZ, POS(14)},        /* Iran Time */
      {"isodow", RESERV, DTK_ISODOW},        /* ISO day of week, Sunday == 7 */
  #if 0
      isst
  #endif
!     {"ist", TZ, POS(8)},        /* Israel */
!     {"it", TZ, POS(14)},        /* Iran Time */
      {"j", UNITS, DTK_JULIAN},
      {"jan", MONTH, 1},
      {"january", MONTH, 1},
!     {"javt", TZ, POS(28)},        /* Java Time (07:00? see JT) */
!     {"jayt", TZ, POS(36)},        /* Jayapura Time (Indonesia) */
      {"jd", UNITS, DTK_JULIAN},
!     {"jst", TZ, POS(36)},        /* Japan Std Time,USSR Zone 8 */
!     {"jt", TZ, POS(30)},        /* Java Time (07:30? see JAVT) */
      {"jul", MONTH, 7},
      {"julian", UNITS, DTK_JULIAN},
      {"july", MONTH, 7},
      {"jun", MONTH, 6},
      {"june", MONTH, 6},
!     {"kdt", DTZ, POS(40)},        /* Korea Daylight Time */
!     {"kgst", DTZ, POS(24)},        /* Kyrgyzstan Summer Time */
!     {"kgt", TZ, POS(20)},        /* Kyrgyzstan Time */
!     {"kost", TZ, POS(48)},        /* Kosrae Time */
!     {"krast", DTZ, POS(28)},    /* Krasnoyarsk Summer Time */
!     {"krat", TZ, POS(32)},        /* Krasnoyarsk Standard Time */
!     {"kst", TZ, POS(36)},        /* Korea Standard Time */
!     {"lhdt", DTZ, POS(44)},        /* Lord Howe Daylight Time, Australia */
!     {"lhst", TZ, POS(42)},        /* Lord Howe Standard Time, Australia */
!     {"ligt", TZ, POS(40)},        /* From Melbourne, Australia */
!     {"lint", TZ, POS(56)},        /* Line Islands Time (Kiribati; +14 hours!) */
!     {"lkt", TZ, POS(24)},        /* Lanka Time */
      {"m", UNITS, DTK_MONTH},    /* "month" for ISO input */
!     {"magst", DTZ, POS(48)},    /* Magadan Summer Time */
!     {"magt", TZ, POS(44)},        /* Magadan Time */
      {"mar", MONTH, 3},
      {"march", MONTH, 3},
!     {"mart", TZ, NEG(38)},        /* Marquesas Time */
!     {"mawt", TZ, POS(24)},        /* Mawson, Antarctica */
      {"may", MONTH, 5},
!     {"mdt", DTZ, NEG(24)},        /* Mountain Daylight Time */
!     {"mest", DTZ, POS(8)},        /* Middle Europe Summer Time */
!     {"met", TZ, POS(4)},        /* Middle Europe Time */
!     {"metdst", DTZ, POS(8)},    /* Middle Europe Daylight Time */
!     {"mewt", TZ, POS(4)},        /* Middle Europe Winter Time */
!     {"mez", TZ, POS(4)},        /* Middle Europe Zone */
!     {"mht", TZ, POS(48)},        /* Kwajalein */
      {"mm", UNITS, DTK_MINUTE},    /* "minute" for ISO input */
!     {"mmt", TZ, POS(26)},        /* Myannar Time */
      {"mon", DOW, 1},
      {"monday", DOW, 1},
  #if 0
      most
  #endif
!     {"mpt", TZ, POS(40)},        /* North Mariana Islands Time */
!     {"msd", DTZ, POS(16)},        /* Moscow Summer Time */
!     {"msk", TZ, POS(12)},        /* Moscow Time */
!     {"mst", TZ, NEG(28)},        /* Mountain Standard Time */
!     {"mt", TZ, POS(34)},        /* Moluccas Time */
!     {"mut", TZ, POS(16)},        /* Mauritius Island Time */
!     {"mvt", TZ, POS(20)},        /* Maldives Island Time */
!     {"myt", TZ, POS(32)},        /* Malaysia Time */
  #if 0
      ncst
  #endif
!     {"nct", TZ, POS(44)},        /* New Caledonia Time */
!     {"ndt", DTZ, NEG(10)},        /* Nfld. Daylight Time */
!     {"nft", TZ, NEG(14)},        /* Newfoundland Standard Time */
!     {"nor", TZ, POS(4)},        /* Norway Standard Time */
      {"nov", MONTH, 11},
      {"november", MONTH, 11},
!     {"novst", DTZ, POS(28)},    /* Novosibirsk Summer Time */
!     {"novt", TZ, POS(24)},        /* Novosibirsk Standard Time */
      {NOW, RESERV, DTK_NOW},        /* current transaction time */
!     {"npt", TZ, POS(23)},        /* Nepal Standard Time (GMT-5:45) */
!     {"nst", TZ, NEG(14)},        /* Nfld. Standard Time */
!     {"nt", TZ, NEG(44)},        /* Nome Time */
!     {"nut", TZ, NEG(44)},        /* Niue Time */
!     {"nzdt", DTZ, POS(52)},        /* New Zealand Daylight Time */
!     {"nzst", TZ, POS(48)},        /* New Zealand Standard Time */
!     {"nzt", TZ, POS(48)},        /* New Zealand Time */
      {"oct", MONTH, 10},
      {"october", MONTH, 10},
!     {"omsst", DTZ, POS(28)},    /* Omsk Summer Time */
!     {"omst", TZ, POS(24)},        /* Omsk Time */
      {"on", IGNORE_DTF, 0},        /* "on" (throwaway) */
!     {"pdt", DTZ, NEG(28)},        /* Pacific Daylight Time */
  #if 0
      pest
  #endif
!     {"pet", TZ, NEG(20)},        /* Peru Time */
!     {"petst", DTZ, POS(52)},    /* Petropavlovsk-Kamchatski Summer Time */
!     {"pett", TZ, POS(48)},        /* Petropavlovsk-Kamchatski Time */
!     {"pgt", TZ, POS(40)},        /* Papua New Guinea Time */
!     {"phot", TZ, POS(52)},        /* Phoenix Islands (Kiribati) Time */
  #if 0
      phst
  #endif
!     {"pht", TZ, POS(32)},        /* Philippine Time */
!     {"pkt", TZ, POS(20)},        /* Pakistan Time */
      {"pm", AMPM, PM},
!     {"pmdt", DTZ, NEG(8)},        /* Pierre & Miquelon Daylight Time */
  #if 0
      pmst
  #endif
!     {"pont", TZ, POS(44)},        /* Ponape Time (Micronesia) */
!     {"pst", TZ, NEG(32)},        /* Pacific Standard Time */
!     {"pwt", TZ, POS(36)},        /* Palau Time */
!     {"pyst", DTZ, NEG(12)},        /* Paraguay Summer Time */
!     {"pyt", TZ, NEG(16)},        /* Paraguay Time */
!     {"ret", DTZ, POS(16)},        /* Reunion Island Time */
      {"s", UNITS, DTK_SECOND},    /* "seconds" for ISO input */
!     {"sadt", DTZ, POS(42)},        /* S. Australian Dayl. Time */
  #if 0
      samst
      samt
  #endif
!     {"sast", TZ, POS(38)},        /* South Australian Std Time */
      {"sat", DOW, 6},
      {"saturday", DOW, 6},
  #if 0
      sbt
  #endif
!     {"sct", DTZ, POS(16)},        /* Mahe Island Time */
      {"sep", MONTH, 9},
      {"sept", MONTH, 9},
      {"september", MONTH, 9},
!     {"set", TZ, NEG(4)},        /* Seychelles Time ?? */
  #if 0
      sgt
  #endif
!     {"sst", DTZ, POS(8)},        /* Swedish Summer Time */
      {"sun", DOW, 0},
      {"sunday", DOW, 0},
!     {"swt", TZ, POS(4)},        /* Swedish Winter Time */
  #if 0
      syot
  #endif
      {"t", ISOTIME, DTK_TIME},    /* Filler for ISO time fields */
!     {"tft", TZ, POS(20)},        /* Kerguelen Time */
!     {"that", TZ, NEG(40)},        /* Tahiti Time */
      {"thu", DOW, 4},
      {"thur", DOW, 4},
      {"thurs", DOW, 4},
      {"thursday", DOW, 4},
!     {"tjt", TZ, POS(20)},        /* Tajikistan Time */
!     {"tkt", TZ, NEG(40)},        /* Tokelau Time */
!     {"tmt", TZ, POS(20)},        /* Turkmenistan Time */
      {TODAY, RESERV, DTK_TODAY}, /* midnight */
      {TOMORROW, RESERV, DTK_TOMORROW},    /* tomorrow midnight */
  #if 0
      tost
  #endif
!     {"tot", TZ, POS(52)},        /* Tonga Time */
  #if 0
      tpt
  #endif
!     {"truk", TZ, POS(40)},        /* Truk Time */
      {"tue", DOW, 2},
      {"tues", DOW, 2},
      {"tuesday", DOW, 2},
!     {"tvt", TZ, POS(48)},        /* Tuvalu Time */
  #if 0
      uct
  #endif
!     {"ulast", DTZ, POS(36)},    /* Ulan Bator Summer Time */
!     {"ulat", TZ, POS(32)},        /* Ulan Bator Time */
      {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
!     {"ut", TZ, POS(0)},
!     {"utc", TZ, POS(0)},
!     {"uyst", DTZ, NEG(8)},        /* Uruguay Summer Time */
!     {"uyt", TZ, NEG(12)},        /* Uruguay Time */
!     {"uzst", DTZ, POS(24)},        /* Uzbekistan Summer Time */
!     {"uzt", TZ, POS(20)},        /* Uzbekistan Time */
!     {"vet", TZ, NEG(16)},        /* Venezuela Time */
!     {"vlast", DTZ, POS(44)},    /* Vladivostok Summer Time */
!     {"vlat", TZ, POS(40)},        /* Vladivostok Time */
  #if 0
      vust
  #endif
!     {"vut", TZ, POS(44)},        /* Vanuata Time */
!     {"wadt", DTZ, POS(32)},        /* West Australian DST */
!     {"wakt", TZ, POS(48)},        /* Wake Time */
  #if 0
      warst
  #endif
!     {"wast", TZ, POS(28)},        /* West Australian Std Time */
!     {"wat", TZ, NEG(4)},        /* West Africa Time */
!     {"wdt", DTZ, POS(36)},        /* West Australian DST */
      {"wed", DOW, 3},
      {"wednesday", DOW, 3},
      {"weds", DOW, 3},
!     {"west", DTZ, POS(4)},        /* Western Europe Summer Time */
!     {"wet", TZ, POS(0)},        /* Western Europe */
!     {"wetdst", DTZ, POS(4)},    /* Western Europe Daylight Savings Time */
!     {"wft", TZ, POS(48)},        /* Wallis and Futuna Time */
!     {"wgst", DTZ, NEG(8)},        /* West Greenland Summer Time */
!     {"wgt", TZ, NEG(12)},        /* West Greenland Time */
!     {"wst", TZ, POS(32)},        /* West Australian Standard Time */
      {"y", UNITS, DTK_YEAR},        /* "year" for ISO input */
!     {"yakst", DTZ, POS(40)},    /* Yakutsk Summer Time */
!     {"yakt", TZ, POS(36)},        /* Yakutsk Time */
!     {"yapt", TZ, POS(40)},        /* Yap Time (Micronesia) */
!     {"ydt", DTZ, NEG(32)},        /* Yukon Daylight Time */
!     {"yekst", DTZ, POS(24)},    /* Yekaterinburg Summer Time */
!     {"yekt", TZ, POS(20)},        /* Yekaterinburg Time */
      {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */
!     {"yst", TZ, NEG(36)},        /* Yukon Standard Time */
!     {"z", TZ, POS(0)},            /* time zone tag per ISO-8601 */
!     {"zp4", TZ, NEG(16)},        /* UTC +4  hours. */
!     {"zp5", TZ, NEG(20)},        /* UTC +5  hours. */
!     {"zp6", TZ, NEG(24)},        /* UTC +6  hours. */
!     {ZULU, TZ, POS(0)},            /* UTC */
  };

  static datetkn deltatktbl[] = {
--- 48,423 ----
      aqtt
      arst
  #endif
!     {"art", TZ, -10800},        /* Argentina Time */
  #if 0
      ashst
      ast                            /* Atlantic Standard Time, Arabia Standard
                                   * Time, Acre Standard Time */
  #endif
!     {"ast", TZ, -14400},        /* Atlantic Std Time (Canada) */
      {"at", IGNORE_DTF, 0},        /* "at" (throwaway) */
      {"aug", MONTH, 8},
      {"august", MONTH, 8},
!     {"awsst", DTZ, 32400},        /* W. Australia */
!     {"awst", TZ, 28800},        /* W. Australia */
!     {"awt", DTZ, -10800},
!     {"azost", DTZ, 0},            /* Azores Summer Time */
!     {"azot", TZ, -3600},        /* Azores Time */
!     {"azst", DTZ, 18000},        /* Azerbaijan Summer Time */
!     {"azt", TZ, 14400},            /* Azerbaijan Time */
      {DB_C, ADBC, BC},            /* "bc" for years < 0 */
!     {"bdst", TZ, 7200},            /* British Double Summer Time */
!     {"bdt", TZ, 21600},            /* Dacca */
!     {"bnt", TZ, 28800},            /* Brunei Darussalam Time */
!     {"bort", TZ, 28800},        /* Borneo Time (Indonesia) */
  #if 0
      bortst
      bost
  #endif
!     {"bot", TZ, -14400},        /* Bolivia Time */
!     {"bra", TZ, -10800},        /* Brazil Time */
  #if 0
      brst
      brt
  #endif
!     {"bst", DTZ, 3600},            /* British Summer Time */
  #if 0
!     {"bst", TZ, -10800},        /* Brazil Standard Time */
!     {"bst", DTZ, -39600},        /* Bering Summer Time */
  #endif
!     {"bt", TZ, 10800},            /* Baghdad Time */
!     {"btt", TZ, 21600},            /* Bhutan Time */
!     {"cadt", DTZ, 37800},        /* Central Australian DST */
!     {"cast", TZ, 34200},        /* Central Australian ST */
!     {"cat", TZ, -36000},        /* Central Alaska Time */
!     {"cct", TZ, 28800},            /* China Coast Time */
  #if 0
!     {"cct", TZ, 23400},            /* Indian Cocos (Island) Time */
  #endif
!     {"cdt", DTZ, -18000},        /* Central Daylight Time */
!     {"cest", DTZ, 7200},        /* Central European Dayl.Time */
!     {"cet", TZ, 3600},            /* Central European Time */
!     {"cetdst", DTZ, 7200},        /* Central European Dayl.Time */
!     {"chadt", DTZ, 49500},        /* Chatham Island Daylight Time (13:45) */
!     {"chast", TZ, 45900},        /* Chatham Island Time (12:45) */
  #if 0
      ckhst
  #endif
!     {"ckt", TZ, 43200},            /* Cook Islands Time */
!     {"clst", DTZ, -10800},        /* Chile Summer Time */
!     {"clt", TZ, -14400},        /* Chile Time */
  #if 0
      cost
  #endif
!     {"cot", TZ, -18000},        /* Columbia Time */
!     {"cst", TZ, -21600},        /* Central Standard Time */
      {DCURRENT, RESERV, DTK_CURRENT},    /* "current" is always now */
  #if 0
      cvst
  #endif
!     {"cvt", TZ, 25200},            /* Christmas Island Time (Indian Ocean) */
!     {"cxt", TZ, 25200},            /* Christmas Island Time (Indian Ocean) */
      {"d", UNITS, DTK_DAY},        /* "day of month" for ISO input */
!     {"davt", TZ, 25200},        /* Davis Time (Antarctica) */
!     {"ddut", TZ, 36000},        /* Dumont-d'Urville Time (Antarctica) */
      {"dec", MONTH, 12},
      {"december", MONTH, 12},
!     {"dnt", TZ, 3600},            /* Dansk Normal Tid */
      {"dow", RESERV, DTK_DOW},    /* day of week */
      {"doy", RESERV, DTK_DOY},    /* day of year */
!     {"dst", DTZMOD, SECS_PER_HOUR},
  #if 0
!     {"dusst", DTZ, 21600},        /* Dushanbe Summer Time */
  #endif
!     {"easst", DTZ, -18000},        /* Easter Island Summer Time */
!     {"east", TZ, -21600},        /* Easter Island Time */
!     {"eat", TZ, 10800},            /* East Africa Time */
  #if 0
!     {"east", DTZ, 14400},        /* Indian Antananarivo Savings Time */
!     {"eat", TZ, 10800},            /* Indian Antananarivo Time */
!     {"ect", TZ, -14400},        /* Eastern Caribbean Time */
!     {"ect", TZ, -18000},        /* Ecuador Time */
  #endif
!     {"edt", DTZ, -14400},        /* Eastern Daylight Time */
!     {"eest", DTZ, 10800},        /* Eastern Europe Summer Time */
!     {"eet", TZ, 7200},            /* East. Europe, USSR Zone 1 */
!     {"eetdst", DTZ, 10800},        /* Eastern Europe Daylight Time */
!     {"egst", DTZ, 0},            /* East Greenland Summer Time */
!     {"egt", TZ, -3600},            /* East Greenland Time */
  #if 0
      ehdt
  #endif
      {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
!     {"est", TZ, -18000},        /* Eastern Standard Time */
      {"feb", MONTH, 2},
      {"february", MONTH, 2},
!     {"fjst", DTZ, -46800},        /* Fiji Summer Time (13 hour offset!) */
!     {"fjt", TZ, -43200},        /* Fiji Time */
!     {"fkst", DTZ, -10800},        /* Falkland Islands Summer Time */
!     {"fkt", TZ, -7200},            /* Falkland Islands Time */
  #if 0
      fnst
      fnt
  #endif
      {"fri", DOW, 5},
      {"friday", DOW, 5},
!     {"fst", TZ, 3600},            /* French Summer Time */
!     {"fwt", DTZ, 7200},            /* French Winter Time  */
!     {"galt", TZ, -21600},        /* Galapagos Time */
!     {"gamt", TZ, -32400},        /* Gambier Time */
!     {"gest", DTZ, 18000},        /* Georgia Summer Time */
!     {"get", TZ, 14400},            /* Georgia Time */
!     {"gft", TZ, -10800},        /* French Guiana Time */
  #if 0
      ghst
  #endif
!     {"gilt", TZ, 43200},        /* Gilbert Islands Time */
!     {"gmt", TZ, 0},                /* Greenwish Mean Time */
!     {"gst", TZ, 36000},            /* Guam Std Time, USSR Zone 9 */
!     {"gyt", TZ, -14400},        /* Guyana Time */
      {"h", UNITS, DTK_HOUR},        /* "hour" */
  #if 0
      hadt
      hast
  #endif
!     {"hdt", DTZ, -32400},        /* Hawaii/Alaska Daylight Time */
  #if 0
      hkst
  #endif
!     {"hkt", TZ, 28800},            /* Hong Kong Time */
  #if 0
!     {"hmt", TZ, 10800},            /* Hellas ? ? */
      hovst
      hovt
  #endif
!     {"hst", TZ, -36000},        /* Hawaii Std Time */
  #if 0
      hwt
  #endif
!     {"ict", TZ, 25200},            /* Indochina Time */
!     {"idle", TZ, 43200},        /* Intl. Date Line, East */
!     {"idlw", TZ, -43200},        /* Intl. Date Line, West */
  #if 0
      idt                            /* Israeli, Iran, Indian Daylight Time */
  #endif
      {LATE, RESERV, DTK_LATE},    /* "infinity" reserved for "late time" */
      {INVALID, RESERV, DTK_INVALID},        /* "invalid" reserved for bad time */
!     {"iot", TZ, 18000},            /* Indian Chagos Time */
!     {"irkst", DTZ, 32400},        /* Irkutsk Summer Time */
!     {"irkt", TZ, 28800},        /* Irkutsk Time */
!     {"irt", TZ, 12600},            /* Iran Time */
      {"isodow", RESERV, DTK_ISODOW},        /* ISO day of week, Sunday == 7 */
  #if 0
      isst
  #endif
!     {"ist", TZ, 7200},            /* Israel */
!     {"it", TZ, 12600},            /* Iran Time */
      {"j", UNITS, DTK_JULIAN},
      {"jan", MONTH, 1},
      {"january", MONTH, 1},
!     {"javt", TZ, 25200},        /* Java Time (07:00? see JT) */
!     {"jayt", TZ, 32400},        /* Jayapura Time (Indonesia) */
      {"jd", UNITS, DTK_JULIAN},
!     {"jst", TZ, 32400},            /* Japan Std Time,USSR Zone 8 */
!     {"jt", TZ, 27000},            /* Java Time (07:30? see JAVT) */
      {"jul", MONTH, 7},
      {"julian", UNITS, DTK_JULIAN},
      {"july", MONTH, 7},
      {"jun", MONTH, 6},
      {"june", MONTH, 6},
!     {"kdt", DTZ, 36000},        /* Korea Daylight Time */
!     {"kgst", DTZ, 21600},        /* Kyrgyzstan Summer Time */
!     {"kgt", TZ, 18000},            /* Kyrgyzstan Time */
!     {"kost", TZ, 43200},        /* Kosrae Time */
!     {"krast", DTZ, 25200},        /* Krasnoyarsk Summer Time */
!     {"krat", TZ, 28800},        /* Krasnoyarsk Standard Time */
!     {"kst", TZ, 32400},            /* Korea Standard Time */
!     {"lhdt", DTZ, 39600},        /* Lord Howe Daylight Time, Australia */
!     {"lhst", TZ, 37800},        /* Lord Howe Standard Time, Australia */
!     {"ligt", TZ, 36000},        /* From Melbourne, Australia */
!     {"lint", TZ, 50400},        /* Line Islands Time (Kiribati; +14 hours!) */
!     {"lkt", TZ, 21600},            /* Lanka Time */
      {"m", UNITS, DTK_MONTH},    /* "month" for ISO input */
!     {"magst", DTZ, 43200},        /* Magadan Summer Time */
!     {"magt", TZ, 39600},        /* Magadan Time */
      {"mar", MONTH, 3},
      {"march", MONTH, 3},
!     {"mart", TZ, -34200},        /* Marquesas Time */
!     {"mawt", TZ, 21600},        /* Mawson, Antarctica */
      {"may", MONTH, 5},
!     {"mdt", DTZ, -21600},        /* Mountain Daylight Time */
!     {"mest", DTZ, 7200},        /* Middle Europe Summer Time */
!     {"met", TZ, 3600},            /* Middle Europe Time */
!     {"metdst", DTZ, 7200},        /* Middle Europe Daylight Time */
!     {"mewt", TZ, 3600},            /* Middle Europe Winter Time */
!     {"mez", TZ, 3600},            /* Middle Europe Zone */
!     {"mht", TZ, 43200},            /* Kwajalein */
      {"mm", UNITS, DTK_MINUTE},    /* "minute" for ISO input */
!     {"mmt", TZ, 23400},            /* Myannar Time */
      {"mon", DOW, 1},
      {"monday", DOW, 1},
  #if 0
      most
  #endif
!     {"mpt", TZ, 36000},            /* North Mariana Islands Time */
!     {"msd", DTZ, 14400},        /* Moscow Summer Time */
!     {"msk", TZ, 10800},            /* Moscow Time */
!     {"mst", TZ, -25200},        /* Mountain Standard Time */
!     {"mt", TZ, 30600},            /* Moluccas Time */
!     {"mut", TZ, 14400},            /* Mauritius Island Time */
!     {"mvt", TZ, 18000},            /* Maldives Island Time */
!     {"myt", TZ, 28800},            /* Malaysia Time */
  #if 0
      ncst
  #endif
!     {"nct", TZ, 39600},            /* New Caledonia Time */
!     {"ndt", DTZ, -9000},        /* Nfld. Daylight Time */
!     {"nft", TZ, -12600},        /* Newfoundland Standard Time */
!     {"nor", TZ, 3600},            /* Norway Standard Time */
      {"nov", MONTH, 11},
      {"november", MONTH, 11},
!     {"novst", DTZ, 25200},        /* Novosibirsk Summer Time */
!     {"novt", TZ, 21600},        /* Novosibirsk Standard Time */
      {NOW, RESERV, DTK_NOW},        /* current transaction time */
!     {"npt", TZ, 20700},            /* Nepal Standard Time (GMT-5:45) */
!     {"nst", TZ, -12600},        /* Nfld. Standard Time */
!     {"nt", TZ, -39600},            /* Nome Time */
!     {"nut", TZ, -39600},        /* Niue Time */
!     {"nzdt", DTZ, 46800},        /* New Zealand Daylight Time */
!     {"nzst", TZ, 43200},        /* New Zealand Standard Time */
!     {"nzt", TZ, 43200},            /* New Zealand Time */
      {"oct", MONTH, 10},
      {"october", MONTH, 10},
!     {"omsst", DTZ, 25200},        /* Omsk Summer Time */
!     {"omst", TZ, 21600},        /* Omsk Time */
      {"on", IGNORE_DTF, 0},        /* "on" (throwaway) */
!     {"pdt", DTZ, -25200},        /* Pacific Daylight Time */
  #if 0
      pest
  #endif
!     {"pet", TZ, -18000},        /* Peru Time */
!     {"petst", DTZ, 46800},        /* Petropavlovsk-Kamchatski Summer Time */
!     {"pett", TZ, 43200},        /* Petropavlovsk-Kamchatski Time */
!     {"pgt", TZ, 36000},            /* Papua New Guinea Time */
!     {"phot", TZ, 46800},        /* Phoenix Islands (Kiribati) Time */
  #if 0
      phst
  #endif
!     {"pht", TZ, 28800},            /* Philippine Time */
!     {"pkt", TZ, 18000},            /* Pakistan Time */
      {"pm", AMPM, PM},
!     {"pmdt", DTZ, -7200},        /* Pierre & Miquelon Daylight Time */
  #if 0
      pmst
  #endif
!     {"pont", TZ, 39600},        /* Ponape Time (Micronesia) */
!     {"pst", TZ, -28800},        /* Pacific Standard Time */
!     {"pwt", TZ, 32400},            /* Palau Time */
!     {"pyst", DTZ, -10800},        /* Paraguay Summer Time */
!     {"pyt", TZ, -14400},        /* Paraguay Time */
!     {"ret", DTZ, 14400},        /* Reunion Island Time */
      {"s", UNITS, DTK_SECOND},    /* "seconds" for ISO input */
!     {"sadt", DTZ, 37800},        /* S. Australian Dayl. Time */
  #if 0
      samst
      samt
  #endif
!     {"sast", TZ, 34200},        /* South Australian Std Time */
      {"sat", DOW, 6},
      {"saturday", DOW, 6},
  #if 0
      sbt
  #endif
!     {"sct", DTZ, 14400},        /* Mahe Island Time */
      {"sep", MONTH, 9},
      {"sept", MONTH, 9},
      {"september", MONTH, 9},
!     {"set", TZ, -3600},            /* Seychelles Time ?? */
  #if 0
      sgt
  #endif
!     {"sst", DTZ, 7200},            /* Swedish Summer Time */
      {"sun", DOW, 0},
      {"sunday", DOW, 0},
!     {"swt", TZ, 3600},            /* Swedish Winter Time */
  #if 0
      syot
  #endif
      {"t", ISOTIME, DTK_TIME},    /* Filler for ISO time fields */
!     {"tft", TZ, 18000},            /* Kerguelen Time */
!     {"that", TZ, -36000},        /* Tahiti Time */
      {"thu", DOW, 4},
      {"thur", DOW, 4},
      {"thurs", DOW, 4},
      {"thursday", DOW, 4},
!     {"tjt", TZ, 18000},            /* Tajikistan Time */
!     {"tkt", TZ, -36000},        /* Tokelau Time */
!     {"tmt", TZ, 18000},            /* Turkmenistan Time */
      {TODAY, RESERV, DTK_TODAY}, /* midnight */
      {TOMORROW, RESERV, DTK_TOMORROW},    /* tomorrow midnight */
  #if 0
      tost
  #endif
!     {"tot", TZ, 46800},            /* Tonga Time */
  #if 0
      tpt
  #endif
!     {"truk", TZ, 36000},        /* Truk Time */
      {"tue", DOW, 2},
      {"tues", DOW, 2},
      {"tuesday", DOW, 2},
!     {"tvt", TZ, 43200},            /* Tuvalu Time */
  #if 0
      uct
  #endif
!     {"ulast", DTZ, 32400},        /* Ulan Bator Summer Time */
!     {"ulat", TZ, 28800},        /* Ulan Bator Time */
      {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
!     {"ut", TZ, 0},
!     {"utc", TZ, 0},
!     {"uyst", DTZ, -7200},        /* Uruguay Summer Time */
!     {"uyt", TZ, -10800},        /* Uruguay Time */
!     {"uzst", DTZ, 21600},        /* Uzbekistan Summer Time */
!     {"uzt", TZ, 18000},            /* Uzbekistan Time */
!     {"vet", TZ, -14400},        /* Venezuela Time */
!     {"vlast", DTZ, 39600},        /* Vladivostok Summer Time */
!     {"vlat", TZ, 36000},        /* Vladivostok Time */
  #if 0
      vust
  #endif
!     {"vut", TZ, 39600},            /* Vanuata Time */
!     {"wadt", DTZ, 28800},        /* West Australian DST */
!     {"wakt", TZ, 43200},        /* Wake Time */
  #if 0
      warst
  #endif
!     {"wast", TZ, 25200},        /* West Australian Std Time */
!     {"wat", TZ, -3600},            /* West Africa Time */
!     {"wdt", DTZ, 32400},        /* West Australian DST */
      {"wed", DOW, 3},
      {"wednesday", DOW, 3},
      {"weds", DOW, 3},
!     {"west", DTZ, 3600},        /* Western Europe Summer Time */
!     {"wet", TZ, 0},                /* Western Europe */
!     {"wetdst", DTZ, 3600},        /* Western Europe Daylight Savings Time */
!     {"wft", TZ, 43200},            /* Wallis and Futuna Time */
!     {"wgst", DTZ, -7200},        /* West Greenland Summer Time */
!     {"wgt", TZ, -10800},        /* West Greenland Time */
!     {"wst", TZ, 28800},            /* West Australian Standard Time */
      {"y", UNITS, DTK_YEAR},        /* "year" for ISO input */
!     {"yakst", DTZ, 36000},        /* Yakutsk Summer Time */
!     {"yakt", TZ, 32400},        /* Yakutsk Time */
!     {"yapt", TZ, 36000},        /* Yap Time (Micronesia) */
!     {"ydt", DTZ, -28800},        /* Yukon Daylight Time */
!     {"yekst", DTZ, 21600},        /* Yekaterinburg Summer Time */
!     {"yekt", TZ, 18000},        /* Yekaterinburg Time */
      {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */
!     {"yst", TZ, -32400},        /* Yukon Standard Time */
!     {"z", TZ, 0},                /* time zone tag per ISO-8601 */
!     {"zp4", TZ, -14400},        /* UTC +4  hours. */
!     {"zp5", TZ, -18000},        /* UTC +5  hours. */
!     {"zp6", TZ, -21600},        /* UTC +6  hours. */
!     {ZULU, TZ, 0},                /* UTC */
  };

  static datetkn deltatktbl[] = {
*************** datebsearch(char *key, datetkn *base, un
*** 521,529 ****
          while (last >= base)
          {
              position = base + ((last - base) >> 1);
!             result = key[0] - position->token[0];
              if (result == 0)
              {
                  result = strncmp(key, position->token, TOKMAXLEN);
                  if (result == 0)
                      return position;
--- 514,524 ----
          while (last >= base)
          {
              position = base + ((last - base) >> 1);
!             /* precheck the first character for a bit of extra speed */
!             result = (int) key[0] - (int) position->token[0];
              if (result == 0)
              {
+                 /* use strncmp so that we match truncated tokens */
                  result = strncmp(key, position->token, TOKMAXLEN);
                  if (result == 0)
                      return position;
*************** DecodeUnits(int field, char *lowtoken, i
*** 547,552 ****
--- 542,548 ----
      int            type;
      datetkn    *tp;

+     /* use strncmp so that we match truncated tokens */
      if (deltacache[field] != NULL &&
          strncmp(lowtoken, deltacache[field]->token, TOKMAXLEN) == 0)
          tp = deltacache[field];
*************** DecodeUnits(int field, char *lowtoken, i
*** 561,570 ****
      else
      {
          type = tp->type;
!         if (type == TZ || type == DTZ)
!             *val = FROMVAL(tp);
!         else
!             *val = tp->value;
      }

      return type;
--- 557,563 ----
      else
      {
          type = tp->type;
!         *val = tp->value;
      }

      return type;
*************** DecodeSpecial(int field, char *lowtoken,
*** 650,655 ****
--- 643,649 ----
      int            type;
      datetkn    *tp;

+     /* use strncmp so that we match truncated tokens */
      if (datecache[field] != NULL &&
          strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0)
          tp = datecache[field];
*************** DecodeSpecial(int field, char *lowtoken,
*** 668,685 ****
      else
      {
          type = tp->type;
!         switch (type)
!         {
!             case TZ:
!             case DTZ:
!             case DTZMOD:
!                 *val = FROMVAL(tp);
!                 break;
!
!             default:
!                 *val = tp->value;
!                 break;
!         }
      }

      return type;
--- 662,668 ----
      else
      {
          type = tp->type;
!         *val = tp->value;
      }

      return type;
*************** DecodePosixTimezone(char *str, int *tzp)
*** 1656,1662 ****
      {
          case DTZ:
          case TZ:
!             *tzp = (val * MINS_PER_HOUR) - tz;
              break;

          default:
--- 1639,1645 ----
      {
          case DTZ:
          case TZ:
!             *tzp = -(val + tz);
              break;

          default:
*************** DecodeDateTime(char **field, int *ftype,
*** 2308,2314 ****
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return -1;
!                         *tzp += val * MINS_PER_HOUR;
                          break;

                      case DTZ:
--- 2291,2297 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return -1;
!                         *tzp -= val;
                          break;

                      case DTZ:
*************** DecodeDateTime(char **field, int *ftype,
*** 2321,2327 ****
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return -1;
!                         *tzp = val * MINS_PER_HOUR;
                          ftype[i] = DTK_TZ;
                          break;

--- 2304,2310 ----
                          tm->tm_isdst = 1;
                          if (tzp == NULL)
                              return -1;
!                         *tzp = -val;
                          ftype[i] = DTK_TZ;
                          break;

*************** DecodeDateTime(char **field, int *ftype,
*** 2329,2335 ****
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return -1;
!                         *tzp = val * MINS_PER_HOUR;
                          ftype[i] = DTK_TZ;
                          break;

--- 2312,2318 ----
                          tm->tm_isdst = 0;
                          if (tzp == NULL)
                              return -1;
!                         *tzp = -val;
                          ftype[i] = DTK_TZ;
                          break;

*************** PGTYPEStimestamp_defmt_scan(char **str,
*** 3000,3024 ****
                  pfmt++;
                  scan_type = PGTYPES_TYPE_STRING_MALLOCED;
                  err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
!
!                 /*
!                  * XXX use DecodeSpecial instead ? - it's declared static but
!                  * the arrays as well. :-(
!                  */
!                 for (j = 0; !err && j < szdatetktbl; j++)
                  {
!                     if (pg_strcasecmp(datetktbl[j].token, scan_val.str_val) == 0)
                      {
!                         /*
!                          * tz calculates the offset for the seconds, the
!                          * timezone value of the datetktbl table is in quarter
!                          * hours
!                          */
!                         *tz = -15 * MINS_PER_HOUR * datetktbl[j].value;
!                         break;
                      }
                  }
-                 free(scan_val.str_val);
                  break;
              case '+':
                  /* XXX */
--- 2983,3008 ----
                  pfmt++;
                  scan_type = PGTYPES_TYPE_STRING_MALLOCED;
                  err = pgtypes_defmt_scan(&scan_val, scan_type, &pstr, pfmt);
!                 if (!err)
                  {
!                     /*
!                      * XXX use DecodeSpecial instead?  Do we need strcasecmp
!                      * here?
!                      */
!                     err = 1;
!                     for (j = 0; j < szdatetktbl; j++)
                      {
!                         if ((datetktbl[j].type == TZ || datetktbl[j].type == DTZ) &&
!                             pg_strcasecmp(datetktbl[j].token,
!                                           scan_val.str_val) == 0)
!                         {
!                             *tz = -datetktbl[j].value;
!                             err = 0;
!                             break;
!                         }
                      }
+                     free(scan_val.str_val);
                  }
                  break;
              case '+':
                  /* XXX */
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index f95617b..552f3d1 100644
*** a/src/test/regress/expected/timestamptz.out
--- b/src/test/regress/expected/timestamptz.out
*************** SELECT make_timestamptz(2014, 12, 10, 10
*** 1805,1807 ****
--- 1805,2487 ----
  (1 row)

  RESET TimeZone;
+ --
+ -- Test behavior with a dynamic (time-varying) timezone abbreviation.
+ -- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
+ -- changed meaning in Mar 2011 and back again in Oct 2014.
+ --
+ SET TimeZone to 'UTC';
+ SELECT '2011-03-27 00:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 21:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:59:59 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:01 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:59:59 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:01 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 04:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 00:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 00:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 21:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:59:59 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:01 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:59:59 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 22:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:01 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Mar 26 23:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 04:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 00:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 20:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:59:59 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 20:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 22:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:01 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 22:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:59:59 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 22:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 23:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:01 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 23:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 03:00:00 Europe/Moscow'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 00:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 20:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:59:59 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 20:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 22:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:01 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 22:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:59:59 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 22:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 23:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:01 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sat Oct 25 23:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 03:00:00 MSK'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 00:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 21:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sun Mar 27 00:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 21:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 22:59:59 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Mar 26 23:00:01 2011 UTC
+ (1 row)
+
+ SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sun Mar 27 00:00:00 2011 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 20:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 20:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 22:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 22:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 22:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 23:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sat Oct 25 23:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+            timezone
+ ------------------------------
+  Sun Oct 26 00:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 20:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 20:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 22:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 22:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 22:59:59 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 23:00:00 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sat Oct 25 23:00:01 2014 UTC
+ (1 row)
+
+ SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'MSK';
+            timezone
+ ------------------------------
+  Sun Oct 26 00:00:00 2014 UTC
+ (1 row)
+
+ SELECT make_timestamptz(2014, 10, 26, 0, 0, 0, 'MSK');
+        make_timestamptz
+ ------------------------------
+  Sat Oct 25 20:00:00 2014 UTC
+ (1 row)
+
+ SELECT make_timestamptz(2014, 10, 26, 3, 0, 0, 'MSK');
+        make_timestamptz
+ ------------------------------
+  Sun Oct 26 00:00:00 2014 UTC
+ (1 row)
+
+ SET TimeZone to 'Europe/Moscow';
+ SELECT '2011-03-26 21:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 00:00:00 2011 MSK
+ (1 row)
+
+ SELECT '2011-03-26 22:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 01:00:00 2011 MSK
+ (1 row)
+
+ SELECT '2011-03-26 22:59:59 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 01:59:59 2011 MSK
+ (1 row)
+
+ SELECT '2011-03-26 23:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 03:00:00 2011 MSK
+ (1 row)
+
+ SELECT '2011-03-26 23:00:01 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 03:00:01 2011 MSK
+ (1 row)
+
+ SELECT '2011-03-26 23:59:59 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 03:59:59 2011 MSK
+ (1 row)
+
+ SELECT '2011-03-27 00:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Mar 27 04:00:00 2011 MSK
+ (1 row)
+
+ SELECT '2014-10-25 20:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 00:00:00 2014 MSK
+ (1 row)
+
+ SELECT '2014-10-25 21:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 01:00:00 2014 MSK
+ (1 row)
+
+ SELECT '2014-10-25 21:59:59 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 01:59:59 2014 MSK
+ (1 row)
+
+ SELECT '2014-10-25 22:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 01:00:00 2014 MSK
+ (1 row)
+
+ SELECT '2014-10-25 22:00:01 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 01:00:01 2014 MSK
+ (1 row)
+
+ SELECT '2014-10-25 22:59:59 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 01:59:59 2014 MSK
+ (1 row)
+
+ SELECT '2014-10-25 23:00:00 UTC'::timestamptz;
+          timestamptz
+ ------------------------------
+  Sun Oct 26 02:00:00 2014 MSK
+ (1 row)
+
+ RESET TimeZone;
+ SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 00:00:00 2011
+ (1 row)
+
+ SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 01:00:00 2011
+ (1 row)
+
+ SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 01:59:59 2011
+ (1 row)
+
+ SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 03:00:00 2011
+ (1 row)
+
+ SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 03:00:01 2011
+ (1 row)
+
+ SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 03:59:59 2011
+ (1 row)
+
+ SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Mar 27 04:00:00 2011
+ (1 row)
+
+ SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 00:00:00 2014
+ (1 row)
+
+ SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 01:00:00 2014
+ (1 row)
+
+ SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 01:59:59 2014
+ (1 row)
+
+ SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 01:00:00 2014
+ (1 row)
+
+ SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 01:00:01 2014
+ (1 row)
+
+ SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 01:59:59 2014
+ (1 row)
+
+ SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+          timezone
+ --------------------------
+  Sun Oct 26 02:00:00 2014
+ (1 row)
+
+ SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 00:00:00 2011
+ (1 row)
+
+ SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 01:00:00 2011
+ (1 row)
+
+ SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 01:59:59 2011
+ (1 row)
+
+ SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 03:00:00 2011
+ (1 row)
+
+ SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 03:00:01 2011
+ (1 row)
+
+ SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 03:59:59 2011
+ (1 row)
+
+ SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Mar 27 04:00:00 2011
+ (1 row)
+
+ SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 00:00:00 2014
+ (1 row)
+
+ SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 01:00:00 2014
+ (1 row)
+
+ SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 01:59:59 2014
+ (1 row)
+
+ SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 01:00:00 2014
+ (1 row)
+
+ SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 01:00:01 2014
+ (1 row)
+
+ SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 01:59:59 2014
+ (1 row)
+
+ SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+          timezone
+ --------------------------
+  Sun Oct 26 02:00:00 2014
+ (1 row)
+
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index ec001f8..92b5bbc 100644
*** a/src/test/regress/sql/timestamptz.sql
--- b/src/test/regress/sql/timestamptz.sql
*************** SELECT make_timestamptz(2008, 12, 10, 10
*** 290,292 ****
--- 290,431 ----
  SELECT make_timestamptz(2014, 12, 10, 10, 10, 10, 'PST8PDT');

  RESET TimeZone;
+
+ --
+ -- Test behavior with a dynamic (time-varying) timezone abbreviation.
+ -- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
+ -- changed meaning in Mar 2011 and back again in Oct 2014.
+ --
+
+ SET TimeZone to 'UTC';
+
+ SELECT '2011-03-27 00:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 01:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 01:59:59 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 02:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 02:00:01 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 02:59:59 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 03:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 03:00:01 Europe/Moscow'::timestamptz;
+ SELECT '2011-03-27 04:00:00 Europe/Moscow'::timestamptz;
+
+ SELECT '2011-03-27 00:00:00 MSK'::timestamptz;
+ SELECT '2011-03-27 01:00:00 MSK'::timestamptz;
+ SELECT '2011-03-27 01:59:59 MSK'::timestamptz;
+ SELECT '2011-03-27 02:00:00 MSK'::timestamptz;
+ SELECT '2011-03-27 02:00:01 MSK'::timestamptz;
+ SELECT '2011-03-27 02:59:59 MSK'::timestamptz;
+ SELECT '2011-03-27 03:00:00 MSK'::timestamptz;
+ SELECT '2011-03-27 03:00:01 MSK'::timestamptz;
+ SELECT '2011-03-27 04:00:00 MSK'::timestamptz;
+
+ SELECT '2014-10-26 00:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 00:59:59 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 01:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 01:00:01 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 01:59:59 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 02:00:00 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 02:00:01 Europe/Moscow'::timestamptz;
+ SELECT '2014-10-26 03:00:00 Europe/Moscow'::timestamptz;
+
+ SELECT '2014-10-26 00:00:00 MSK'::timestamptz;
+ SELECT '2014-10-26 00:59:59 MSK'::timestamptz;
+ SELECT '2014-10-26 01:00:00 MSK'::timestamptz;
+ SELECT '2014-10-26 01:00:01 MSK'::timestamptz;
+ SELECT '2014-10-26 01:59:59 MSK'::timestamptz;
+ SELECT '2014-10-26 02:00:00 MSK'::timestamptz;
+ SELECT '2014-10-26 02:00:01 MSK'::timestamptz;
+ SELECT '2014-10-26 03:00:00 MSK'::timestamptz;
+
+ SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+
+ SELECT '2011-03-27 00:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 01:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 01:59:59'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 02:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 02:00:01'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 02:59:59'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 03:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 03:00:01'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 04:00:00'::timestamp AT TIME ZONE 'MSK';
+
+ SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'Europe/Moscow';
+
+ SELECT '2014-10-26 00:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 00:59:59'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 01:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 01:00:01'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 01:59:59'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 02:00:00'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 02:00:01'::timestamp AT TIME ZONE 'MSK';
+ SELECT '2014-10-26 03:00:00'::timestamp AT TIME ZONE 'MSK';
+
+ SELECT make_timestamptz(2014, 10, 26, 0, 0, 0, 'MSK');
+ SELECT make_timestamptz(2014, 10, 26, 3, 0, 0, 'MSK');
+
+ SET TimeZone to 'Europe/Moscow';
+
+ SELECT '2011-03-26 21:00:00 UTC'::timestamptz;
+ SELECT '2011-03-26 22:00:00 UTC'::timestamptz;
+ SELECT '2011-03-26 22:59:59 UTC'::timestamptz;
+ SELECT '2011-03-26 23:00:00 UTC'::timestamptz;
+ SELECT '2011-03-26 23:00:01 UTC'::timestamptz;
+ SELECT '2011-03-26 23:59:59 UTC'::timestamptz;
+ SELECT '2011-03-27 00:00:00 UTC'::timestamptz;
+
+ SELECT '2014-10-25 20:00:00 UTC'::timestamptz;
+ SELECT '2014-10-25 21:00:00 UTC'::timestamptz;
+ SELECT '2014-10-25 21:59:59 UTC'::timestamptz;
+ SELECT '2014-10-25 22:00:00 UTC'::timestamptz;
+ SELECT '2014-10-25 22:00:01 UTC'::timestamptz;
+ SELECT '2014-10-25 22:59:59 UTC'::timestamptz;
+ SELECT '2014-10-25 23:00:00 UTC'::timestamptz;
+
+ RESET TimeZone;
+
+ SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+
+ SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+ SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'Europe/Moscow';
+
+ SELECT '2011-03-26 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2011-03-26 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2011-03-26 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2011-03-26 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2011-03-26 23:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2011-03-26 23:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2011-03-27 00:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+
+ SELECT '2014-10-25 20:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2014-10-25 21:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2014-10-25 21:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2014-10-25 22:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2014-10-25 22:00:01 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2014-10-25 22:59:59 UTC'::timestamptz AT TIME ZONE 'MSK';
+ SELECT '2014-10-25 23:00:00 UTC'::timestamptz AT TIME ZONE 'MSK';
diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c
index 85b227c..80bb856 100644
*** a/src/timezone/localtime.c
--- b/src/timezone/localtime.c
*************** increment_overflow(int *number, int delt
*** 1292,1300 ****
  }

  /*
!  * Find the next DST transition time after the given time
   *
!  * *timep is the input value, the other parameters are output values.
   *
   * When the function result is 1, *boundary is set to the time_t
   * representation of the next DST transition time after *timep,
--- 1292,1300 ----
  }

  /*
!  * Find the next DST transition time in the given zone after the given time
   *
!  * *timep and *tz are input arguments, the other parameters are output values.
   *
   * When the function result is 1, *boundary is set to the time_t
   * representation of the next DST transition time after *timep,
*************** pg_next_dst_boundary(const pg_time_t *ti
*** 1445,1450 ****
--- 1445,1518 ----
  }

  /*
+  * Identify a timezone abbreviation's meaning in the given zone
+  *
+  * Determine the GMT offset and DST flag associated with the abbreviation.
+  * This is generally used only when the abbreviation has actually changed
+  * meaning over time; therefore, we also take a UTC cutoff time, and return
+  * the meaning in use at or most recently before that time, or the meaning
+  * in first use after that time if the abbrev was never used before that.
+  *
+  * On success, returns TRUE and sets *gmtoff and *isdst.  If the abbreviation
+  * was never used at all in this zone, returns FALSE.
+  *
+  * Note: abbrev is matched case-sensitively; it should be all-upper-case.
+  */
+ bool
+ pg_interpret_timezone_abbrev(const char *abbrev,
+                              const pg_time_t *timep,
+                              long int *gmtoff,
+                              int *isdst,
+                              const pg_tz *tz)
+ {
+     const struct state *sp;
+     const struct ttinfo *found_ttisp;
+     const struct ttinfo *ttisp;
+     int            abbrind;
+     int            i;
+     const pg_time_t t = *timep;
+
+     sp = &tz->state;
+
+     /*
+      * Locate the abbreviation in the zone's abbreviation list.  We assume
+      * there are not duplicates in the list.
+      */
+     abbrind = 0;
+     while (abbrind < sp->charcnt)
+     {
+         if (strcmp(abbrev, sp->chars + abbrind) == 0)
+             break;
+         abbrind += strlen(sp->chars + abbrind) + 1;
+     }
+     if (abbrind >= sp->charcnt)
+         return FALSE;            /* not there! */
+
+     /*
+      * Iterate through the zone's transition times to find the latest interval
+      * using the given abbrev before the cutoff time, or the first one after.
+      * We could make this a bit faster, but a great deal more complex, by
+      * doing a binary search first; not clear it's worth it.
+      */
+     found_ttisp = NULL;
+     for (i = 0; i < sp->timecnt; i++)
+     {
+         if (sp->ats[i] > t && found_ttisp != NULL)
+             break;
+         ttisp = &sp->ttis[sp->types[i]];
+         if (ttisp->tt_abbrind == abbrind)
+             found_ttisp = ttisp;
+     }
+
+     if (found_ttisp == NULL)
+         return FALSE;            /* hm, not actually used in any interval? */
+
+     *gmtoff = found_ttisp->tt_gmtoff;
+     *isdst = found_ttisp->tt_isdst;
+     return TRUE;
+ }
+
+ /*
   * If the given timezone uses only one GMT offset, store that offset
   * into *gmtoff and return TRUE, else return FALSE.
   */
diff --git a/src/timezone/tznames/Default b/src/timezone/tznames/Default
index 9e5209e..6f2a0d7 100644
*** a/src/timezone/tznames/Default
--- b/src/timezone/tznames/Default
*************** MEST     7200 D  # Middle Europe Summer
*** 638,645 ****
  MET      3600    # Middle Europe Time (not in zic)
  METDST   7200 D  # Middle Europe Summer Time (not in zic)
  MEZ      3600    # Mitteleuropaeische Zeit (German) (not in zic)
! MSD     14400 D  # Moscow Daylight Time (obsolete)
! MSK     10800    # Moscow Time (caution: this used to mean 14400)
                   #     (Europe/Moscow)
  VOLT    14400    # Volgograd Time (obsolete)
  WET         0    # Western Europe Time
--- 638,645 ----
  MET      3600    # Middle Europe Time (not in zic)
  METDST   7200 D  # Middle Europe Summer Time (not in zic)
  MEZ      3600    # Mitteleuropaeische Zeit (German) (not in zic)
! MSD     Europe/Moscow  # Moscow Daylight Time (obsolete)
! MSK     Europe/Moscow  # Moscow Time
                   #     (Europe/Moscow)
  VOLT    14400    # Volgograd Time (obsolete)
  WET         0    # Western Europe Time

Re: Proposal for better support of time-varying timezone abbreviations

From
Michael Meskes
Date:
On 15.10.2014 00:26, Tom Lane wrote:
> * I've not touched ecpg except for cosmetic changes to keep the struct
> definitions in sync, and to fix the previously-mentioned bogus free()
> attempt.  I doubt that it would be worth teaching ecpg how to access the
> zic timezone database --- the problem of configuring where to find those
> files seems like more trouble than it's worth given the lack of
> complaints.  I'm not sure what we should do about the obsolete timezone
> abbreviations in its table.

Maybe we should just remove thme for the new release. Yes, that might
break some applications, but then the server doesn't know these either,
so the applications might break anyway.

Michael
-- 
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
Jabber: michael.meskes at gmail dot com
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL



Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
Michael Meskes <meskes@postgresql.org> writes:
> On 15.10.2014 00:26, Tom Lane wrote:
>> * I've not touched ecpg except for cosmetic changes to keep the struct
>> definitions in sync, and to fix the previously-mentioned bogus free()
>> attempt.  I doubt that it would be worth teaching ecpg how to access the
>> zic timezone database --- the problem of configuring where to find those
>> files seems like more trouble than it's worth given the lack of
>> complaints.  I'm not sure what we should do about the obsolete timezone
>> abbreviations in its table.

> Maybe we should just remove thme for the new release. Yes, that might
> break some applications, but then the server doesn't know these either,
> so the applications might break anyway.

The same thought had occurred to me.  Probably the main use of the
datetime parsing code in ecpg is for interpreting outputs from the
server, and (at least by default) the server doesn't use timezone
abbreviations when printing timestamps.  So maybe that's largely
dead code anyhow.  I would not propose back-patching such a change,
but we could try it in 9.5 and see if anyone complains.

A less drastic remedy would be to remove just those abbreviations
whose meaning has actually changed over time.  Eventually that
might be all of them ... but in the meantime, we could at least
argue that we weren't breaking any case that worked well before.
        regards, tom lane



Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
... and here is a draft patch for the timezone abbreviation data files.

I changed all the abbreviations for which the parent zone had used more
than one GMT offset since 1970.  That seemed like a good cutoff to avoid
wasting cycles on ancient history, especially since the IANA people
themselves don't make any large promises about the quality of their data
before 1970.

Although zones in the Russian Federation are the majority of zones that
had abbreviation changes, a quick look at this patch shows that they're
hardly the only ones.  We've been sticking our heads in the sand about
this problem for quite a while :-(

            regards, tom lane

diff --git a/src/timezone/tznames/America.txt b/src/timezone/tznames/America.txt
index 54b51fe..9e62732 100644
*** a/src/timezone/tznames/America.txt
--- b/src/timezone/tznames/America.txt
*************** AMT    -14400    # Amazon Time
*** 47,53 ****
                   #     (America/Cuiaba)
                   #     (America/Manaus)
                   #     (America/Porto_Velho)
! ART    -10800    # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
--- 47,53 ----
                   #     (America/Cuiaba)
                   #     (America/Manaus)
                   #     (America/Porto_Velho)
! ART    America/Argentina/Buenos_Aires  # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
*************** ART    -10800    # Argentina Time
*** 58,64 ****
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    -7200 D  # Argentina Summer Time
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Arabic Standard Time (Asia)
--- 58,64 ----
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    America/Argentina/Buenos_Aires  # Argentina Summer Time
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Arabic Standard Time (Asia)
*************** GMT         0    # Greenwich Mean Time
*** 228,234 ****
                   #     (Etc/GMT)
                   #     (Europe/Dublin)
                   #     (Europe/London)
! GYT    -14400    # Guyana Time
                   #     (America/Guyana)
  HADT   -32400 D  # Hawaii-Aleutian Daylight Time
                   #     (America/Adak)
--- 228,234 ----
                   #     (Etc/GMT)
                   #     (Europe/Dublin)
                   #     (Europe/London)
! GYT    America/Guyana  # Guyana Time
                   #     (America/Guyana)
  HADT   -32400 D  # Hawaii-Aleutian Daylight Time
                   #     (America/Adak)
*************** PST    -28800    # Pacific Standard Time
*** 285,299 ****
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    -14400    # Paraguay Time
                   #     (America/Asuncion)
! SRT    -10800    # Suriname Time
                   #     (America/Paramaribo)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    -16200    # Venezuela Time (caution: this used to mean -14400)
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
--- 285,299 ----
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    America/Asuncion  # Paraguay Time
                   #     (America/Asuncion)
! SRT    America/Paramaribo  # Suriname Time
                   #     (America/Paramaribo)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    America/Caracas  # Venezuela Time
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
diff --git a/src/timezone/tznames/Antarctica.txt b/src/timezone/tznames/Antarctica.txt
index 5a03250..2359020 100644
*** a/src/timezone/tznames/Antarctica.txt
--- b/src/timezone/tznames/Antarctica.txt
*************** CLST   -10800 D  # Chile Summer Time
*** 16,26 ****
  CLT    -14400    # Chile Time
                   #     (America/Santiago)
                   #     (Antarctica/Palmer)
! DAVT    25200    # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d`Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
! MAWT    18000    # Mawson Time (Antarctica) (caution: this used to mean 21600)
                   #     (Antarctica/Mawson)
  MIST    39600    # Macquarie Island Time
                   #     (Antarctica/Macquarie)
--- 16,26 ----
  CLT    -14400    # Chile Time
                   #     (America/Santiago)
                   #     (Antarctica/Palmer)
! DAVT    Antarctica/Davis  # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d`Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
! MAWT    Antarctica/Mawson  # Mawson Time (Antarctica)
                   #     (Antarctica/Mawson)
  MIST    39600    # Macquarie Island Time
                   #     (Antarctica/Macquarie)
diff --git a/src/timezone/tznames/Asia.txt b/src/timezone/tznames/Asia.txt
index 8c3cb35..bb28646 100644
*** a/src/timezone/tznames/Asia.txt
--- b/src/timezone/tznames/Asia.txt
*************** ALMT    21600    # Alma-Ata Time
*** 15,31 ****
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    18000 D  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     14400    # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   46800 D  # Anadyr Summer Time (obsolete)
! ANAT    43200    # Anadyr Time
                   #     (Asia/Anadyr)
! AQTT    18000    # Aqtau Time (obsolete)
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Atlantic Standard Time (America)
--- 15,33 ----
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    Asia/Yerevan  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     Asia/Yerevan  # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   Asia/Anadyr  # Anadyr Summer Time (obsolete)
! ANAT    Asia/Anadyr  # Anadyr Time
                   #     (Asia/Anadyr)
! AQTST   Asia/Aqtau  # Aqtau Summer Time (obsolete)
! AQTT    Asia/Aqtau  # Aqtau Time
!                  #     (Asia/Aqtau)
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Atlantic Standard Time (America)
*************** AST     10800    # Arabia Standard Time
*** 41,49 ****
                   #     (Asia/Kuwait)
                   #     (Asia/Qatar)
                   #     (Asia/Riyadh)
! AZST    18000 D  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     14400    # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
--- 43,51 ----
                   #     (Asia/Kuwait)
                   #     (Asia/Qatar)
                   #     (Asia/Riyadh)
! AZST    Asia/Baku  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     Asia/Baku  # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
*************** BTT     21600    # Bhutan Time
*** 54,60 ****
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
  CHOST   36000 D  # Choibalsan Summer Time (obsolete)
! CHOT    28800    # Choibalsan Time (caution: this used to mean 32400)
                   #     (Asia/Choibalsan)
  CIT     28800    # Central Indonesia Time (obsolete, WITA is now preferred)
  EEST    10800 D  # East-Egypt Summer Time
--- 56,62 ----
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
  CHOST   36000 D  # Choibalsan Summer Time (obsolete)
! CHOT    Asia/Choibalsan  # Choibalsan Time
                   #     (Asia/Choibalsan)
  CIT     28800    # Central Indonesia Time (obsolete, WITA is now preferred)
  EEST    10800 D  # East-Egypt Summer Time
*************** EET      7200    # East-Egypt Time
*** 105,113 ****
                   #     (Europe/Vilnius)
                   #     (Europe/Zaporozhye)
  EIT     32400    # East Indonesia Time (obsolete, WIT is now preferred)
! GEST    14400 D  # Georgia Summer Time (obsolete)
!                  #     (Asia/Tbilisi)
! GET     14400    # Georgia Time (caution: this used to mean 10800)
                   #     (Asia/Tbilisi)
  # CONFLICT! GST is not unique
  # Other timezones:
--- 107,114 ----
                   #     (Europe/Vilnius)
                   #     (Europe/Zaporozhye)
  EIT     32400    # East Indonesia Time (obsolete, WIT is now preferred)
! GEST    Asia/Tbilisi  # Georgia Summer Time (obsolete)
! GET     Asia/Tbilisi  # Georgia Time
                   #     (Asia/Tbilisi)
  # CONFLICT! GST is not unique
  # Other timezones:
*************** GST     14400    # Gulf Standard Time
*** 117,123 ****
                   #     (Asia/Muscat)
  HKT     28800    # Hong Kong Time (not in zic)
  HOVST   28800 D  # Hovd Summer Time (obsolete)
! HOVT    25200    # Hovd Time
                   #     (Asia/Hovd)
  ICT     25200    # Indochina Time
                   #     (Asia/Bangkok)
--- 118,124 ----
                   #     (Asia/Muscat)
  HKT     28800    # Hong Kong Time (not in zic)
  HOVST   28800 D  # Hovd Summer Time (obsolete)
! HOVT    Asia/Hovd  # Hovd Time
                   #     (Asia/Hovd)
  ICT     25200    # Indochina Time
                   #     (Asia/Bangkok)
*************** ICT     25200    # Indochina Time
*** 126,137 ****
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRDT    16200 D  # Iran Daylight Time
                   #     (Asia/Tehran)
! IRKST   32400 D  # Irkutsk Summer Time (obsolete)
! IRKT    28800    # Irkutsk Time (caution: this used to mean 32400)
                   #     (Asia/Irkutsk)
! IRST    12600    # Iran Standard Time
                   #     (Asia/Tehran)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
--- 127,138 ----
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRDT    Asia/Tehran  # Iran Daylight Time
                   #     (Asia/Tehran)
! IRKST   Asia/Irkutsk  # Irkutsk Summer Time (obsolete)
! IRKT    Asia/Irkutsk  # Irkutsk Time
                   #     (Asia/Irkutsk)
! IRST    Asia/Tehran  # Iran Standard Time
                   #     (Asia/Tehran)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
*************** JST     32400    # Japan Standard Time
*** 151,185 ****
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
                   #     (Asia/Bishkek)
! KGT     21600    # Kyrgyzstan Time (caution: this used to mean 18000)
!                  #     (Asia/Bishkek)
! KRAST   28800 D  # Krasnoyarsk Summer Time (obsolete)
! KRAT    25200    # Krasnoyarsk Time (caution: this used to mean 28800)
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     21600    # Lanka Time (obsolete)
! MAGST   43200 D  # Magadan Summer Time (obsolete)
! MAGT    36000    # Magadan Time (caution: this used to mean 43200)
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   25200 D  # Novosibirsk Summer Time (obsolete)
! NOVT    21600    # Novosibirsk Time (caution: this used to mean 25200)
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   25200 D  # Omsk Summer Time (obsolete)
! OMST    21600    # Omsk Time (caution: this used to mean 25200)
                   #     (Asia/Omsk)
! ORAT    18000    # Oral Time
                   #     (Asia/Oral)
! PETST   46800 D  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    43200    # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
--- 152,185 ----
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
+ KGT     Asia/Bishkek  # Kyrgyzstan Time
                   #     (Asia/Bishkek)
! KRAST   Asia/Krasnoyarsk  # Krasnoyarsk Summer Time (obsolete)
! KRAT    Asia/Krasnoyarsk  # Krasnoyarsk Time
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     Asia/Colombo  # Lanka Time (obsolete)
! MAGST   Asia/Magadan  # Magadan Summer Time (obsolete)
! MAGT    Asia/Magadan  # Magadan Time
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   Asia/Novosibirsk  # Novosibirsk Summer Time (obsolete)
! NOVT    Asia/Novosibirsk  # Novosibirsk Time
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   Asia/Omsk  # Omsk Summer Time (obsolete)
! OMST    Asia/Omsk  # Omsk Time
                   #     (Asia/Omsk)
! ORAT    Asia/Oral  # Oral Time
                   #     (Asia/Oral)
! PETST   Asia/Kamchatka  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    Asia/Kamchatka  # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
*************** PKST    21600 D  # Pakistan Summer Time
*** 189,198 ****
                   #     (Asia/Karachi)
  QYZT    21600    # Kizilorda Time
                   #     (Asia/Qyzylorda)
! SAKST   39600 D  # Sakhalin Summer Time (obsolete)
! SAKT    36000    # Sakhalin Time (caution: this used to mean 39600)
                   #     (Asia/Sakhalin)
! SGT     28800    # Singapore Time
                   #     (Asia/Singapore)
  SRET    39600    # Srednekolymsk Time
                   #     (Asia/Srednekolymsk)
--- 189,198 ----
                   #     (Asia/Karachi)
  QYZT    21600    # Kizilorda Time
                   #     (Asia/Qyzylorda)
! SAKST   Asia/Sakhalin  # Sakhalin Summer Time (obsolete)
! SAKT    Asia/Sakhalin  # Sakhalin Time
                   #     (Asia/Sakhalin)
! SGT     Asia/Singapore  # Singapore Time
                   #     (Asia/Singapore)
  SRET    39600    # Srednekolymsk Time
                   #     (Asia/Srednekolymsk)
*************** TJT     18000    # Tajikistan Time
*** 200,209 ****
                   #     (Asia/Dushanbe)
  TLT     32400    # East Timor Time
                   #     (Asia/Dili)
! TMT     18000    # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    28800    # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
--- 200,209 ----
                   #     (Asia/Dushanbe)
  TLT     32400    # East Timor Time
                   #     (Asia/Dili)
! TMT     Asia/Ashgabat  # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    Asia/Ulaanbaatar  # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
*************** UZST    21600 D  # Uzbekistan Summer Tim
*** 211,218 ****
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   39600 D  # Vladivostok Summer Time (obsolete)
! VLAT    36000    # Vladivostok Time (caution: this used to mean 39600)
                   #     (Asia/Vladivostok)
  WIB     25200    # Waktu Indonesia Barat
                   #     (Asia/Jakarta)
--- 211,218 ----
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   Asia/Vladivostok  # Vladivostok Summer Time (obsolete)
! VLAT    Asia/Vladivostok  # Vladivostok Time
                   #     (Asia/Vladivostok)
  WIB     25200    # Waktu Indonesia Barat
                   #     (Asia/Jakarta)
*************** WITA    28800    # Waktu Indonesia Tenga
*** 223,231 ****
                   #     (Asia/Makassar)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   36000 D  # Yakutsk Summer Time (obsolete)
! YAKT    32400    # Yakutsk Time (caution: this used to mean 36000)
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    18000    # Yekaterinburg Time (caution: this used to mean 21600)
                   #     (Asia/Yekaterinburg)
--- 223,231 ----
                   #     (Asia/Makassar)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   Asia/Yakutsk  # Yakutsk Summer Time (obsolete)
! YAKT    Asia/Yakutsk  # Yakutsk Time
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    Asia/Yekaterinburg  # Yekaterinburg Time
                   #     (Asia/Yekaterinburg)
diff --git a/src/timezone/tznames/Atlantic.txt b/src/timezone/tznames/Atlantic.txt
index c65734b..1d34d1e 100644
*** a/src/timezone/tznames/Atlantic.txt
--- b/src/timezone/tznames/Atlantic.txt
*************** AZOST       0 D  # Azores Summer Time
*** 48,58 ****
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! CVT     -3600    # Cape Verde Time
                   #     (Atlantic/Cape_Verde)
! FKST   -10800    # Falkland Islands Summer Time (now used all year round)
                   #     (Atlantic/Stanley)
! FKT    -14400    # Falkland Islands Time (obsolete)
  GMT         0    # Greenwich Mean Time
                   #     (Africa/Abidjan)
                   #     (Africa/Bamako)
--- 48,58 ----
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! CVT     Atlantic/Cape_Verde  # Cape Verde Time
                   #     (Atlantic/Cape_Verde)
! FKST    Atlantic/Stanley  # Falkland Islands Summer/Standard Time
                   #     (Atlantic/Stanley)
! FKT     Atlantic/Stanley  # Falkland Islands Time (obsolete)
  GMT         0    # Greenwich Mean Time
                   #     (Africa/Abidjan)
                   #     (Africa/Bamako)
diff --git a/src/timezone/tznames/Australia.txt b/src/timezone/tznames/Australia.txt
index 8373093..92c2968 100644
*** a/src/timezone/tznames/Australia.txt
--- b/src/timezone/tznames/Australia.txt
*************** EAST    36000    # East Australian Stand
*** 52,58 ****
  # Other timezones:
  #  - EST: Eastern Standard Time (America)
  EST     36000    # Eastern Standard Time (not in zic)
! LHDT    39600 D  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
--- 52,58 ----
  # Other timezones:
  #  - EST: Eastern Standard Time (America)
  EST     36000    # Eastern Standard Time (not in zic)
! LHDT    Australia/Lord_Howe  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
diff --git a/src/timezone/tznames/Default b/src/timezone/tznames/Default
index 9e5209e..a8b8eac 100644
*** a/src/timezone/tznames/Default
--- b/src/timezone/tznames/Default
*************** AKST   -32400    # Alaska Standard Time
*** 54,60 ****
                   #     (America/Juneau)
                   #     (America/Nome)
                   #     (America/Yakutat)
! ART    -10800    # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
--- 54,60 ----
                   #     (America/Juneau)
                   #     (America/Nome)
                   #     (America/Yakutat)
! ART    America/Argentina/Buenos_Aires  # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
*************** ART    -10800    # Argentina Time
*** 65,71 ****
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    -7200 D  # Argentina Summer Time
  BOT    -14400    # Bolivia Time
                   #     (America/La_Paz)
  BRA    -10800    # Brazil Time (not in zic)
--- 65,71 ----
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    America/Argentina/Buenos_Aires  # Argentina Summer Time
  BOT    -14400    # Bolivia Time
                   #     (America/La_Paz)
  BRA    -10800    # Brazil Time (not in zic)
*************** FNST    -3600 D  # Fernando de Noronha S
*** 170,176 ****
                   #     (America/Noronha)
  GFT    -10800    # French Guiana Time
                   #     (America/Cayenne)
! GYT    -14400    # Guyana Time
                   #     (America/Guyana)
  MDT    -21600 D  # Mexico Mountain Daylight Time
                   # Mountain Daylight Time
--- 170,176 ----
                   #     (America/Noronha)
  GFT    -10800    # French Guiana Time
                   #     (America/Cayenne)
! GYT    America/Guyana  # Guyana Time
                   #     (America/Guyana)
  MDT    -21600 D  # Mexico Mountain Daylight Time
                   # Mountain Daylight Time
*************** PST    -28800    # Pacific Standard Time
*** 219,231 ****
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    -14400    # Paraguay Time
                   #     (America/Asuncion)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    -16200    # Venezuela Time (caution: this used to mean -14400)
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
--- 219,231 ----
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    America/Asuncion  # Paraguay Time
                   #     (America/Asuncion)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    America/Caracas  # Venezuela Time
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
*************** WGT    -10800    # West Greenland Time
*** 234,246 ****

  #################### ANTARCTICA ####################

! DAVT    25200    # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d'Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
                   #     (Antarctica/Palmer)
                   #     (America/Santiago)
! MAWT    18000    # Mawson Time (Antarctica) (caution: this used to mean 21600)
                   #     (Antarctica/Mawson)

  #################### ASIA ####################
--- 234,246 ----

  #################### ANTARCTICA ####################

! DAVT    Antarctica/Davis  # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d'Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
                   #     (Antarctica/Palmer)
                   #     (America/Santiago)
! MAWT    Antarctica/Mawson  # Mawson Time (Antarctica)
                   #     (Antarctica/Mawson)

  #################### ASIA ####################
*************** ALMST   25200 D  # Alma-Ata Summer Time
*** 253,271 ****
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    18000 D  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     14400    # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   46800 D  # Anadyr Summer Time (obsolete)
! ANAT    43200    # Anadyr Time
                   #     (Asia/Anadyr)
! AZST    18000 D  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     14400    # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
--- 253,271 ----
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    Asia/Yerevan  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     Asia/Yerevan  # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   Asia/Anadyr  # Anadyr Summer Time (obsolete)
! ANAT    Asia/Anadyr  # Anadyr Time
                   #     (Asia/Anadyr)
! AZST    Asia/Baku  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     Asia/Baku  # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
*************** BORT    28800    # Borneo Time (Indonesi
*** 275,283 ****
  BTT     21600    # Bhutan Time
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
! GEST    14400 D  # Georgia Summer Time (obsolete)
!                  #     (Asia/Tbilisi)
! GET     14400    # Georgia Time (caution: this used to mean 10800)
                   #     (Asia/Tbilisi)
  HKT     28800    # Hong Kong Time (not in zic)
  ICT     25200    # Indochina Time
--- 275,282 ----
  BTT     21600    # Bhutan Time
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
! GEST    Asia/Tbilisi  # Georgia Summer Time (obsolete)
! GET     Asia/Tbilisi  # Georgia Time
                   #     (Asia/Tbilisi)
  HKT     28800    # Hong Kong Time (not in zic)
  ICT     25200    # Indochina Time
*************** ICT     25200    # Indochina Time
*** 287,294 ****
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRKST   32400 D  # Irkutsk Summer Time (obsolete)
! IRKT    28800    # Irkutsk Time (caution: this used to mean 32400)
                   #     (Asia/Irkutsk)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
--- 286,293 ----
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRKST   Asia/Irkutsk  # Irkutsk Summer Time (obsolete)
! IRKT    Asia/Irkutsk  # Irkutsk Time
                   #     (Asia/Irkutsk)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
*************** JST     32400    # Japan Standard Time
*** 302,334 ****
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
                   #     (Asia/Bishkek)
! KGT     21600    # Kyrgyzstan Time (caution: this used to mean 18000)
!                  #     (Asia/Bishkek)
! KRAST   28800 D  # Krasnoyarsk Summer Time (obsolete)
! KRAT    25200    # Krasnoyarsk Time (caution: this used to mean 28800)
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     21600    # Lanka Time (obsolete)
! MAGST   43200 D  # Magadan Summer Time (obsolete)
! MAGT    36000    # Magadan Time (caution: this used to mean 43200)
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   25200 D  # Novosibirsk Summer Time (obsolete)
! NOVT    21600    # Novosibirsk Time (caution: this used to mean 25200)
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   25200 D  # Omsk Summer Time (obsolete)
! OMST    21600    # Omsk Time (caution: this used to mean 25200)
                   #     (Asia/Omsk)
! PETST   46800 D  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    43200    # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
--- 301,332 ----
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
+ KGT     Asia/Bishkek  # Kyrgyzstan Time
                   #     (Asia/Bishkek)
! KRAST   Asia/Krasnoyarsk  # Krasnoyarsk Summer Time (obsolete)
! KRAT    Asia/Krasnoyarsk  # Krasnoyarsk Time
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     Asia/Colombo  # Lanka Time (obsolete)
! MAGST   Asia/Magadan  # Magadan Summer Time (obsolete)
! MAGT    Asia/Magadan  # Magadan Time
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   Asia/Novosibirsk  # Novosibirsk Summer Time (obsolete)
! NOVT    Asia/Novosibirsk  # Novosibirsk Time
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   Asia/Omsk  # Omsk Summer Time (obsolete)
! OMST    Asia/Omsk  # Omsk Time
                   #     (Asia/Omsk)
! PETST   Asia/Kamchatka  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    Asia/Kamchatka  # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
*************** PKT     18000    # Pakistan Time
*** 336,349 ****
                   #     (Asia/Karachi)
  PKST    21600 D  # Pakistan Summer Time
                   #     (Asia/Karachi)
! SGT     28800    # Singapore Time
                   #     (Asia/Singapore)
  TJT     18000    # Tajikistan Time
                   #     (Asia/Dushanbe)
! TMT     18000    # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    28800    # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
--- 334,347 ----
                   #     (Asia/Karachi)
  PKST    21600 D  # Pakistan Summer Time
                   #     (Asia/Karachi)
! SGT     Asia/Singapore  # Singapore Time
                   #     (Asia/Singapore)
  TJT     18000    # Tajikistan Time
                   #     (Asia/Dushanbe)
! TMT     Asia/Ashgabat  # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    Asia/Ulaanbaatar  # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
*************** UZST    21600 D  # Uzbekistan Summer Tim
*** 351,366 ****
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   39600 D  # Vladivostok Summer Time (obsolete)
! VLAT    36000    # Vladivostok Time (caution: this used to mean 39600)
                   #     (Asia/Vladivostok)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   36000 D  # Yakutsk Summer Time (obsolete)
! YAKT    32400    # Yakutsk Time (caution: this used to mean 36000)
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    18000    # Yekaterinburg Time (caution: this used to mean 21600)
                   #     (Asia/Yekaterinburg)

  #################### ATLANTIC ####################
--- 349,364 ----
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   Asia/Vladivostok  # Vladivostok Summer Time (obsolete)
! VLAT    Asia/Vladivostok  # Vladivostok Time
                   #     (Asia/Vladivostok)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   Asia/Yakutsk  # Yakutsk Summer Time (obsolete)
! YAKT    Asia/Yakutsk  # Yakutsk Time
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    Asia/Yekaterinburg  # Yekaterinburg Time
                   #     (Asia/Yekaterinburg)

  #################### ATLANTIC ####################
*************** AZOST       0 D  # Azores Summer Time
*** 406,414 ****
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! FKST   -10800    # Falkland Islands Summer Time (now used all year round)
                   #     (Atlantic/Stanley)
! FKT    -14400    # Falkland Islands Time (obsolete)

  #################### AUSTRALIA ####################

--- 404,412 ----
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! FKST    Atlantic/Stanley  # Falkland Islands Summer/Standard Time
                   #     (Atlantic/Stanley)
! FKT     Atlantic/Stanley  # Falkland Islands Time (obsolete)

  #################### AUSTRALIA ####################

*************** AWST    28800    # Australian Western St
*** 443,449 ****
                   #     (Australia/Perth)
  CADT    37800 D  # Central Australia Daylight-Saving Time (not in zic)
  CAST    34200    # Central Australia Standard Time (not in zic)
! LHDT    39600 D  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
--- 441,447 ----
                   #     (Australia/Perth)
  CADT    37800 D  # Central Australia Daylight-Saving Time (not in zic)
  CAST    34200    # Central Australia Standard Time (not in zic)
! LHDT    Australia/Lord_Howe  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
*************** MET      3600    # Middle Europe Time (n
*** 639,647 ****
  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
                   #     (Africa/Casablanca)
                   #     (Africa/El_Aaiun)
--- 637,646 ----
  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     Europe/Moscow  # Moscow Time
                   #     (Europe/Moscow)
!                  #     (Europe/Volgograd)
! VOLT    Europe/Volgograd  # Volgograd Time (obsolete)
  WET         0    # Western Europe Time
                   #     (Africa/Casablanca)
                   #     (Africa/El_Aaiun)
*************** WETDST   3600 D  # Western Europe Summer
*** 659,665 ****

  CXT     25200    # Christmas Island Time (Indian Ocean)
                   #     (Indian/Christmas)
! IOT     21600    # British Indian Ocean Territory (Chagos) (there was a timezone change recently in 1996)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
--- 658,664 ----

  CXT     25200    # Christmas Island Time (Indian Ocean)
                   #     (Indian/Christmas)
! IOT     Indian/Chagos  # British Indian Ocean Territory (Chagos)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
*************** CHAST   45900    # Chatham Standard Time
*** 682,692 ****
                   #     (Pacific/Chatham)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT    -36000    # Cook Islands Time (caution: this used to mean 43200)
                   #     (Pacific/Rarotonga)
! EASST  -18000 D  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
! EAST   -21600    # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time
                   #     (Pacific/Fiji)
--- 681,691 ----
                   #     (Pacific/Chatham)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT     Pacific/Rarotonga  # Cook Islands Time
                   #     (Pacific/Rarotonga)
! EASST   Pacific/Easter  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
! EAST    Pacific/Easter  # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time
                   #     (Pacific/Fiji)
*************** GILT    43200    # Gilbert Islands Time
*** 701,709 ****
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    39600    # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    50400    # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
--- 700,708 ----
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    Pacific/Kosrae  # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    Pacific/Kiritimati  # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
*************** MPT     36000    # North Mariana Islands
*** 715,721 ****
  # Other timezones:
  #  - NFT: Norfolk Time (Pacific)
  NFT    -12600    # Newfoundland Time (not in zic)
! NUT    -39600    # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
--- 714,720 ----
  # Other timezones:
  #  - NFT: Norfolk Time (Pacific)
  NFT    -12600    # Newfoundland Time (not in zic)
! NUT     Pacific/Niue  # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
*************** NZST    43200    # New Zealand Standard
*** 725,731 ****
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    46800    # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
--- 724,730 ----
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    Pacific/Enderbury  # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
*************** PWT     32400    # Palau Time
*** 733,739 ****
                   #     (Pacific/Palau)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     46800    # Tokelau Time (caution: this used to mean -36000)
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
--- 732,738 ----
                   #     (Pacific/Palau)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     Pacific/Fakaofo  # Tokelau Time
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
diff --git a/src/timezone/tznames/Europe.txt b/src/timezone/tznames/Europe.txt
index c6b37bd..421f8f1 100644
*** a/src/timezone/tznames/Europe.txt
--- b/src/timezone/tznames/Europe.txt
*************** MET      3600    # Middle Europe Time (n
*** 186,197 ****
  METDST   7200 D  # Middle Europe Summer Time (not in zic)
  MEZ      3600    # Mitteleurop�ische Zeit (German) (not in zic)
  MSD     14400 D  # Moscow Daylight Time (obsolete)
! MSK     10800    # Moscow Time (caution: this used to mean 14400)
                   #     (Europe/Moscow)
! SAMST   18000 D  # Samara Summer Time (obsolete)
! SAMT    14400    # Samara Time
                   #     (Europe/Samara)
! VOLT    14400    # Volgograd Time (obsolete)
  WEST     3600 D  # Western Europe Summer Time
                   #     (Africa/Casablanca)
                   #     (Atlantic/Canary)
--- 186,198 ----
  METDST   7200 D  # Middle Europe Summer Time (not in zic)
  MEZ      3600    # Mitteleurop�ische Zeit (German) (not in zic)
  MSD     14400 D  # Moscow Daylight Time (obsolete)
! MSK     Europe/Moscow  # Moscow Time
                   #     (Europe/Moscow)
!                  #     (Europe/Volgograd)
! SAMST   Europe/Samara  # Samara Summer Time (obsolete)
! SAMT    Europe/Samara  # Samara Time
                   #     (Europe/Samara)
! VOLT    Europe/Volgograd  # Volgograd Time (obsolete)
  WEST     3600 D  # Western Europe Summer Time
                   #     (Africa/Casablanca)
                   #     (Atlantic/Canary)
diff --git a/src/timezone/tznames/Indian.txt b/src/timezone/tznames/Indian.txt
index c77c991..6346600 100644
*** a/src/timezone/tznames/Indian.txt
--- b/src/timezone/tznames/Indian.txt
*************** EAT     10800    # East Africa Time
*** 23,29 ****
                   #     (Indian/Antananarivo)
                   #     (Indian/Comoro)
                   #     (Indian/Mayotte)
! IOT     21600    # British Indian Ocean Territory (Chagos) (there was a timezone change recently in 1996)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
--- 23,29 ----
                   #     (Indian/Antananarivo)
                   #     (Indian/Comoro)
                   #     (Indian/Mayotte)
! IOT     Indian/Chagos  # British Indian Ocean Territory (Chagos)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
diff --git a/src/timezone/tznames/Pacific.txt b/src/timezone/tznames/Pacific.txt
index 2f98814..1d20558 100644
*** a/src/timezone/tznames/Pacific.txt
--- b/src/timezone/tznames/Pacific.txt
*************** ChST    36000    # Chamorro Standard Tim
*** 16,29 ****
                   #     (Pacific/Saipan)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT    -36000    # Cook Islands Time (caution: this used to mean 43200)
                   #     (Pacific/Rarotonga)
! EASST  -18000 D  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
  # CONFLICT! EAST is not unique
  # Other timezones:
  #  - EAST: East Australian Standard Time (Australia)
! EAST   -21600    # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time (caution: this used to mean -46800)
                   #     (Pacific/Fiji)
--- 16,29 ----
                   #     (Pacific/Saipan)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT     Pacific/Rarotonga  # Cook Islands Time
                   #     (Pacific/Rarotonga)
! EASST   Pacific/Easter  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
  # CONFLICT! EAST is not unique
  # Other timezones:
  #  - EAST: East Australian Standard Time (Australia)
! EAST    Pacific/Easter  # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time (caution: this used to mean -46800)
                   #     (Pacific/Fiji)
*************** GILT    43200    # Gilbert Islands Time
*** 38,46 ****
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    39600    # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    50400    # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
--- 38,46 ----
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    Pacific/Kosrae  # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    Pacific/Kiritimati  # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
*************** NCT     39600    # New Caledonia Time
*** 55,63 ****
  #  - NFT: Newfoundland Time (America)
  NFT     41400    # Norfolk Time
                   #     (Pacific/Norfolk)
! NRT     43200    # Nauru Time
                   #     (Pacific/Nauru)
! NUT    -39600    # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
--- 55,63 ----
  #  - NFT: Newfoundland Time (America)
  NFT     41400    # Norfolk Time
                   #     (Pacific/Norfolk)
! NRT     Pacific/Nauru  # Nauru Time
                   #     (Pacific/Nauru)
! NUT     Pacific/Niue  # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
*************** NZST    43200    # New Zealand Standard
*** 67,73 ****
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    46800    # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
--- 67,73 ----
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    Pacific/Enderbury  # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
*************** SST    -39600    # South Sumatran Time
*** 87,93 ****
                   #     (Pacific/Pago_Pago)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     46800    # Tokelau Time (caution: this used to mean -36000)
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
--- 87,93 ----
                   #     (Pacific/Pago_Pago)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     Pacific/Fakaofo  # Tokelau Time
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)

Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
A bit of documentation-hacking later, here is the complete proposed patch
including docs updates.

As a reminder, I'm proposing to apply this to the back branches as well.
There's some further cleanup that should only happen in HEAD, but I think
we are going to be hearing about it if released branches fail to cope
nicely with the upcoming Russian zone changes.

            regards, tom lane

diff --git a/contrib/btree_gist/btree_ts.c b/contrib/btree_gist/btree_ts.c
index a13dcc8..b9c2b49 100644
*** a/contrib/btree_gist/btree_ts.c
--- b/contrib/btree_gist/btree_ts.c
*************** tstz_dist(PG_FUNCTION_ARGS)
*** 200,226 ****
   **************************************************/


! static Timestamp
  tstz_to_ts_gmt(TimestampTz ts)
  {
!     Timestamp    gmt;
!     int            val,
!                 tz;
!
!     gmt = ts;
!     DecodeSpecial(0, "gmt", &val);
!
!     if (ts < DT_NOEND && ts > DT_NOBEGIN)
!     {
!         tz = val * 60;
!
! #ifdef HAVE_INT64_TIMESTAMP
!         gmt -= (tz * INT64CONST(1000000));
! #else
!         gmt -= tz;
! #endif
!     }
!     return gmt;
  }


--- 200,210 ----
   **************************************************/


! static inline Timestamp
  tstz_to_ts_gmt(TimestampTz ts)
  {
!     /* No timezone correction is needed, since GMT is offset 0 by definition */
!     return (Timestamp) ts;
  }


diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 9494439..6ee17d8 100644
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** SET XML OPTION { DOCUMENT | CONTENT };
*** 6003,6011 ****
          Sets the collection of time zone abbreviations that will be accepted
          by the server for datetime input.  The default is <literal>'Default'</>,
          which is a collection that works in most of the world; there are
!         also <literal>'Australia'</literal> and <literal>'India'</literal>, and other collections can be defined
!         for a particular installation.  See <xref
!         linkend="datetime-appendix"> for more information.
         </para>
        </listitem>
       </varlistentry>
--- 6003,6011 ----
          Sets the collection of time zone abbreviations that will be accepted
          by the server for datetime input.  The default is <literal>'Default'</>,
          which is a collection that works in most of the world; there are
!         also <literal>'Australia'</literal> and <literal>'India'</literal>,
!         and other collections can be defined for a particular installation.
!         See <xref linkend="datetime-config-files"> for more information.
         </para>
        </listitem>
       </varlistentry>
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 3e83dbb..223ba6a 100644
*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
*************** January 8 04:05:06 1999 PST
*** 2325,2331 ****
      but continue to be prone to arbitrary changes, particularly with
      respect to daylight-savings rules.
      <productname>PostgreSQL</productname> uses the widely-used
!     <literal>zoneinfo</> (Olson) time zone database for information about
      historical time zone rules.  For times in the future, the assumption
      is that the latest known rules for a given time zone will
      continue to be observed indefinitely far into the future.
--- 2325,2331 ----
      but continue to be prone to arbitrary changes, particularly with
      respect to daylight-savings rules.
      <productname>PostgreSQL</productname> uses the widely-used
!     IANA (Olson) time zone database for information about
      historical time zone rules.  For times in the future, the assumption
      is that the latest known rules for a given time zone will
      continue to be observed indefinitely far into the future.
*************** January 8 04:05:06 1999 PST
*** 2390,2397 ****
          The recognized time zone names are listed in the
          <literal>pg_timezone_names</literal> view (see <xref
          linkend="view-pg-timezone-names">).
!         <productname>PostgreSQL</productname> uses the widely-used
!         <literal>zoneinfo</> time zone data for this purpose, so the same
          names are also recognized by much other software.
         </para>
        </listitem>
--- 2390,2397 ----
          The recognized time zone names are listed in the
          <literal>pg_timezone_names</literal> view (see <xref
          linkend="view-pg-timezone-names">).
!         <productname>PostgreSQL</productname> uses the widely-used IANA
!         time zone data for this purpose, so the same time zone
          names are also recognized by much other software.
         </para>
        </listitem>
*************** January 8 04:05:06 1999 PST
*** 2427,2433 ****
          When a daylight-savings zone abbreviation is present,
          it is assumed to be used
          according to the same daylight-savings transition rules used in the
!         <literal>zoneinfo</> time zone database's <filename>posixrules</> entry.
          In a standard <productname>PostgreSQL</productname> installation,
          <filename>posixrules</> is the same as <literal>US/Eastern</>, so
          that POSIX-style time zone specifications follow USA daylight-savings
--- 2427,2433 ----
          When a daylight-savings zone abbreviation is present,
          it is assumed to be used
          according to the same daylight-savings transition rules used in the
!         IANA time zone database's <filename>posixrules</> entry.
          In a standard <productname>PostgreSQL</productname> installation,
          <filename>posixrules</> is the same as <literal>US/Eastern</>, so
          that POSIX-style time zone specifications follow USA daylight-savings
*************** January 8 04:05:06 1999 PST
*** 2438,2446 ****
       </itemizedlist>

       In short, this is the difference between abbreviations
!      and full names: abbreviations always represent a fixed offset from
!      UTC, whereas most of the full names imply a local daylight-savings time
!      rule, and so have two possible UTC offsets.
      </para>

      <para>
--- 2438,2462 ----
       </itemizedlist>

       In short, this is the difference between abbreviations
!      and full names: abbreviations represent a specific offset from UTC,
!      whereas many of the full names imply a local daylight-savings time
!      rule, and so have two possible UTC offsets.  As an example,
!      <literal>2014-06-04 12:00 America/New_York</> represents noon local
!      time in New York, which for this particular date was Eastern Daylight
!      Time (UTC-4).  So <literal>2014-06-04 12:00 EDT</> specifies that
!      same time instant.  But <literal>2014-06-04 12:00 EST</> specifies
!      noon Eastern Standard Time (UTC-5), regardless of whether daylight
!      savings was nominally in effect on that date.
!     </para>
!
!     <para>
!      To complicate matters, some jurisdictions have used the same timezone
!      abbreviation to mean different UTC offsets at different times; for
!      example, in Moscow <literal>MSK</> has meant UTC+3 in some years and
!      UTC+4 in others.  <application>PostgreSQL</> interprets such
!      abbreviations according to whatever they meant (or had most recently
!      meant) on the specified date; but, as with the <literal>EST</> example
!      above, this is not necessarily the same as local civil time on that date.
      </para>

      <para>
*************** January 8 04:05:06 1999 PST
*** 2457,2469 ****
      </para>

      <para>
!      In all cases, timezone names are recognized case-insensitively.
!      (This is a change from <productname>PostgreSQL</productname> versions
!      prior to 8.2, which were case-sensitive in some contexts but not others.)
      </para>

      <para>
!      Neither full names nor abbreviations are hard-wired into the server;
       they are obtained from configuration files stored under
       <filename>.../share/timezone/</> and <filename>.../share/timezonesets/</>
       of the installation directory
--- 2473,2486 ----
      </para>

      <para>
!      In all cases, timezone names and abbreviations are recognized
!      case-insensitively.  (This is a change from <productname>PostgreSQL</>
!      versions prior to 8.2, which were case-sensitive in some contexts but
!      not others.)
      </para>

      <para>
!      Neither timezone names nor abbreviations are hard-wired into the server;
       they are obtained from configuration files stored under
       <filename>.../share/timezone/</> and <filename>.../share/timezonesets/</>
       of the installation directory
diff --git a/doc/src/sgml/datetime.sgml b/doc/src/sgml/datetime.sgml
index 444b0ec..ffd0715 100644
*** a/doc/src/sgml/datetime.sgml
--- b/doc/src/sgml/datetime.sgml
***************
*** 374,395 ****
      these formats:

  <synopsis>
! <replaceable>time_zone_name</replaceable> <replaceable>offset</replaceable>
! <replaceable>time_zone_name</replaceable> <replaceable>offset</replaceable> D
  @INCLUDE <replaceable>file_name</replaceable>
  @OVERRIDE
  </synopsis>
     </para>

     <para>
!     A <replaceable>time_zone_name</replaceable> is just the abbreviation
!     being defined.  The <replaceable>offset</replaceable> is the zone's
      offset in seconds from UTC, positive being east from Greenwich and
      negative being west.  For example, -18000 would be five hours west
      of Greenwich, or North American east coast standard time.  <literal>D</>
!     indicates that the zone name represents local daylight-savings time
!     rather than standard time. Since all known time zone offsets are on
!     15 minute boundaries, the number of seconds has to be a multiple of 900.
     </para>

     <para>
--- 374,400 ----
      these formats:

  <synopsis>
! <replaceable>zone_abbreviation</replaceable> <replaceable>offset</replaceable>
! <replaceable>zone_abbreviation</replaceable> <replaceable>offset</replaceable> D
! <replaceable>zone_abbreviation</replaceable> <replaceable>time_zone_name</replaceable>
  @INCLUDE <replaceable>file_name</replaceable>
  @OVERRIDE
  </synopsis>
     </para>

     <para>
!     A <replaceable>zone_abbreviation</replaceable> is just the abbreviation
!     being defined.  The <replaceable>offset</replaceable> is the equivalent
      offset in seconds from UTC, positive being east from Greenwich and
      negative being west.  For example, -18000 would be five hours west
      of Greenwich, or North American east coast standard time.  <literal>D</>
!     indicates that the zone name represents local daylight-savings time rather
!     than standard time.  Alternatively, a <replaceable>time_zone_name</> can
!     be given, in which case that time zone definition is consulted, and the
!     abbreviation's meaning in that zone is used.  This alternative is
!     recommended only for abbreviations whose meaning has historically varied,
!     as looking up the meaning is noticeably more expensive than just using
!     a fixed integer value.
     </para>

     <para>
***************
*** 400,408 ****

     <para>
      The <literal>@OVERRIDE</> syntax indicates that subsequent entries in the
!     file can override previous entries (i.e., entries obtained from included
!     files).  Without this, conflicting definitions of the same timezone
!     abbreviation are considered an error.
     </para>

     <para>
--- 405,413 ----

     <para>
      The <literal>@OVERRIDE</> syntax indicates that subsequent entries in the
!     file can override previous entries (typically, entries obtained from
!     included files).  Without this, conflicting definitions of the same
!     timezone abbreviation are considered an error.
     </para>

     <para>
***************
*** 410,423 ****
      all the non-conflicting time zone abbreviations for most of the world.
      Additional files <filename>Australia</> and <filename>India</> are
      provided for those regions: these files first include the
!     <literal>Default</> file and then add or modify timezones as needed.
     </para>

     <para>
      For reference purposes, a standard installation also contains files
      <filename>Africa.txt</>, <filename>America.txt</>, etc, containing
      information about every time zone abbreviation known to be in use
!     according to the <literal>zoneinfo</> timezone database.  The zone name
      definitions found in these files can be copied and pasted into a custom
      configuration file as needed.  Note that these files cannot be directly
      referenced as <varname>timezone_abbreviations</> settings, because of
--- 415,428 ----
      all the non-conflicting time zone abbreviations for most of the world.
      Additional files <filename>Australia</> and <filename>India</> are
      provided for those regions: these files first include the
!     <literal>Default</> file and then add or modify abbreviations as needed.
     </para>

     <para>
      For reference purposes, a standard installation also contains files
      <filename>Africa.txt</>, <filename>America.txt</>, etc, containing
      information about every time zone abbreviation known to be in use
!     according to the IANA timezone database.  The zone name
      definitions found in these files can be copied and pasted into a custom
      configuration file as needed.  Note that these files cannot be directly
      referenced as <varname>timezone_abbreviations</> settings, because of
***************
*** 426,434 ****

     <note>
      <para>
!      If an error occurs while reading the time zone data sets, no new value is
!      applied but the old set is kept. If the error occurs while starting the
!      database, startup fails.
      </para>
     </note>

--- 431,439 ----

     <note>
      <para>
!      If an error occurs while reading the time zone abbreviation set, no new
!      value is applied and the old set is kept. If the error occurs while
!      starting the database, startup fails.
      </para>
     </note>

diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index a3c9bea..68931d2 100644
*** a/doc/src/sgml/installation.sgml
--- b/doc/src/sgml/installation.sgml
*************** su - postgres
*** 1108,1114 ****
          <para>
           <productname>PostgreSQL</> includes its own time zone database,
           which it requires for date and time operations.  This time zone
!          database is in fact compatible with the <quote>zoneinfo</> time zone
           database provided by many operating systems such as FreeBSD,
           Linux, and Solaris, so it would be redundant to install it again.
           When this option is used, the system-supplied time zone database
--- 1108,1114 ----
          <para>
           <productname>PostgreSQL</> includes its own time zone database,
           which it requires for date and time operations.  This time zone
!          database is in fact compatible with the IANA time zone
           database provided by many operating systems such as FreeBSD,
           Linux, and Solaris, so it would be redundant to install it again.
           When this option is used, the system-supplied time zone database
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/known_abbrevs.txt b/src/timezone/known_abbrevs.txt
index f309a48..db78cf3 100644
*** a/src/timezone/known_abbrevs.txt
--- b/src/timezone/known_abbrevs.txt
*************** IDT    10800    D
*** 85,90 ****
--- 85,91 ----
  IOT    21600
  IRDT    16200    D
  IRKT    28800
+ IRKT    32400
  IRST    12600
  IST    19800
  IST    3600    D
*************** JST    32400
*** 93,103 ****
--- 94,106 ----
  KGT    21600
  KOST    39600
  KRAT    25200
+ KRAT    28800
  KST    32400
  LHDT    39600    D
  LHST    37800
  LINT    50400
  MAGT    36000
+ MAGT    43200
  MART    -34200
  MAWT    18000
  MDT    -21600    D
*************** MHT    43200
*** 107,112 ****
--- 110,116 ----
  MIST    39600
  MMT    23400
  MSK    10800
+ MSK    14400
  MST    -25200
  MUT    14400
  MVT    18000
*************** NCT    39600
*** 115,120 ****
--- 119,125 ----
  NDT    -9000    D
  NFT    41400
  NOVT    21600
+ NOVT    25200
  NPT    20700
  NRT    43200
  NST    -12600
*************** NUT    -39600
*** 122,127 ****
--- 127,133 ----
  NZDT    46800    D
  NZST    43200
  OMST    21600
+ OMST    25200
  ORAT    18000
  PDT    -25200    D
  PET    -18000
*************** QYZT    21600
*** 141,146 ****
--- 147,153 ----
  RET    14400
  ROTT    -10800
  SAKT    36000
+ SAKT    39600
  SAMT    14400
  SAST    7200
  SBT    39600
*************** UYT    -10800
*** 165,170 ****
--- 172,178 ----
  UZT    18000
  VET    -16200
  VLAT    36000
+ VLAT    39600
  VOST    21600
  VUT    39600
  WAKT    43200
*************** WSDT    50400    D
*** 182,185 ****
--- 190,195 ----
  WSST    46800
  XJT    21600
  YAKT    32400
+ YAKT    36000
  YEKT    18000
+ YEKT    21600
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/America.txt b/src/timezone/tznames/America.txt
index 54b51fe..9e62732 100644
*** a/src/timezone/tznames/America.txt
--- b/src/timezone/tznames/America.txt
*************** AMT    -14400    # Amazon Time
*** 47,53 ****
                   #     (America/Cuiaba)
                   #     (America/Manaus)
                   #     (America/Porto_Velho)
! ART    -10800    # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
--- 47,53 ----
                   #     (America/Cuiaba)
                   #     (America/Manaus)
                   #     (America/Porto_Velho)
! ART    America/Argentina/Buenos_Aires  # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
*************** ART    -10800    # Argentina Time
*** 58,64 ****
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    -7200 D  # Argentina Summer Time
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Arabic Standard Time (Asia)
--- 58,64 ----
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    America/Argentina/Buenos_Aires  # Argentina Summer Time
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Arabic Standard Time (Asia)
*************** GMT         0    # Greenwich Mean Time
*** 228,234 ****
                   #     (Etc/GMT)
                   #     (Europe/Dublin)
                   #     (Europe/London)
! GYT    -14400    # Guyana Time
                   #     (America/Guyana)
  HADT   -32400 D  # Hawaii-Aleutian Daylight Time
                   #     (America/Adak)
--- 228,234 ----
                   #     (Etc/GMT)
                   #     (Europe/Dublin)
                   #     (Europe/London)
! GYT    America/Guyana  # Guyana Time
                   #     (America/Guyana)
  HADT   -32400 D  # Hawaii-Aleutian Daylight Time
                   #     (America/Adak)
*************** PST    -28800    # Pacific Standard Time
*** 285,299 ****
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    -14400    # Paraguay Time
                   #     (America/Asuncion)
! SRT    -10800    # Suriname Time
                   #     (America/Paramaribo)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    -16200    # Venezuela Time (caution: this used to mean -14400)
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
--- 285,299 ----
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    America/Asuncion  # Paraguay Time
                   #     (America/Asuncion)
! SRT    America/Paramaribo  # Suriname Time
                   #     (America/Paramaribo)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    America/Caracas  # Venezuela Time
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
diff --git a/src/timezone/tznames/Antarctica.txt b/src/timezone/tznames/Antarctica.txt
index 5a03250..2359020 100644
*** a/src/timezone/tznames/Antarctica.txt
--- b/src/timezone/tznames/Antarctica.txt
*************** CLST   -10800 D  # Chile Summer Time
*** 16,26 ****
  CLT    -14400    # Chile Time
                   #     (America/Santiago)
                   #     (Antarctica/Palmer)
! DAVT    25200    # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d`Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
! MAWT    18000    # Mawson Time (Antarctica) (caution: this used to mean 21600)
                   #     (Antarctica/Mawson)
  MIST    39600    # Macquarie Island Time
                   #     (Antarctica/Macquarie)
--- 16,26 ----
  CLT    -14400    # Chile Time
                   #     (America/Santiago)
                   #     (Antarctica/Palmer)
! DAVT    Antarctica/Davis  # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d`Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
! MAWT    Antarctica/Mawson  # Mawson Time (Antarctica)
                   #     (Antarctica/Mawson)
  MIST    39600    # Macquarie Island Time
                   #     (Antarctica/Macquarie)
diff --git a/src/timezone/tznames/Asia.txt b/src/timezone/tznames/Asia.txt
index 8c3cb35..bb28646 100644
*** a/src/timezone/tznames/Asia.txt
--- b/src/timezone/tznames/Asia.txt
*************** ALMT    21600    # Alma-Ata Time
*** 15,31 ****
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    18000 D  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     14400    # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   46800 D  # Anadyr Summer Time (obsolete)
! ANAT    43200    # Anadyr Time
                   #     (Asia/Anadyr)
! AQTT    18000    # Aqtau Time (obsolete)
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Atlantic Standard Time (America)
--- 15,33 ----
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    Asia/Yerevan  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     Asia/Yerevan  # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   Asia/Anadyr  # Anadyr Summer Time (obsolete)
! ANAT    Asia/Anadyr  # Anadyr Time
                   #     (Asia/Anadyr)
! AQTST   Asia/Aqtau  # Aqtau Summer Time (obsolete)
! AQTT    Asia/Aqtau  # Aqtau Time
!                  #     (Asia/Aqtau)
  # CONFLICT! AST is not unique
  # Other timezones:
  #  - AST: Atlantic Standard Time (America)
*************** AST     10800    # Arabia Standard Time
*** 41,49 ****
                   #     (Asia/Kuwait)
                   #     (Asia/Qatar)
                   #     (Asia/Riyadh)
! AZST    18000 D  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     14400    # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
--- 43,51 ----
                   #     (Asia/Kuwait)
                   #     (Asia/Qatar)
                   #     (Asia/Riyadh)
! AZST    Asia/Baku  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     Asia/Baku  # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
*************** BTT     21600    # Bhutan Time
*** 54,60 ****
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
  CHOST   36000 D  # Choibalsan Summer Time (obsolete)
! CHOT    28800    # Choibalsan Time (caution: this used to mean 32400)
                   #     (Asia/Choibalsan)
  CIT     28800    # Central Indonesia Time (obsolete, WITA is now preferred)
  EEST    10800 D  # East-Egypt Summer Time
--- 56,62 ----
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
  CHOST   36000 D  # Choibalsan Summer Time (obsolete)
! CHOT    Asia/Choibalsan  # Choibalsan Time
                   #     (Asia/Choibalsan)
  CIT     28800    # Central Indonesia Time (obsolete, WITA is now preferred)
  EEST    10800 D  # East-Egypt Summer Time
*************** EET      7200    # East-Egypt Time
*** 105,113 ****
                   #     (Europe/Vilnius)
                   #     (Europe/Zaporozhye)
  EIT     32400    # East Indonesia Time (obsolete, WIT is now preferred)
! GEST    14400 D  # Georgia Summer Time (obsolete)
!                  #     (Asia/Tbilisi)
! GET     14400    # Georgia Time (caution: this used to mean 10800)
                   #     (Asia/Tbilisi)
  # CONFLICT! GST is not unique
  # Other timezones:
--- 107,114 ----
                   #     (Europe/Vilnius)
                   #     (Europe/Zaporozhye)
  EIT     32400    # East Indonesia Time (obsolete, WIT is now preferred)
! GEST    Asia/Tbilisi  # Georgia Summer Time (obsolete)
! GET     Asia/Tbilisi  # Georgia Time
                   #     (Asia/Tbilisi)
  # CONFLICT! GST is not unique
  # Other timezones:
*************** GST     14400    # Gulf Standard Time
*** 117,123 ****
                   #     (Asia/Muscat)
  HKT     28800    # Hong Kong Time (not in zic)
  HOVST   28800 D  # Hovd Summer Time (obsolete)
! HOVT    25200    # Hovd Time
                   #     (Asia/Hovd)
  ICT     25200    # Indochina Time
                   #     (Asia/Bangkok)
--- 118,124 ----
                   #     (Asia/Muscat)
  HKT     28800    # Hong Kong Time (not in zic)
  HOVST   28800 D  # Hovd Summer Time (obsolete)
! HOVT    Asia/Hovd  # Hovd Time
                   #     (Asia/Hovd)
  ICT     25200    # Indochina Time
                   #     (Asia/Bangkok)
*************** ICT     25200    # Indochina Time
*** 126,137 ****
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRDT    16200 D  # Iran Daylight Time
                   #     (Asia/Tehran)
! IRKST   32400 D  # Irkutsk Summer Time (obsolete)
! IRKT    28800    # Irkutsk Time (caution: this used to mean 32400)
                   #     (Asia/Irkutsk)
! IRST    12600    # Iran Standard Time
                   #     (Asia/Tehran)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
--- 127,138 ----
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRDT    Asia/Tehran  # Iran Daylight Time
                   #     (Asia/Tehran)
! IRKST   Asia/Irkutsk  # Irkutsk Summer Time (obsolete)
! IRKT    Asia/Irkutsk  # Irkutsk Time
                   #     (Asia/Irkutsk)
! IRST    Asia/Tehran  # Iran Standard Time
                   #     (Asia/Tehran)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
*************** JST     32400    # Japan Standard Time
*** 151,185 ****
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
                   #     (Asia/Bishkek)
! KGT     21600    # Kyrgyzstan Time (caution: this used to mean 18000)
!                  #     (Asia/Bishkek)
! KRAST   28800 D  # Krasnoyarsk Summer Time (obsolete)
! KRAT    25200    # Krasnoyarsk Time (caution: this used to mean 28800)
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     21600    # Lanka Time (obsolete)
! MAGST   43200 D  # Magadan Summer Time (obsolete)
! MAGT    36000    # Magadan Time (caution: this used to mean 43200)
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   25200 D  # Novosibirsk Summer Time (obsolete)
! NOVT    21600    # Novosibirsk Time (caution: this used to mean 25200)
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   25200 D  # Omsk Summer Time (obsolete)
! OMST    21600    # Omsk Time (caution: this used to mean 25200)
                   #     (Asia/Omsk)
! ORAT    18000    # Oral Time
                   #     (Asia/Oral)
! PETST   46800 D  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    43200    # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
--- 152,185 ----
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
+ KGT     Asia/Bishkek  # Kyrgyzstan Time
                   #     (Asia/Bishkek)
! KRAST   Asia/Krasnoyarsk  # Krasnoyarsk Summer Time (obsolete)
! KRAT    Asia/Krasnoyarsk  # Krasnoyarsk Time
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     Asia/Colombo  # Lanka Time (obsolete)
! MAGST   Asia/Magadan  # Magadan Summer Time (obsolete)
! MAGT    Asia/Magadan  # Magadan Time
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   Asia/Novosibirsk  # Novosibirsk Summer Time (obsolete)
! NOVT    Asia/Novosibirsk  # Novosibirsk Time
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   Asia/Omsk  # Omsk Summer Time (obsolete)
! OMST    Asia/Omsk  # Omsk Time
                   #     (Asia/Omsk)
! ORAT    Asia/Oral  # Oral Time
                   #     (Asia/Oral)
! PETST   Asia/Kamchatka  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    Asia/Kamchatka  # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
*************** PKST    21600 D  # Pakistan Summer Time
*** 189,198 ****
                   #     (Asia/Karachi)
  QYZT    21600    # Kizilorda Time
                   #     (Asia/Qyzylorda)
! SAKST   39600 D  # Sakhalin Summer Time (obsolete)
! SAKT    36000    # Sakhalin Time (caution: this used to mean 39600)
                   #     (Asia/Sakhalin)
! SGT     28800    # Singapore Time
                   #     (Asia/Singapore)
  SRET    39600    # Srednekolymsk Time
                   #     (Asia/Srednekolymsk)
--- 189,198 ----
                   #     (Asia/Karachi)
  QYZT    21600    # Kizilorda Time
                   #     (Asia/Qyzylorda)
! SAKST   Asia/Sakhalin  # Sakhalin Summer Time (obsolete)
! SAKT    Asia/Sakhalin  # Sakhalin Time
                   #     (Asia/Sakhalin)
! SGT     Asia/Singapore  # Singapore Time
                   #     (Asia/Singapore)
  SRET    39600    # Srednekolymsk Time
                   #     (Asia/Srednekolymsk)
*************** TJT     18000    # Tajikistan Time
*** 200,209 ****
                   #     (Asia/Dushanbe)
  TLT     32400    # East Timor Time
                   #     (Asia/Dili)
! TMT     18000    # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    28800    # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
--- 200,209 ----
                   #     (Asia/Dushanbe)
  TLT     32400    # East Timor Time
                   #     (Asia/Dili)
! TMT     Asia/Ashgabat  # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    Asia/Ulaanbaatar  # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
*************** UZST    21600 D  # Uzbekistan Summer Tim
*** 211,218 ****
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   39600 D  # Vladivostok Summer Time (obsolete)
! VLAT    36000    # Vladivostok Time (caution: this used to mean 39600)
                   #     (Asia/Vladivostok)
  WIB     25200    # Waktu Indonesia Barat
                   #     (Asia/Jakarta)
--- 211,218 ----
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   Asia/Vladivostok  # Vladivostok Summer Time (obsolete)
! VLAT    Asia/Vladivostok  # Vladivostok Time
                   #     (Asia/Vladivostok)
  WIB     25200    # Waktu Indonesia Barat
                   #     (Asia/Jakarta)
*************** WITA    28800    # Waktu Indonesia Tenga
*** 223,231 ****
                   #     (Asia/Makassar)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   36000 D  # Yakutsk Summer Time (obsolete)
! YAKT    32400    # Yakutsk Time (caution: this used to mean 36000)
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    18000    # Yekaterinburg Time (caution: this used to mean 21600)
                   #     (Asia/Yekaterinburg)
--- 223,231 ----
                   #     (Asia/Makassar)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   Asia/Yakutsk  # Yakutsk Summer Time (obsolete)
! YAKT    Asia/Yakutsk  # Yakutsk Time
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    Asia/Yekaterinburg  # Yekaterinburg Time
                   #     (Asia/Yekaterinburg)
diff --git a/src/timezone/tznames/Atlantic.txt b/src/timezone/tznames/Atlantic.txt
index c65734b..1d34d1e 100644
*** a/src/timezone/tznames/Atlantic.txt
--- b/src/timezone/tznames/Atlantic.txt
*************** AZOST       0 D  # Azores Summer Time
*** 48,58 ****
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! CVT     -3600    # Cape Verde Time
                   #     (Atlantic/Cape_Verde)
! FKST   -10800    # Falkland Islands Summer Time (now used all year round)
                   #     (Atlantic/Stanley)
! FKT    -14400    # Falkland Islands Time (obsolete)
  GMT         0    # Greenwich Mean Time
                   #     (Africa/Abidjan)
                   #     (Africa/Bamako)
--- 48,58 ----
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! CVT     Atlantic/Cape_Verde  # Cape Verde Time
                   #     (Atlantic/Cape_Verde)
! FKST    Atlantic/Stanley  # Falkland Islands Summer/Standard Time
                   #     (Atlantic/Stanley)
! FKT     Atlantic/Stanley  # Falkland Islands Time (obsolete)
  GMT         0    # Greenwich Mean Time
                   #     (Africa/Abidjan)
                   #     (Africa/Bamako)
diff --git a/src/timezone/tznames/Australia.txt b/src/timezone/tznames/Australia.txt
index 8373093..92c2968 100644
*** a/src/timezone/tznames/Australia.txt
--- b/src/timezone/tznames/Australia.txt
*************** EAST    36000    # East Australian Stand
*** 52,58 ****
  # Other timezones:
  #  - EST: Eastern Standard Time (America)
  EST     36000    # Eastern Standard Time (not in zic)
! LHDT    39600 D  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
--- 52,58 ----
  # Other timezones:
  #  - EST: Eastern Standard Time (America)
  EST     36000    # Eastern Standard Time (not in zic)
! LHDT    Australia/Lord_Howe  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
diff --git a/src/timezone/tznames/Default b/src/timezone/tznames/Default
index 9e5209e..a8b8eac 100644
*** a/src/timezone/tznames/Default
--- b/src/timezone/tznames/Default
*************** AKST   -32400    # Alaska Standard Time
*** 54,60 ****
                   #     (America/Juneau)
                   #     (America/Nome)
                   #     (America/Yakutat)
! ART    -10800    # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
--- 54,60 ----
                   #     (America/Juneau)
                   #     (America/Nome)
                   #     (America/Yakutat)
! ART    America/Argentina/Buenos_Aires  # Argentina Time
                   #     (America/Argentina/Buenos_Aires)
                   #     (America/Argentina/Cordoba)
                   #     (America/Argentina/Tucuman)
*************** ART    -10800    # Argentina Time
*** 65,71 ****
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    -7200 D  # Argentina Summer Time
  BOT    -14400    # Bolivia Time
                   #     (America/La_Paz)
  BRA    -10800    # Brazil Time (not in zic)
--- 65,71 ----
                   #     (America/Argentina/Mendoza)
                   #     (America/Argentina/Rio_Gallegos)
                   #     (America/Argentina/Ushuaia)
! ARST    America/Argentina/Buenos_Aires  # Argentina Summer Time
  BOT    -14400    # Bolivia Time
                   #     (America/La_Paz)
  BRA    -10800    # Brazil Time (not in zic)
*************** FNST    -3600 D  # Fernando de Noronha S
*** 170,176 ****
                   #     (America/Noronha)
  GFT    -10800    # French Guiana Time
                   #     (America/Cayenne)
! GYT    -14400    # Guyana Time
                   #     (America/Guyana)
  MDT    -21600 D  # Mexico Mountain Daylight Time
                   # Mountain Daylight Time
--- 170,176 ----
                   #     (America/Noronha)
  GFT    -10800    # French Guiana Time
                   #     (America/Cayenne)
! GYT    America/Guyana  # Guyana Time
                   #     (America/Guyana)
  MDT    -21600 D  # Mexico Mountain Daylight Time
                   # Mountain Daylight Time
*************** PST    -28800    # Pacific Standard Time
*** 219,231 ****
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    -14400    # Paraguay Time
                   #     (America/Asuncion)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    -16200    # Venezuela Time (caution: this used to mean -14400)
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
--- 219,231 ----
                   #     (Pacific/Pitcairn)
  PYST   -10800 D  # Paraguay Summer Time
                   #     (America/Asuncion)
! PYT    America/Asuncion  # Paraguay Time
                   #     (America/Asuncion)
  UYST    -7200 D  # Uruguay Summer Time
                   #     (America/Montevideo)
  UYT    -10800    # Uruguay Time
                   #     (America/Montevideo)
! VET    America/Caracas  # Venezuela Time
                   #     (America/Caracas)
  WGST    -7200 D  # Western Greenland Summer Time
                   #     (America/Godthab)
*************** WGT    -10800    # West Greenland Time
*** 234,246 ****

  #################### ANTARCTICA ####################

! DAVT    25200    # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d'Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
                   #     (Antarctica/Palmer)
                   #     (America/Santiago)
! MAWT    18000    # Mawson Time (Antarctica) (caution: this used to mean 21600)
                   #     (Antarctica/Mawson)

  #################### ASIA ####################
--- 234,246 ----

  #################### ANTARCTICA ####################

! DAVT    Antarctica/Davis  # Davis Time (Antarctica)
                   #     (Antarctica/Davis)
  DDUT    36000    # Dumont-d'Urville Time (Antarctica)
                   #     (Antarctica/DumontDUrville)
                   #     (Antarctica/Palmer)
                   #     (America/Santiago)
! MAWT    Antarctica/Mawson  # Mawson Time (Antarctica)
                   #     (Antarctica/Mawson)

  #################### ASIA ####################
*************** ALMST   25200 D  # Alma-Ata Summer Time
*** 253,271 ****
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    18000 D  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     14400    # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   46800 D  # Anadyr Summer Time (obsolete)
! ANAT    43200    # Anadyr Time
                   #     (Asia/Anadyr)
! AZST    18000 D  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     14400    # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
--- 253,271 ----
  # CONFLICT! AMST is not unique
  # Other timezones:
  #  - AMST: Amazon Summer Time (America)
! AMST    Asia/Yerevan  # Armenia Summer Time
                   #     (Asia/Yerevan)
  # CONFLICT! AMT is not unique
  # Other timezones:
  #  - AMT: Amazon Time (America)
! AMT     Asia/Yerevan  # Armenia Time
                   #     (Asia/Yerevan)
! ANAST   Asia/Anadyr  # Anadyr Summer Time (obsolete)
! ANAT    Asia/Anadyr  # Anadyr Time
                   #     (Asia/Anadyr)
! AZST    Asia/Baku  # Azerbaijan Summer Time
                   #     (Asia/Baku)
! AZT     Asia/Baku  # Azerbaijan Time
                   #     (Asia/Baku)
  BDT     21600    # Bangladesh Time
                   #     (Asia/Dhaka)
*************** BORT    28800    # Borneo Time (Indonesi
*** 275,283 ****
  BTT     21600    # Bhutan Time
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
! GEST    14400 D  # Georgia Summer Time (obsolete)
!                  #     (Asia/Tbilisi)
! GET     14400    # Georgia Time (caution: this used to mean 10800)
                   #     (Asia/Tbilisi)
  HKT     28800    # Hong Kong Time (not in zic)
  ICT     25200    # Indochina Time
--- 275,282 ----
  BTT     21600    # Bhutan Time
                   #     (Asia/Thimphu)
  CCT     28800    # China Coastal Time (not in zic)
! GEST    Asia/Tbilisi  # Georgia Summer Time (obsolete)
! GET     Asia/Tbilisi  # Georgia Time
                   #     (Asia/Tbilisi)
  HKT     28800    # Hong Kong Time (not in zic)
  ICT     25200    # Indochina Time
*************** ICT     25200    # Indochina Time
*** 287,294 ****
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRKST   32400 D  # Irkutsk Summer Time (obsolete)
! IRKT    28800    # Irkutsk Time (caution: this used to mean 32400)
                   #     (Asia/Irkutsk)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
--- 286,293 ----
                   #     (Asia/Vientiane)
  IDT     10800 D  # Israel Daylight Time
                   #     (Asia/Jerusalem)
! IRKST   Asia/Irkutsk  # Irkutsk Summer Time (obsolete)
! IRKT    Asia/Irkutsk  # Irkutsk Time
                   #     (Asia/Irkutsk)
  IRT     12600    # Iran Time (not in zic)
  # CONFLICT! IST is not unique
*************** JST     32400    # Japan Standard Time
*** 302,334 ****
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
                   #     (Asia/Bishkek)
! KGT     21600    # Kyrgyzstan Time (caution: this used to mean 18000)
!                  #     (Asia/Bishkek)
! KRAST   28800 D  # Krasnoyarsk Summer Time (obsolete)
! KRAT    25200    # Krasnoyarsk Time (caution: this used to mean 28800)
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     21600    # Lanka Time (obsolete)
! MAGST   43200 D  # Magadan Summer Time (obsolete)
! MAGT    36000    # Magadan Time (caution: this used to mean 43200)
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   25200 D  # Novosibirsk Summer Time (obsolete)
! NOVT    21600    # Novosibirsk Time (caution: this used to mean 25200)
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   25200 D  # Omsk Summer Time (obsolete)
! OMST    21600    # Omsk Time (caution: this used to mean 25200)
                   #     (Asia/Omsk)
! PETST   46800 D  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    43200    # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
--- 301,332 ----
                   #     (Asia/Tokyo)
  KDT     36000 D  # Korean Daylight Time (not in zic)
  KGST    21600 D  # Kyrgyzstan Summer Time (obsolete)
+ KGT     Asia/Bishkek  # Kyrgyzstan Time
                   #     (Asia/Bishkek)
! KRAST   Asia/Krasnoyarsk  # Krasnoyarsk Summer Time (obsolete)
! KRAT    Asia/Krasnoyarsk  # Krasnoyarsk Time
                   #     (Asia/Krasnoyarsk)
  KST     32400    # Korean Standard Time
                   #     (Asia/Pyongyang)
! LKT     Asia/Colombo  # Lanka Time (obsolete)
! MAGST   Asia/Magadan  # Magadan Summer Time (obsolete)
! MAGT    Asia/Magadan  # Magadan Time
                   #     (Asia/Magadan)
  MMT     23400    # Myanmar Time
                   #     (Asia/Rangoon)
  MYT     28800    # Malaysia Time
                   #     (Asia/Kuala_Lumpur)
                   #     (Asia/Kuching)
! NOVST   Asia/Novosibirsk  # Novosibirsk Summer Time (obsolete)
! NOVT    Asia/Novosibirsk  # Novosibirsk Time
                   #     (Asia/Novosibirsk)
  NPT     20700    # Nepal Time
                   #     (Asia/Katmandu)
! OMSST   Asia/Omsk  # Omsk Summer Time (obsolete)
! OMST    Asia/Omsk  # Omsk Time
                   #     (Asia/Omsk)
! PETST   Asia/Kamchatka  # Petropavlovsk-Kamchatski Summer Time (obsolete)
! PETT    Asia/Kamchatka  # Petropavlovsk-Kamchatski Time
                   #     (Asia/Kamchatka)
  PHT     28800    # Philippine Time
                   #     (Asia/Manila)
*************** PKT     18000    # Pakistan Time
*** 336,349 ****
                   #     (Asia/Karachi)
  PKST    21600 D  # Pakistan Summer Time
                   #     (Asia/Karachi)
! SGT     28800    # Singapore Time
                   #     (Asia/Singapore)
  TJT     18000    # Tajikistan Time
                   #     (Asia/Dushanbe)
! TMT     18000    # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    28800    # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
--- 334,347 ----
                   #     (Asia/Karachi)
  PKST    21600 D  # Pakistan Summer Time
                   #     (Asia/Karachi)
! SGT     Asia/Singapore  # Singapore Time
                   #     (Asia/Singapore)
  TJT     18000    # Tajikistan Time
                   #     (Asia/Dushanbe)
! TMT     Asia/Ashgabat  # Turkmenistan Time
                   #     (Asia/Ashgabat)
  ULAST   32400 D  # Ulan Bator Summer Time (obsolete)
! ULAT    Asia/Ulaanbaatar  # Ulan Bator Time
                   #     (Asia/Ulaanbaatar)
  UZST    21600 D  # Uzbekistan Summer Time
                   #     (Asia/Samarkand)
*************** UZST    21600 D  # Uzbekistan Summer Tim
*** 351,366 ****
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   39600 D  # Vladivostok Summer Time (obsolete)
! VLAT    36000    # Vladivostok Time (caution: this used to mean 39600)
                   #     (Asia/Vladivostok)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   36000 D  # Yakutsk Summer Time (obsolete)
! YAKT    32400    # Yakutsk Time (caution: this used to mean 36000)
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    18000    # Yekaterinburg Time (caution: this used to mean 21600)
                   #     (Asia/Yekaterinburg)

  #################### ATLANTIC ####################
--- 349,364 ----
  UZT     18000    # Uzbekistan Time
                   #     (Asia/Samarkand)
                   #     (Asia/Tashkent)
! VLAST   Asia/Vladivostok  # Vladivostok Summer Time (obsolete)
! VLAT    Asia/Vladivostok  # Vladivostok Time
                   #     (Asia/Vladivostok)
  XJT     21600    # Xinjiang Time
                   #     (Asia/Urumqi)
! YAKST   Asia/Yakutsk  # Yakutsk Summer Time (obsolete)
! YAKT    Asia/Yakutsk  # Yakutsk Time
                   #     (Asia/Yakutsk)
  YEKST   21600 D  # Yekaterinburg Summer Time (obsolete)
! YEKT    Asia/Yekaterinburg  # Yekaterinburg Time
                   #     (Asia/Yekaterinburg)

  #################### ATLANTIC ####################
*************** AZOST       0 D  # Azores Summer Time
*** 406,414 ****
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! FKST   -10800    # Falkland Islands Summer Time (now used all year round)
                   #     (Atlantic/Stanley)
! FKT    -14400    # Falkland Islands Time (obsolete)

  #################### AUSTRALIA ####################

--- 404,412 ----
                   #     (Atlantic/Azores)
  AZOT    -3600    # Azores Time
                   #     (Atlantic/Azores)
! FKST    Atlantic/Stanley  # Falkland Islands Summer/Standard Time
                   #     (Atlantic/Stanley)
! FKT     Atlantic/Stanley  # Falkland Islands Time (obsolete)

  #################### AUSTRALIA ####################

*************** AWST    28800    # Australian Western St
*** 443,449 ****
                   #     (Australia/Perth)
  CADT    37800 D  # Central Australia Daylight-Saving Time (not in zic)
  CAST    34200    # Central Australia Standard Time (not in zic)
! LHDT    39600 D  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
--- 441,447 ----
                   #     (Australia/Perth)
  CADT    37800 D  # Central Australia Daylight-Saving Time (not in zic)
  CAST    34200    # Central Australia Standard Time (not in zic)
! LHDT    Australia/Lord_Howe  # Lord Howe Daylight Time
                   #     (Australia/Lord_Howe)
  LHST    37800    # Lord Howe Standard Time
                   #     (Australia/Lord_Howe)
*************** MET      3600    # Middle Europe Time (n
*** 639,647 ****
  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
                   #     (Africa/Casablanca)
                   #     (Africa/El_Aaiun)
--- 637,646 ----
  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     Europe/Moscow  # Moscow Time
                   #     (Europe/Moscow)
!                  #     (Europe/Volgograd)
! VOLT    Europe/Volgograd  # Volgograd Time (obsolete)
  WET         0    # Western Europe Time
                   #     (Africa/Casablanca)
                   #     (Africa/El_Aaiun)
*************** WETDST   3600 D  # Western Europe Summer
*** 659,665 ****

  CXT     25200    # Christmas Island Time (Indian Ocean)
                   #     (Indian/Christmas)
! IOT     21600    # British Indian Ocean Territory (Chagos) (there was a timezone change recently in 1996)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
--- 658,664 ----

  CXT     25200    # Christmas Island Time (Indian Ocean)
                   #     (Indian/Christmas)
! IOT     Indian/Chagos  # British Indian Ocean Territory (Chagos)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
*************** CHAST   45900    # Chatham Standard Time
*** 682,692 ****
                   #     (Pacific/Chatham)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT    -36000    # Cook Islands Time (caution: this used to mean 43200)
                   #     (Pacific/Rarotonga)
! EASST  -18000 D  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
! EAST   -21600    # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time
                   #     (Pacific/Fiji)
--- 681,691 ----
                   #     (Pacific/Chatham)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT     Pacific/Rarotonga  # Cook Islands Time
                   #     (Pacific/Rarotonga)
! EASST   Pacific/Easter  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
! EAST    Pacific/Easter  # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time
                   #     (Pacific/Fiji)
*************** GILT    43200    # Gilbert Islands Time
*** 701,709 ****
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    39600    # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    50400    # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
--- 700,708 ----
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    Pacific/Kosrae  # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    Pacific/Kiritimati  # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
*************** MPT     36000    # North Mariana Islands
*** 715,721 ****
  # Other timezones:
  #  - NFT: Norfolk Time (Pacific)
  NFT    -12600    # Newfoundland Time (not in zic)
! NUT    -39600    # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
--- 714,720 ----
  # Other timezones:
  #  - NFT: Norfolk Time (Pacific)
  NFT    -12600    # Newfoundland Time (not in zic)
! NUT     Pacific/Niue  # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
*************** NZST    43200    # New Zealand Standard
*** 725,731 ****
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    46800    # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
--- 724,730 ----
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    Pacific/Enderbury  # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
*************** PWT     32400    # Palau Time
*** 733,739 ****
                   #     (Pacific/Palau)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     46800    # Tokelau Time (caution: this used to mean -36000)
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
--- 732,738 ----
                   #     (Pacific/Palau)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     Pacific/Fakaofo  # Tokelau Time
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
diff --git a/src/timezone/tznames/Europe.txt b/src/timezone/tznames/Europe.txt
index c6b37bd..421f8f1 100644
*** a/src/timezone/tznames/Europe.txt
--- b/src/timezone/tznames/Europe.txt
*************** MET      3600    # Middle Europe Time (n
*** 186,197 ****
  METDST   7200 D  # Middle Europe Summer Time (not in zic)
  MEZ      3600    # Mitteleurop�ische Zeit (German) (not in zic)
  MSD     14400 D  # Moscow Daylight Time (obsolete)
! MSK     10800    # Moscow Time (caution: this used to mean 14400)
                   #     (Europe/Moscow)
! SAMST   18000 D  # Samara Summer Time (obsolete)
! SAMT    14400    # Samara Time
                   #     (Europe/Samara)
! VOLT    14400    # Volgograd Time (obsolete)
  WEST     3600 D  # Western Europe Summer Time
                   #     (Africa/Casablanca)
                   #     (Atlantic/Canary)
--- 186,198 ----
  METDST   7200 D  # Middle Europe Summer Time (not in zic)
  MEZ      3600    # Mitteleurop�ische Zeit (German) (not in zic)
  MSD     14400 D  # Moscow Daylight Time (obsolete)
! MSK     Europe/Moscow  # Moscow Time
                   #     (Europe/Moscow)
!                  #     (Europe/Volgograd)
! SAMST   Europe/Samara  # Samara Summer Time (obsolete)
! SAMT    Europe/Samara  # Samara Time
                   #     (Europe/Samara)
! VOLT    Europe/Volgograd  # Volgograd Time (obsolete)
  WEST     3600 D  # Western Europe Summer Time
                   #     (Africa/Casablanca)
                   #     (Atlantic/Canary)
diff --git a/src/timezone/tznames/Indian.txt b/src/timezone/tznames/Indian.txt
index c77c991..6346600 100644
*** a/src/timezone/tznames/Indian.txt
--- b/src/timezone/tznames/Indian.txt
*************** EAT     10800    # East Africa Time
*** 23,29 ****
                   #     (Indian/Antananarivo)
                   #     (Indian/Comoro)
                   #     (Indian/Mayotte)
! IOT     21600    # British Indian Ocean Territory (Chagos) (there was a timezone change recently in 1996)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
--- 23,29 ----
                   #     (Indian/Antananarivo)
                   #     (Indian/Comoro)
                   #     (Indian/Mayotte)
! IOT     Indian/Chagos  # British Indian Ocean Territory (Chagos)
                   #     (Indian/Chagos)
  MUT     14400    # Mauritius Island Time
                   #     (Indian/Mauritius)
diff --git a/src/timezone/tznames/Pacific.txt b/src/timezone/tznames/Pacific.txt
index 2f98814..1d20558 100644
*** a/src/timezone/tznames/Pacific.txt
--- b/src/timezone/tznames/Pacific.txt
*************** ChST    36000    # Chamorro Standard Tim
*** 16,29 ****
                   #     (Pacific/Saipan)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT    -36000    # Cook Islands Time (caution: this used to mean 43200)
                   #     (Pacific/Rarotonga)
! EASST  -18000 D  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
  # CONFLICT! EAST is not unique
  # Other timezones:
  #  - EAST: East Australian Standard Time (Australia)
! EAST   -21600    # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time (caution: this used to mean -46800)
                   #     (Pacific/Fiji)
--- 16,29 ----
                   #     (Pacific/Saipan)
  CHUT    36000    # Chuuk Time
                   #     (Pacific/Chuuk)
! CKT     Pacific/Rarotonga  # Cook Islands Time
                   #     (Pacific/Rarotonga)
! EASST   Pacific/Easter  # Easter Island Summer Time (Chile)
                   #     (Pacific/Easter)
  # CONFLICT! EAST is not unique
  # Other timezones:
  #  - EAST: East Australian Standard Time (Australia)
! EAST    Pacific/Easter  # Easter Island Time (Chile)
                   #     (Pacific/Easter)
  FJST    46800 D  # Fiji Summer Time (caution: this used to mean -46800)
                   #     (Pacific/Fiji)
*************** GILT    43200    # Gilbert Islands Time
*** 38,46 ****
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    39600    # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    50400    # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
--- 38,46 ----
  HST    -36000    # Hawaiian Standard Time
                   #     (Pacific/Honolulu)
                   #     (Pacific/Johnston)
! KOST    Pacific/Kosrae  # Kosrae Time
                   #     (Pacific/Kosrae)
! LINT    Pacific/Kiritimati  # Line Islands Time (Kiribati)
                   #     (Pacific/Kiritimati)
  MART   -34200    # Marquesas Time
                   #     (Pacific/Marquesas)
*************** NCT     39600    # New Caledonia Time
*** 55,63 ****
  #  - NFT: Newfoundland Time (America)
  NFT     41400    # Norfolk Time
                   #     (Pacific/Norfolk)
! NRT     43200    # Nauru Time
                   #     (Pacific/Nauru)
! NUT    -39600    # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
--- 55,63 ----
  #  - NFT: Newfoundland Time (America)
  NFT     41400    # Norfolk Time
                   #     (Pacific/Norfolk)
! NRT     Pacific/Nauru  # Nauru Time
                   #     (Pacific/Nauru)
! NUT     Pacific/Niue  # Niue Time
                   #     (Pacific/Niue)
  NZDT    46800 D  # New Zealand Daylight Time
                   #     (Antarctica/McMurdo)
*************** NZST    43200    # New Zealand Standard
*** 67,73 ****
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    46800    # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
--- 67,73 ----
                   #     (Pacific/Auckland)
  PGT     36000    # Papua New Guinea Time
                   #     (Pacific/Port_Moresby)
! PHOT    Pacific/Enderbury  # Phoenix Islands Time (Kiribati)
                   #     (Pacific/Enderbury)
  PONT    39600    # Ponape Time (Micronesia)
                   #     (Pacific/Ponape)
*************** SST    -39600    # South Sumatran Time
*** 87,93 ****
                   #     (Pacific/Pago_Pago)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     46800    # Tokelau Time (caution: this used to mean -36000)
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
--- 87,93 ----
                   #     (Pacific/Pago_Pago)
  TAHT   -36000    # Tahiti Time (zic says "TAHT", other sources "THAT")
                   #     (Pacific/Tahiti)
! TKT     Pacific/Fakaofo  # Tokelau Time
                   #     (Pacific/Fakaofo)
  TOT     46800    # Tonga Time
                   #     (Pacific/Tongatapu)
diff --git a/src/timezone/tznames/README b/src/timezone/tznames/README
index 6cb0ae8..c80caa3 100644
*** a/src/timezone/tznames/README
--- b/src/timezone/tznames/README
*************** tznames
*** 6,31 ****
  This directory contains files with timezone sets for PostgreSQL.  The problem
  is that time zone abbreviations are not unique throughout the world and you
  might find out that a time zone abbreviation in the `Default' set collides
! with the one you wanted to use.  All other files except for `Default' are
! intended to override values from the `Default' set.  So you might already have
! a file here that serves your needs.  If not, you can create your own.

  In order to use one of these files, you need to set

     timezone_abbreviations = 'xyz'

  in any of the usual ways for setting a parameter, where xyz is the filename
! that contains the desired time zone names.

! If you do not find an appropriate set of time zone names for your geographic
  location supplied here, please report this to <pgsql-hackers@postgresql.org>.
! Your set of time zone names can then be included in future releases.
  For the time being you can always add your own set.

  The files named Africa.txt, etc, are not intended to be used directly as
  time zone abbreviation files. They contain reference definitions of time zone
! names that can be copied into a custom abbreviation file as needed.
!
! Note that these files (*.txt) are already a subset of the zic timezone
! database files: we tried to list only those time zones that (according to
! the zic timezone database) appear to be still in use.
--- 6,34 ----
  This directory contains files with timezone sets for PostgreSQL.  The problem
  is that time zone abbreviations are not unique throughout the world and you
  might find out that a time zone abbreviation in the `Default' set collides
! with the one you wanted to use.  This can be fixed by selecting a timezone
! set that defines the abbreviation the way you want it.  There might already
! be a file here that serves your needs.  If not, you can create your own.

  In order to use one of these files, you need to set

     timezone_abbreviations = 'xyz'

  in any of the usual ways for setting a parameter, where xyz is the filename
! that contains the desired time zone abbreviations.

! If you do not find an appropriate set of abbreviations for your geographic
  location supplied here, please report this to <pgsql-hackers@postgresql.org>.
! Your set of time zone abbreviations can then be included in future releases.
  For the time being you can always add your own set.

+ Typically a custom abbreviation set is made by including the `Default' set
+ and then adding or overriding abbreviations as necessary.  For examples,
+ see the `Australia' and `India' files.
+
  The files named Africa.txt, etc, are not intended to be used directly as
  time zone abbreviation files. They contain reference definitions of time zone
! abbreviations that can be copied into a custom abbreviation file as needed.
! Note that these files (*.txt) are already a subset of the IANA timezone
! database files: we tried to list only those time zone abbreviations that
! (according to the IANA timezone database) appear to be still in use.
diff --git a/src/timezone/zic.c b/src/timezone/zic.c
index 13baf73..1a7ec68 100644
*** a/src/timezone/zic.c
--- b/src/timezone/zic.c
*************** writezone(const char *name, const char *
*** 1771,1777 ****

                  /* Print current timezone abbreviations if requested */
                  if (print_abbrevs &&
!                     (ats[i] >= print_cutoff || i == thistimelim - 1))
                  {
                      unsigned char tm = typemap[types[i]];
                      char       *thisabbrev = &thischars[indmap[abbrinds[tm]]];
--- 1771,1777 ----

                  /* Print current timezone abbreviations if requested */
                  if (print_abbrevs &&
!                     (i == thistimelim - 1 || ats[i + 1] > print_cutoff))
                  {
                      unsigned char tm = typemap[types[i]];
                      char       *thisabbrev = &thischars[indmap[abbrinds[tm]]];

Re: Proposal for better support of time-varying timezone abbreviations

From
Michael Meskes
Date:
On Wed, Oct 15, 2014 at 09:50:16AM -0400, Tom Lane wrote:
> The same thought had occurred to me.  Probably the main use of the
> datetime parsing code in ecpg is for interpreting outputs from the
> server, and (at least by default) the server doesn't use timezone
> abbreviations when printing timestamps.  So maybe that's largely
> dead code anyhow.  I would not propose back-patching such a change,
> but we could try it in 9.5 and see if anyone complains.

Agreed on all accounts.

> A less drastic remedy would be to remove just those abbreviations
> whose meaning has actually changed over time.  Eventually that
> might be all of them ... but in the meantime, we could at least
> argue that we weren't breaking any case that worked well before.

This is what your patch did, right?

Michael
-- 
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
Jabber: michael.meskes at gmail dot com
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL



Re: Proposal for better support of time-varying timezone abbreviations

From
Tom Lane
Date:
Michael Meskes <meskes@postgresql.org> writes:
> On Wed, Oct 15, 2014 at 09:50:16AM -0400, Tom Lane wrote:
>> The same thought had occurred to me.  Probably the main use of the
>> datetime parsing code in ecpg is for interpreting outputs from the
>> server, and (at least by default) the server doesn't use timezone
>> abbreviations when printing timestamps.  So maybe that's largely
>> dead code anyhow.  I would not propose back-patching such a change,
>> but we could try it in 9.5 and see if anyone complains.

> Agreed on all accounts.

>> A less drastic remedy would be to remove just those abbreviations
>> whose meaning has actually changed over time.  Eventually that
>> might be all of them ... but in the meantime, we could at least
>> argue that we weren't breaking any case that worked well before.

> This is what your patch did, right?

No, I did not touch ecpg's set of tokens at all, just changed the
representation of datetktbl to match the new backend coding.
I figured we could discuss behavioral changes separately.

I don't have a strong opinion about which of the above things to do ...
what's your preference?
        regards, tom lane



Re: Proposal for better support of time-varying timezone abbreviations

From
Michael Meskes
Date:
On Wed, Oct 22, 2014 at 11:32:41AM -0400, Tom Lane wrote:
> I don't have a strong opinion about which of the above things to do ...
> what's your preference?

I think it's better for the future if me make a clean cut. Yes, the option
keeps compatability a little bit higher, but that doesn't matter that much as
the codes won't be used by the server anyway. Besides, they may eventually
change and then we have to make sure to remember ecpg's copy again.

Michael
-- 
Michael Meskes
Michael at Fam-Meskes dot De, Michael at Meskes dot (De|Com|Net|Org)
Michael at BorussiaFan dot De, Meskes at (Debian|Postgresql) dot Org
Jabber: michael.meskes at gmail dot com
VfL Borussia! Força Barça! Go SF 49ers! Use Debian GNU/Linux, PostgreSQL