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

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Proposal for better support of time-varying timezone abbreviations
Дата
Msg-id 28124.1412719520@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Proposal for better support of time-varying timezone abbreviations  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: Proposal for better support of time-varying timezone abbreviations
Список pgsql-hackers
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 */

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Andres Freund
Дата:
Сообщение: Re: lwlock contention with SSI
Следующее
От: Peter Geoghegan
Дата:
Сообщение: Re: Promise index tuples for UPSERT