]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/datetime.c
Fix initialization of fake LSN for unlogged relations
[postgresql] / src / backend / utils / adt / datetime.c
index c29b8e1019168d5c610b63da6f88526977e49217..e38bd930543f9e1a1462d6b330e7e1637660884b 100644 (file)
@@ -3,7 +3,7 @@
  * datetime.c
  *       Support functions for date/time types.
  *
- * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
 #include "postgres.h"
 
 #include <ctype.h>
-#include <float.h>
 #include <limits.h>
 #include <math.h>
 
+#include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "utils/tzparser.h"
 
 
-static int DecodeNumber(int flen, char *field, bool haveTextMonth,
-                        int fmask, int *tmask,
-                        struct pg_tm * tm, fsec_t *fsec, bool *is2digits);
-static int DecodeNumberField(int len, char *str,
-                                 int fmask, int *tmask,
-                                 struct pg_tm * tm, fsec_t *fsec, bool *is2digits);
-static int DecodeTime(char *str, int fmask, int range,
-                  int *tmask, struct pg_tm * tm, fsec_t *fsec);
-static int     DecodeTimezone(char *str, int *tzp);
+static int     DecodeNumber(int flen, char *field, bool haveTextMonth,
+                                                int fmask, int *tmask,
+                                                struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int     DecodeNumberField(int len, char *str,
+                                                         int fmask, int *tmask,
+                                                         struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int     DecodeTime(char *str, int fmask, int range,
+                                          int *tmask, struct pg_tm *tm, fsec_t *fsec);
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
-static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
-                  struct pg_tm * tm);
-static int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
-                        struct pg_tm * tm);
-static void TrimTrailingZeros(char *str);
-static void AppendSeconds(char *cp, int sec, fsec_t fsec,
-                         int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec,
-                                  int scale);
-static void AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec,
-                               int scale);
+static int     DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
+                                          struct pg_tm *tm);
+static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
+                                                  int precision, bool fillzeros);
+static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
+                                                          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 bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
+                                                                                                 const char *abbr, pg_tz *tzp,
+                                                                                                 int *offset, int *isdst);
+static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
 
 
 const int      day_tab[2][13] =
@@ -60,10 +63,10 @@ const int   day_tab[2][13] =
        {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}
 };
 
-char      *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+const char *const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
 
-char      *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
+const char *const days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
 "Thursday", "Friday", "Saturday", NULL};
 
 
@@ -71,47 +74,24 @@ char           *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
  *      PRIVATE ROUTINES                                                                                                                *
  *****************************************************************************/
 
-/*
- * 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 text field is NOT guaranteed to be NULL-terminated.
- *
- * To keep this table reasonably small, we divide the lexval for TZ and DTZ
- * entries by 15 (so they are on 15 minute boundaries) and truncate the text
- * 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 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
- * format as the static datetktbl.
+ * 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 datetkn *timezonetktbl = NULL;
-
-static int     sztimezonetktbl = 0;
-
 static const datetkn datetktbl[] = {
-/*     text, token, lexval */
+       /* token, type, value */
        {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
        {DA_D, ADBC, AD},                       /* "ad" for years > 0 */
-       {"allballs", RESERV, DTK_ZULU},         /* 00:00:00 */
+       {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
        {"am", AMPM, AM},
        {"apr", MONTH, 4},
        {"april", MONTH, 4},
@@ -119,13 +99,12 @@ static const datetkn datetktbl[] = {
        {"aug", MONTH, 8},
        {"august", MONTH, 8},
        {DB_C, ADBC, BC},                       /* "bc" for years <= 0 */
-       {DCURRENT, RESERV, DTK_CURRENT},        /* "current" is always now */
        {"d", UNITS, DTK_DAY},          /* "day of month" for ISO input */
        {"dec", MONTH, 12},
        {"december", MONTH, 12},
-       {"dow", RESERV, DTK_DOW},       /* day of week */
-       {"doy", RESERV, DTK_DOY},       /* day of year */
-       {"dst", DTZMOD, 6},
+       {"dow", UNITS, DTK_DOW},        /* day of week */
+       {"doy", UNITS, 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},
@@ -133,8 +112,7 @@ static const datetkn datetktbl[] = {
        {"friday", DOW, 5},
        {"h", UNITS, DTK_HOUR},         /* "hour" */
        {LATE, RESERV, DTK_LATE},       /* "infinity" reserved for "late time" */
-       {INVALID, RESERV, DTK_INVALID},         /* "invalid" reserved for bad time */
-       {"isodow", RESERV, DTK_ISODOW},         /* ISO day of week, Sunday == 7 */
+       {"isodow", UNITS, DTK_ISODOW},  /* ISO day of week, Sunday == 7 */
        {"isoyear", UNITS, DTK_ISOYEAR},        /* year in terms of the ISO week date */
        {"j", UNITS, DTK_JULIAN},
        {"jan", MONTH, 1},
@@ -177,7 +155,6 @@ static const datetkn datetktbl[] = {
        {"tue", DOW, 2},
        {"tues", DOW, 2},
        {"tuesday", DOW, 2},
-       {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
        {"wed", DOW, 3},
        {"wednesday", DOW, 3},
        {"weds", DOW, 3},
@@ -185,40 +162,43 @@ static const datetkn datetktbl[] = {
        {YESTERDAY, RESERV, DTK_YESTERDAY}      /* yesterday midnight */
 };
 
-static int     szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
+static const int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
 
-static datetkn deltatktbl[] = {
-       /* text, token, lexval */
+/*
+ * 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 */
        {DAGO, AGO, 0},                         /* "ago" indicates negative time offset */
        {"c", UNITS, DTK_CENTURY},      /* "century" relative */
-       {"cent", UNITS, DTK_CENTURY},           /* "century" relative */
+       {"cent", UNITS, DTK_CENTURY},   /* "century" relative */
        {"centuries", UNITS, DTK_CENTURY},      /* "centuries" relative */
-       {DCENTURY, UNITS, DTK_CENTURY},         /* "century" relative */
+       {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative */
        {"d", UNITS, DTK_DAY},          /* "day" relative */
        {DDAY, UNITS, DTK_DAY},         /* "day" relative */
        {"days", UNITS, DTK_DAY},       /* "days" relative */
        {"dec", UNITS, DTK_DECADE}, /* "decade" relative */
-       {DDECADE, UNITS, DTK_DECADE},           /* "decade" relative */
-       {"decades", UNITS, DTK_DECADE},         /* "decades" relative */
+       {DDECADE, UNITS, DTK_DECADE},   /* "decade" relative */
+       {"decades", UNITS, DTK_DECADE}, /* "decades" relative */
        {"decs", UNITS, DTK_DECADE},    /* "decades" relative */
        {"h", UNITS, DTK_HOUR},         /* "hour" relative */
        {DHOUR, UNITS, DTK_HOUR},       /* "hour" relative */
        {"hours", UNITS, DTK_HOUR}, /* "hours" relative */
        {"hr", UNITS, DTK_HOUR},        /* "hour" relative */
        {"hrs", UNITS, DTK_HOUR},       /* "hours" relative */
-       {INVALID, RESERV, DTK_INVALID},         /* reserved for invalid time */
        {"m", UNITS, DTK_MINUTE},       /* "minute" relative */
-       {"microsecon", UNITS, DTK_MICROSEC},            /* "microsecond" relative */
-       {"mil", UNITS, DTK_MILLENNIUM},         /* "millennium" relative */
-       {"millennia", UNITS, DTK_MILLENNIUM},           /* "millennia" relative */
-       {DMILLENNIUM, UNITS, DTK_MILLENNIUM},           /* "millennium" relative */
-       {"millisecon", UNITS, DTK_MILLISEC},            /* relative */
+       {"microsecon", UNITS, DTK_MICROSEC},    /* "microsecond" relative */
+       {"mil", UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
+       {"millennia", UNITS, DTK_MILLENNIUM},   /* "millennia" relative */
+       {DMILLENNIUM, UNITS, DTK_MILLENNIUM},   /* "millennium" relative */
+       {"millisecon", UNITS, DTK_MILLISEC},    /* relative */
        {"mils", UNITS, DTK_MILLENNIUM},        /* "millennia" relative */
        {"min", UNITS, DTK_MINUTE}, /* "minute" relative */
        {"mins", UNITS, DTK_MINUTE},    /* "minutes" relative */
-       {DMINUTE, UNITS, DTK_MINUTE},           /* "minute" relative */
-       {"minutes", UNITS, DTK_MINUTE},         /* "minutes" relative */
+       {DMINUTE, UNITS, DTK_MINUTE},   /* "minute" relative */
+       {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative */
        {"mon", UNITS, DTK_MONTH},      /* "months" relative */
        {"mons", UNITS, DTK_MONTH}, /* "months" relative */
        {DMONTH, UNITS, DTK_MONTH}, /* "month" relative */
@@ -229,7 +209,7 @@ static datetkn deltatktbl[] = {
        {"mseconds", UNITS, DTK_MILLISEC},
        {"msecs", UNITS, DTK_MILLISEC},
        {"qtr", UNITS, DTK_QUARTER},    /* "quarter" relative */
-       {DQUARTER, UNITS, DTK_QUARTER},         /* "quarter" relative */
+       {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative */
        {"s", UNITS, DTK_SECOND},
        {"sec", UNITS, DTK_SECOND},
        {DSECOND, UNITS, DTK_SECOND},
@@ -237,13 +217,12 @@ static datetkn deltatktbl[] = {
        {"secs", UNITS, DTK_SECOND},
        {DTIMEZONE, UNITS, DTK_TZ}, /* "timezone" time offset */
        {"timezone_h", UNITS, DTK_TZ_HOUR}, /* timezone hour units */
-       {"timezone_m", UNITS, DTK_TZ_MINUTE},           /* timezone minutes units */
-       {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
+       {"timezone_m", UNITS, DTK_TZ_MINUTE},   /* timezone minutes units */
        {"us", UNITS, DTK_MICROSEC},    /* "microsecond" relative */
-       {"usec", UNITS, DTK_MICROSEC},          /* "microsecond" relative */
+       {"usec", UNITS, DTK_MICROSEC},  /* "microsecond" relative */
        {DMICROSEC, UNITS, DTK_MICROSEC},       /* "microsecond" relative */
        {"useconds", UNITS, DTK_MICROSEC},      /* "microseconds" relative */
-       {"usecs", UNITS, DTK_MICROSEC},         /* "microseconds" relative */
+       {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative */
        {"w", UNITS, DTK_WEEK},         /* "week" relative */
        {DWEEK, UNITS, DTK_WEEK},       /* "week" relative */
        {"weeks", UNITS, DTK_WEEK}, /* "weeks" relative */
@@ -254,28 +233,17 @@ static datetkn deltatktbl[] = {
        {"yrs", UNITS, DTK_YEAR}        /* "years" relative */
 };
 
-static int     szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];
+static const 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};
 
-
-/*
- * strtoi --- just like strtol, but returns int not long
- */
-static int
-strtoi(const char *nptr, char **endptr, int base)
-{
-       long            val;
-
-       val = strtol(nptr, endptr, base);
-#ifdef HAVE_LONG_INT_64
-       if (val != (long) ((int32) val))
-               errno = ERANGE;
-#endif
-       return (int) val;
-}
+static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL};
 
 
 /*
@@ -286,14 +254,16 @@ strtoi(const char *nptr, char **endptr, int base)
  *     and calendar date for all non-negative Julian days
  *     (i.e. from Nov 24, -4713 on).
  *
- * These routines will be used by other date/time packages
- * - thomas 97/02/25
- *
  * Rewritten to eliminate overflow problems. This now allows the
  * routines to work correctly for all Julian day counts from
  * 0 to 2147483647     (Nov 24, -4713 to Jun 3, 5874898) assuming
  * a 32-bit integer. Longer types should also work to the limits
  * of their precision.
+ *
+ * Actually, date2j() will work sanely, in the sense of producing
+ * valid negative Julian dates, significantly before Nov 24, -4713.
+ * We rely on it to do so back to Nov 1, -4713; see IS_VALID_JULIAN()
+ * and associated commentary in timestamp.h.
  */
 
 int
@@ -319,7 +289,7 @@ date2j(int y, int m, int d)
        julian += 7834 * m / 256 + d;
 
        return julian;
-}      /* date2j() */
+}                                                              /* date2j() */
 
 void
 j2date(int jd, int *year, int *month, int *day)
@@ -346,28 +316,27 @@ j2date(int jd, int *year, int *month, int *day)
        *month = (quad + 10) % MONTHS_PER_YEAR + 1;
 
        return;
-}      /* j2date() */
+}                                                              /* j2date() */
 
 
 /*
  * j2day - convert Julian date to day-of-week (0..6 == Sun..Sat)
  *
  * Note: various places use the locution j2day(date - 1) to produce a
- * result according to the convention 0..6 = Mon..Sun. This is a bit of
+ * result according to the convention 0..6 = Mon..Sun.  This is a bit of
  * a crock, but will work as long as the computation here is just a modulo.
  */
 int
 j2day(int date)
 {
-       unsigned int day;
-
-       day = date;
-
-       day += 1;
-       day %= 7;
+       date += 1;
+       date %= 7;
+       /* Cope if division truncates towards zero, as it probably does */
+       if (date < 0)
+               date += 7;
 
-       return (int) day;
-}      /* j2day() */
+       return date;
+}                                                              /* j2day() */
 
 
 /*
@@ -376,7 +345,7 @@ j2day(int date)
  * Get the transaction start time ("now()") broken down as a struct pg_tm.
  */
 void
-GetCurrentDateTime(struct pg_tm * tm)
+GetCurrentDateTime(struct pg_tm *tm)
 {
        int                     tz;
        fsec_t          fsec;
@@ -393,7 +362,7 @@ GetCurrentDateTime(struct pg_tm * tm)
  * including fractional seconds and timezone offset.
  */
 void
-GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp)
+GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
 {
        int                     tz;
 
@@ -405,71 +374,84 @@ GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp)
 }
 
 
-/* TrimTrailingZeros()
- * ... resulting from printing numbers with full precision.
- *
- * Before Postgres 8.4, this always left at least 2 fractional digits,
- * but conversations on the lists suggest this isn't desired
- * since showing '0.10' is misleading with values of precision(1).
- */
-static void
-TrimTrailingZeros(char *str)
-{
-       int                     len = strlen(str);
-
-       while (len > 1 && *(str + len - 1) == '0' && *(str + len - 2) != '.')
-       {
-               len--;
-               *(str + len) = '\0';
-       }
-}
-
 /*
- * Append sections and fractional seconds (if any) at *cp.
+ * Append seconds and fractional seconds (if any) at *cp.
+ *
  * precision is the max number of fraction digits, fillzeros says to
  * pad to two integral-seconds digits.
+ *
+ * Returns a pointer to the new end of string.  No NUL terminator is put
+ * there; callers are responsible for NUL terminating str themselves.
+ *
  * Note that any sign is stripped from the input seconds values.
  */
-static void
+static char *
 AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 {
-       if (fsec == 0)
-       {
-               if (fillzeros)
-                       sprintf(cp, "%02d", abs(sec));
-               else
-                       sprintf(cp, "%d", abs(sec));
-       }
+       Assert(precision >= 0);
+
+       if (fillzeros)
+               cp = pg_ltostr_zeropad(cp, Abs(sec), 2);
        else
+               cp = pg_ltostr(cp, Abs(sec));
+
+       /* fsec_t is just an int32 */
+       if (fsec != 0)
        {
-#ifdef HAVE_INT64_TIMESTAMP
-               if (fillzeros)
-                       sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
-               else
-                       sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
-#else
-               if (fillzeros)
-                       sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec));
-               else
-                       sprintf(cp, "%.*f", precision, fabs(sec + fsec));
-#endif
-               TrimTrailingZeros(cp);
+               int32           value = Abs(fsec);
+               char       *end = &cp[precision + 1];
+               bool            gotnonzero = false;
+
+               *cp++ = '.';
+
+               /*
+                * Append the fractional seconds part.  Note that we don't want any
+                * trailing zeros here, so since we're building the number in reverse
+                * we'll skip appending zeros until we've output a non-zero digit.
+                */
+               while (precision--)
+               {
+                       int32           oldval = value;
+                       int32           remainder;
+
+                       value /= 10;
+                       remainder = oldval - value * 10;
+
+                       /* check if we got a non-zero */
+                       if (remainder)
+                               gotnonzero = true;
+
+                       if (gotnonzero)
+                               cp[precision] = '0' + remainder;
+                       else
+                               end = &cp[precision];
+               }
+
+               /*
+                * If we still have a non-zero value then precision must have not been
+                * enough to print the number.  We punt the problem to pg_ltostr(),
+                * which will generate a correct answer in the minimum valid width.
+                */
+               if (value)
+                       return pg_ltostr(cp, Abs(fsec));
+
+               return end;
        }
+       else
+               return cp;
 }
 
-/* Variant of above that's specialized to timestamp case */
-static void
-AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
+
+/*
+ * Variant of above that's specialized to timestamp case.
+ *
+ * Returns a pointer to the new end of string.  No NUL terminator is put
+ * there; callers are responsible for NUL terminating str themselves.
+ */
+static char *
+AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 {
-       /*
-        * In float mode, don't print fractional seconds before 1 AD, since it's
-        * unlikely there's any precision left ...
-        */
-#ifndef HAVE_INT64_TIMESTAMP
-       if (tm->tm_year <= 0)
-               fsec = 0;
-#endif
-       AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
+       return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
 }
 
 /*
@@ -477,7 +459,7 @@ AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec)
  * We assume the input frac is less than 1 so overflow is not an issue.
  */
 static void
-AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec, int scale)
+AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
        int                     sec;
 
@@ -487,16 +469,12 @@ AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec, int scale)
        sec = (int) frac;
        tm->tm_sec += sec;
        frac -= sec;
-#ifdef HAVE_INT64_TIMESTAMP
        *fsec += rint(frac * 1000000);
-#else
-       *fsec += frac;
-#endif
 }
 
 /* As above, but initial scale produces days */
 static void
-AdjustFractDays(double frac, struct pg_tm * tm, fsec_t *fsec, int scale)
+AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
 {
        int                     extra_days;
 
@@ -522,11 +500,7 @@ ParseFractionalSecond(char *cp, fsec_t *fsec)
        /* check for parse failure */
        if (*cp != '\0' || errno != 0)
                return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
        *fsec = rint(frac * 1000000);
-#else
-       *fsec = frac;
-#endif
        return 0;
 }
 
@@ -785,7 +759,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
  */
 int
 DecodeDateTime(char **field, int *ftype, int nf,
-                          int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp)
+                          int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
 {
        int                     fmask = 0,
                                tmask,
@@ -795,11 +769,14 @@ DecodeDateTime(char **field, int *ftype, int nf,
        int                     val;
        int                     dterr;
        int                     mer = HR24;
-       bool            haveTextMonth = FALSE;
-       bool            isjulian = FALSE;
-       bool            is2digits = FALSE;
-       bool            bc = FALSE;
+       bool            haveTextMonth = false;
+       bool            isjulian = false;
+       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;
 
        /*
@@ -821,11 +798,12 @@ DecodeDateTime(char **field, int *ftype, int nf,
                switch (ftype[i])
                {
                        case DTK_DATE:
-                               /***
-                                * Integral julian day with attached time zone?
-                                * All other forms with JD will be separated into
-                                * distinct fields, so we handle just this case here.
-                                ***/
+
+                               /*
+                                * Integral julian day with attached time zone? All other
+                                * forms with JD will be separated into distinct fields, so we
+                                * handle just this case here.
+                                */
                                if (ptype == DTK_JULIAN)
                                {
                                        char       *cp;
@@ -835,12 +813,12 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                return DTERR_BAD_FORMAT;
 
                                        errno = 0;
-                                       val = strtoi(field[i], &cp, 10);
+                                       val = strtoint(field[i], &cp, 10);
                                        if (errno == ERANGE || val < 0)
                                                return DTERR_FIELD_OVERFLOW;
 
                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
-                                       isjulian = TRUE;
+                                       isjulian = true;
 
                                        /* Get the time zone from the end of the string */
                                        dterr = DecodeTimezone(cp, tzp);
@@ -851,7 +829,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                        ptype = 0;
                                        break;
                                }
-                               /***
+
+                               /*
                                 * Already have a date? Then this might be a time zone name
                                 * with embedded punctuation (e.g. "America/New_York") or a
                                 * run-together time with trailing time zone (e.g. hhmmss-zz).
@@ -860,7 +839,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                 * We consider it a time zone if we already have month & day.
                                 * This is to allow the form "mmm dd hhmmss tz year", which
                                 * we've historically accepted.
-                                ***/
+                                */
                                else if (ptype != 0 ||
                                                 ((fmask & (DTK_M(MONTH) | DTK_M(DAY))) ==
                                                  (DTK_M(MONTH) | DTK_M(DAY))))
@@ -944,6 +923,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                break;
 
                        case DTK_TIME:
+
                                /*
                                 * This might be an ISO time following a "t" field.
                                 */
@@ -997,7 +977,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                        int                     val;
 
                                        errno = 0;
-                                       val = strtoi(field[i], &cp, 10);
+                                       val = strtoint(field[i], &cp, 10);
                                        if (errno == ERANGE)
                                                return DTERR_FIELD_OVERFLOW;
 
@@ -1085,7 +1065,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                                return DTERR_FIELD_OVERFLOW;
                                                        tmask = DTK_DATE_M;
                                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
-                                                       isjulian = TRUE;
+                                                       isjulian = true;
 
                                                        /* fractional Julian Day? */
                                                        if (*cp == '.')
@@ -1096,12 +1076,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                                time = strtod(cp, &cp);
                                                                if (*cp != '\0' || errno != 0)
                                                                        return DTERR_BAD_FORMAT;
-
-#ifdef HAVE_INT64_TIMESTAMP
                                                                time *= USECS_PER_DAY;
-#else
-                                                               time *= SECS_PER_DAY;
-#endif
                                                                dt2time(time,
                                                                                &tm->tm_hour, &tm->tm_min,
                                                                                &tm->tm_sec, fsec);
@@ -1159,7 +1134,18 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                if (dterr < 0)
                                                        return dterr;
                                        }
-                                       else if (flen > 4)
+
+                                       /*
+                                        * Is this a YMD or HMS specification, or a year number?
+                                        * YMD and HMS are required to be six digits or more, so
+                                        * if it is 5 digits, it is a year.  If it is six or more
+                                        * digits, we assume it is YMD or HMS unless no date and
+                                        * no time values have been specified.  This forces 6+
+                                        * digit years to be at the end of the string, or to use
+                                        * the ISO date specification.
+                                        */
+                                       else if (flen >= 6 && (!(fmask & DTK_DATE_M) ||
+                                                                                  !(fmask & DTK_TIME_M)))
                                        {
                                                dterr = DecodeNumberField(flen, field[i], fmask,
                                                                                                  &tmask, tm,
@@ -1182,7 +1168,10 @@ DecodeDateTime(char **field, int *ftype, int nf,
 
                        case DTK_STRING:
                        case DTK_SPECIAL:
-                               type = DecodeSpecial(i, field[i], &val);
+                               /* 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;
 
@@ -1192,14 +1181,6 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                        case RESERV:
                                                switch (val)
                                                {
-                                                       case DTK_CURRENT:
-                                                               ereport(ERROR,
-                                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                         errmsg("date/time value \"current\" is no longer supported")));
-
-                                                               return DTERR_BAD_FORMAT;
-                                                               break;
-
                                                        case DTK_NOW:
                                                                tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
                                                                *dtype = DTK_DATE;
@@ -1211,7 +1192,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                                *dtype = DTK_DATE;
                                                                GetCurrentDateTime(&cur_tm);
                                                                j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) - 1,
-                                                                       &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                                                                          &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
                                                                break;
 
                                                        case DTK_TODAY:
@@ -1228,7 +1209,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                                *dtype = DTK_DATE;
                                                                GetCurrentDateTime(&cur_tm);
                                                                j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) + 1,
-                                                                       &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                                                                          &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
                                                                break;
 
                                                        case DTK_ZULU:
@@ -1260,7 +1241,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                        tm->tm_mday = tm->tm_mon;
                                                        tmask = DTK_M(DAY);
                                                }
-                                               haveTextMonth = TRUE;
+                                               haveTextMonth = true;
                                                tm->tm_mon = val;
                                                break;
 
@@ -1274,7 +1255,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                tm->tm_isdst = 1;
                                                if (tzp == NULL)
                                                        return DTERR_BAD_FORMAT;
-                                               *tzp += val * MINS_PER_HOUR;
+                                               *tzp -= val;
                                                break;
 
                                        case DTZ:
@@ -1287,17 +1268,23 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                tm->tm_isdst = 1;
                                                if (tzp == NULL)
                                                        return DTERR_BAD_FORMAT;
-                                               *tzp = val * MINS_PER_HOUR;
+                                               *tzp = -val;
                                                break;
 
                                        case TZ:
                                                tm->tm_isdst = 0;
                                                if (tzp == NULL)
                                                        return DTERR_BAD_FORMAT;
-                                               *tzp = val * MINS_PER_HOUR;
+                                               *tzp = -val;
                                                break;
 
-                                       case IGNORE_DTF:
+                                       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:
@@ -1407,7 +1394,20 @@ DecodeDateTime(char **field, int *ftype, int nf,
                        *tzp = DetermineTimeZoneOffset(tm, namedTz);
                }
 
-               /* timezone not specified? then find local timezone if possible */
+               /*
+                * 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)))
                {
                        /*
@@ -1427,17 +1427,40 @@ DecodeDateTime(char **field, int *ftype, int nf,
 
 /* 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.
+ * 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.
  */
-int
-DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
+static int
+DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp, pg_time_t *tp)
 {
        int                     date,
                                sec;
@@ -1453,17 +1476,11 @@ DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
                                after_isdst;
        int                     res;
 
-       if (tzp == session_timezone && HasCTZSet)
-       {
-               tm->tm_isdst = 0;               /* for lack of a better idea */
-               return CTimeZone;
-       }
-
        /*
         * 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.)
+        * 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;
@@ -1500,6 +1517,7 @@ DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
        {
                /* Non-DST zone, life is simple */
                tm->tm_isdst = before_isdst;
+               *tp = mytime - before_gmtoff;
                return -(int) before_gmtoff;
        }
 
@@ -1520,45 +1538,176 @@ DetermineTimeZoneOffset(struct pg_tm * tm, pg_tz *tzp)
                goto overflow;
 
        /*
-        * If both before or both after the boundary time, we know what to do
+        * 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)
+       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. Prefer
-        * the standard-time interpretation.
+        * 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.  Also,
+        * in some zones such as Europe/Dublin, there is widespread confusion
+        * about which time offset is "standard" time, so it's fortunate that our
+        * behavior doesn't depend on that.)
         */
-       if (after_isdst == 0)
+       if (beforetime > aftertime)
        {
-               tm->tm_isdst = after_isdst;
-               return -(int) after_gmtoff;
+               tm->tm_isdst = before_isdst;
+               *tp = beforetime;
+               return -(int) before_gmtoff;
        }
-       tm->tm_isdst = before_isdst;
-       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.
+ * (However, that happens only if we can match the given abbreviation to some
+ * abbreviation that appears in the IANA timezone data.  Otherwise, we fall
+ * back to doing DetermineTimeZoneOffset().)
+ */
+int
+DetermineTimeZoneAbbrevOffset(struct pg_tm *tm, const char *abbr, pg_tz *tzp)
+{
+       pg_time_t       t;
+       int                     zone_offset;
+       int                     abbr_offset;
+       int                     abbr_isdst;
+
+       /*
+        * 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.)
+        */
+       zone_offset = DetermineTimeZoneOffsetInternal(tm, tzp, &t);
+
+       /*
+        * Try to match the abbreviation to something in the zone definition.
+        */
+       if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
+                                                                                         &abbr_offset, &abbr_isdst))
+       {
+               /* Success, so use the abbrev-specific answers. */
+               tm->tm_isdst = abbr_isdst;
+               return abbr_offset;
+       }
+
+       /*
+        * No match, so use the answers we already got from
+        * DetermineTimeZoneOffsetInternal.
+        */
+       return zone_offset;
+}
+
+
+/* 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);
+       int                     zone_offset;
+       int                     abbr_offset;
+       int                     tz;
+       struct pg_tm tm;
+       fsec_t          fsec;
+
+       /*
+        * If the abbrev matches anything in the zone data, this is pretty easy.
+        */
+       if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
+                                                                                         &abbr_offset, isdst))
+               return abbr_offset;
+
+       /*
+        * Else, break down the timestamp so we can use DetermineTimeZoneOffset.
+        */
+       if (timestamp2tm(ts, &tz, &tm, &fsec, NULL, tzp) != 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("timestamp out of range")));
+
+       zone_offset = DetermineTimeZoneOffset(&tm, tzp);
+       *isdst = tm.tm_isdst;
+       return zone_offset;
+}
+
+
+/* DetermineTimeZoneAbbrevOffsetInternal()
+ *
+ * Workhorse for above two functions: work from a pg_time_t probe instant.
+ * On success, return GMT offset and DST status into *offset and *isdst.
+ */
+static bool
+DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
+                                                                         int *offset, 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))
+       {
+               /* Change sign to agree with DetermineTimeZoneOffset() */
+               *offset = (int) -gmtoff;
+               return true;
+       }
+       return false;
+}
+
+
 /* DecodeTimeOnly()
  * Interpret parsed string as time fields only.
  * Returns 0 if successful, DTERR code if bogus input detected.
  *
  * Note that support for time zone is here for
- * SQL92 TIME WITH TIME ZONE, but it reveals
- * bogosity with SQL92 date/time standards, since
+ * SQL TIME WITH TIME ZONE, but it reveals
+ * bogosity with SQL date/time standards, since
  * we must infer a time zone from current time.
  * - thomas 2000-03-10
  * Allow specifying date to get a better time zone,
@@ -1566,7 +1715,7 @@ overflow:
  */
 int
 DecodeTimeOnly(char **field, int *ftype, int nf,
-                          int *dtype, struct pg_tm * tm, fsec_t *fsec, int *tzp)
+                          int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
 {
        int                     fmask = 0,
                                tmask,
@@ -1575,11 +1724,14 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
        int                     i;
        int                     val;
        int                     dterr;
-       bool            isjulian = FALSE;
-       bool            is2digits = FALSE;
-       bool            bc = FALSE;
+       bool            isjulian = false;
+       bool            is2digits = false;
+       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;
@@ -1703,7 +1855,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 
                                /*
                                 * Was this an "ISO time" with embedded field labels? An
-                                * example is "h04m05s06" - thomas 2001-02-04
+                                * example is "h04mm05s06" - thomas 2001-02-04
                                 */
                                if (ptype != 0)
                                {
@@ -1724,7 +1876,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                        }
 
                                        errno = 0;
-                                       val = strtoi(field[i], &cp, 10);
+                                       val = strtoint(field[i], &cp, 10);
                                        if (errno == ERANGE)
                                                return DTERR_FIELD_OVERFLOW;
 
@@ -1812,7 +1964,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                                return DTERR_FIELD_OVERFLOW;
                                                        tmask = DTK_DATE_M;
                                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
-                                                       isjulian = TRUE;
+                                                       isjulian = true;
 
                                                        if (*cp == '.')
                                                        {
@@ -1822,12 +1974,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                                time = strtod(cp, &cp);
                                                                if (*cp != '\0' || errno != 0)
                                                                        return DTERR_BAD_FORMAT;
-
-#ifdef HAVE_INT64_TIMESTAMP
                                                                time *= USECS_PER_DAY;
-#else
-                                                               time *= SECS_PER_DAY;
-#endif
                                                                dt2time(time,
                                                                                &tm->tm_hour, &tm->tm_min,
                                                                                &tm->tm_sec, fsec);
@@ -1912,7 +2059,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                        else
                                        {
                                                dterr = DecodeNumber(flen, field[i],
-                                                                                        FALSE,
+                                                                                        false,
                                                                                         (fmask | DTK_DATE_M),
                                                                                         &tmask, tm,
                                                                                         fsec, &is2digits);
@@ -1924,7 +2071,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 
                        case DTK_STRING:
                        case DTK_SPECIAL:
-                               type = DecodeSpecial(i, field[i], &val);
+                               /* 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;
 
@@ -1934,13 +2084,6 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                        case RESERV:
                                                switch (val)
                                                {
-                                                       case DTK_CURRENT:
-                                                               ereport(ERROR,
-                                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                                         errmsg("date/time value \"current\" is no longer supported")));
-                                                               return DTERR_BAD_FORMAT;
-                                                               break;
-
                                                        case DTK_NOW:
                                                                tmask = DTK_TIME_M;
                                                                *dtype = DTK_TIME;
@@ -1972,7 +2115,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                tm->tm_isdst = 1;
                                                if (tzp == NULL)
                                                        return DTERR_BAD_FORMAT;
-                                               *tzp += val * MINS_PER_HOUR;
+                                               *tzp -= val;
                                                break;
 
                                        case DTZ:
@@ -1985,7 +2128,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                tm->tm_isdst = 1;
                                                if (tzp == NULL)
                                                        return DTERR_BAD_FORMAT;
-                                               *tzp = val * MINS_PER_HOUR;
+                                               *tzp = -val;
                                                ftype[i] = DTK_TZ;
                                                break;
 
@@ -1993,11 +2136,18 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                tm->tm_isdst = 0;
                                                if (tzp == NULL)
                                                        return DTERR_BAD_FORMAT;
-                                               *tzp = val * MINS_PER_HOUR;
+                                               *tzp = -val;
                                                ftype[i] = DTK_TZ;
                                                break;
 
-                                       case IGNORE_DTF:
+                                       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:
@@ -2071,18 +2221,16 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
        else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
                tm->tm_hour += HOURS_PER_DAY / 2;
 
+       /*
+        * This should match the checks in make_timestamp_internal
+        */
        if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
                tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
                tm->tm_hour > HOURS_PER_DAY ||
        /* test for > 24:00:00 */
                (tm->tm_hour == HOURS_PER_DAY &&
                 (tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0)) ||
-#ifdef HAVE_INT64_TIMESTAMP
-               *fsec < INT64CONST(0) || *fsec > USECS_PER_SEC
-#else
-               *fsec < 0 || *fsec > 1
-#endif
-               )
+               *fsec < INT64CONST(0) || *fsec > USECS_PER_SEC)
                return DTERR_FIELD_OVERFLOW;
 
        if ((fmask & DTK_TIME_M) != DTK_TIME_M)
@@ -2114,7 +2262,39 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                }
        }
 
-       /* timezone not specified? then find local timezone if possible */
+       /*
+        * 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
+               {
+                       /* a date has to be specified */
+                       if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+                               return DTERR_BAD_FORMAT;
+                       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,
@@ -2130,6 +2310,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                        GetCurrentDateTime(tmp);
                else
                {
+                       /* a date has to be specified */
+                       if ((fmask & DTK_DATE_M) != DTK_DATE_M)
+                               return DTERR_BAD_FORMAT;
                        tmp->tm_year = tm->tm_year;
                        tmp->tm_mon = tm->tm_mon;
                        tmp->tm_mday = tm->tm_mday;
@@ -2151,19 +2334,19 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
  *     str: field to be parsed
  *     fmask: bitmask for field types already seen
  *     *tmask: receives bitmask for fields found here
- *     *is2digits: set to TRUE if we find 2-digit year
+ *     *is2digits: set to true if we find 2-digit year
  *     *tm: field values are stored into appropriate members of this struct
  */
 static int
 DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
-                  struct pg_tm * tm)
+                  struct pg_tm *tm)
 {
        fsec_t          fsec;
        int                     nf = 0;
        int                     i,
                                len;
        int                     dterr;
-       bool            haveTextMonth = FALSE;
+       bool            haveTextMonth = false;
        int                     type,
                                val,
                                dmask = 0;
@@ -2175,9 +2358,12 @@ DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
        while (*str != '\0' && nf < MAXDATEFIELDS)
        {
                /* skip field separators */
-               while (!isalnum((unsigned char) *str))
+               while (*str != '\0' && !isalnum((unsigned char) *str))
                        str++;
 
+               if (*str == '\0')
+                       return DTERR_BAD_FORMAT;        /* end of string after separator */
+
                field[nf] = str;
                if (isdigit((unsigned char) *str))
                {
@@ -2210,7 +2396,7 @@ DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
                        {
                                case MONTH:
                                        tm->tm_mon = val;
-                                       haveTextMonth = TRUE;
+                                       haveTextMonth = true;
                                        break;
 
                                default:
@@ -2261,9 +2447,9 @@ DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
  * Check valid year/month/day values, handle BC and DOY cases
  * Return 0 if okay, a DTERR code if not.
  */
-static int
+int
 ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
-                        struct pg_tm * tm)
+                        struct pg_tm *tm)
 {
        if (fmask & DTK_M(YEAR))
        {
@@ -2342,7 +2528,7 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
  */
 static int
 DecodeTime(char *str, int fmask, int range,
-                  int *tmask, struct pg_tm * tm, fsec_t *fsec)
+                  int *tmask, struct pg_tm *tm, fsec_t *fsec)
 {
        char       *cp;
        int                     dterr;
@@ -2350,13 +2536,13 @@ DecodeTime(char *str, int fmask, int range,
        *tmask = DTK_TIME_M;
 
        errno = 0;
-       tm->tm_hour = strtoi(str, &cp, 10);
+       tm->tm_hour = strtoint(str, &cp, 10);
        if (errno == ERANGE)
                return DTERR_FIELD_OVERFLOW;
        if (*cp != ':')
                return DTERR_BAD_FORMAT;
        errno = 0;
-       tm->tm_min = strtoi(cp + 1, &cp, 10);
+       tm->tm_min = strtoint(cp + 1, &cp, 10);
        if (errno == ERANGE)
                return DTERR_FIELD_OVERFLOW;
        if (*cp == '\0')
@@ -2384,7 +2570,7 @@ DecodeTime(char *str, int fmask, int range,
        else if (*cp == ':')
        {
                errno = 0;
-               tm->tm_sec = strtoi(cp + 1, &cp, 10);
+               tm->tm_sec = strtoint(cp + 1, &cp, 10);
                if (errno == ERANGE)
                        return DTERR_FIELD_OVERFLOW;
                if (*cp == '\0')
@@ -2402,18 +2588,11 @@ DecodeTime(char *str, int fmask, int range,
                return DTERR_BAD_FORMAT;
 
        /* do a sanity check */
-#ifdef HAVE_INT64_TIMESTAMP
        if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
                tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
                *fsec < INT64CONST(0) ||
                *fsec > USECS_PER_SEC)
                return DTERR_FIELD_OVERFLOW;
-#else
-       if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
-               tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
-               *fsec < 0 || *fsec > 1)
-               return DTERR_FIELD_OVERFLOW;
-#endif
 
        return 0;
 }
@@ -2425,7 +2604,7 @@ DecodeTime(char *str, int fmask, int range,
  */
 static int
 DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
-                        int *tmask, struct pg_tm * tm, fsec_t *fsec, bool *is2digits)
+                        int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
 {
        int                     val;
        char       *cp;
@@ -2434,7 +2613,7 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
        *tmask = 0;
 
        errno = 0;
-       val = strtoi(str, &cp, 10);
+       val = strtoint(str, &cp, 10);
        if (errno == ERANGE)
                return DTERR_FIELD_OVERFLOW;
        if (cp == str)
@@ -2481,7 +2660,7 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
 
                        /*
                         * Nothing so far; make a decision about what we think the input
-                        * is.  There used to be lots of heuristics here, but the
+                        * is.  There used to be lots of heuristics here, but the
                         * consensus now is to be paranoid.  It *must* be either
                         * YYYY-MM-DD (with a more-than-two-digit year field), or the
                         * field order defined by DateOrder.
@@ -2514,9 +2693,9 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
                        {
                                /*
                                 * We are at the first numeric field of a date that included a
-                                * textual month name.  We want to support the variants
+                                * textual month name.  We want to support the variants
                                 * MON-DD-YYYY, DD-MON-YYYY, and YYYY-MON-DD as unambiguous
-                                * inputs.      We will also accept MON-DD-YY or DD-MON-YY in
+                                * inputs.  We will also accept MON-DD-YY or DD-MON-YY in
                                 * either DMY or MDY modes, as well as YY-MON-DD in YMD mode.
                                 */
                                if (flen >= 3 || DateOrder == DATEORDER_YMD)
@@ -2545,10 +2724,10 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
                                if (flen >= 3 && *is2digits)
                                {
                                        /* Guess that first numeric field is day was wrong */
-                                       *tmask = DTK_M(DAY);            /* YEAR is already set */
+                                       *tmask = DTK_M(DAY);    /* YEAR is already set */
                                        tm->tm_mday = tm->tm_year;
                                        tm->tm_year = val;
-                                       *is2digits = FALSE;
+                                       *is2digits = false;
                                }
                                else
                                {
@@ -2610,7 +2789,7 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
  */
 static int
 DecodeNumberField(int len, char *str, int fmask,
-                               int *tmask, struct pg_tm * tm, fsec_t *fsec, bool *is2digits)
+                                 int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
 {
        char       *cp;
 
@@ -2630,11 +2809,7 @@ DecodeNumberField(int len, char *str, int fmask,
                frac = strtod(cp, NULL);
                if (errno != 0)
                        return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
                *fsec = rint(frac * 1000000);
-#else
-               *fsec = frac;
-#endif
                /* Now truncate off the fraction for further processing */
                *cp = '\0';
                len = strlen(str);
@@ -2642,29 +2817,21 @@ DecodeNumberField(int len, char *str, int fmask,
        /* No decimal point and no complete date yet? */
        else if ((fmask & DTK_DATE_M) != DTK_DATE_M)
        {
-               /* yyyymmdd? */
-               if (len == 8)
+               if (len >= 6)
                {
                        *tmask = DTK_DATE_M;
 
-                       tm->tm_mday = atoi(str + 6);
-                       *(str + 6) = '\0';
-                       tm->tm_mon = atoi(str + 4);
-                       *(str + 4) = '\0';
-                       tm->tm_year = atoi(str + 0);
-
-                       return DTK_DATE;
-               }
-               /* yymmdd? */
-               else if (len == 6)
-               {
-                       *tmask = DTK_DATE_M;
-                       tm->tm_mday = atoi(str + 4);
-                       *(str + 4) = '\0';
-                       tm->tm_mon = atoi(str + 2);
-                       *(str + 2) = '\0';
-                       tm->tm_year = atoi(str + 0);
-                       *is2digits = TRUE;
+                       /*
+                        * Start from end and consider first 2 as Day, next 2 as Month,
+                        * and the rest as Year.
+                        */
+                       tm->tm_mday = atoi(str + (len - 2));
+                       *(str + (len - 2)) = '\0';
+                       tm->tm_mon = atoi(str + (len - 4));
+                       *(str + (len - 4)) = '\0';
+                       tm->tm_year = atoi(str);
+                       if ((len - 4) == 2)
+                               *is2digits = true;
 
                        return DTK_DATE;
                }
@@ -2681,7 +2848,7 @@ DecodeNumberField(int len, char *str, int fmask,
                        *(str + 4) = '\0';
                        tm->tm_min = atoi(str + 2);
                        *(str + 2) = '\0';
-                       tm->tm_hour = atoi(str + 0);
+                       tm->tm_hour = atoi(str);
 
                        return DTK_TIME;
                }
@@ -2692,7 +2859,7 @@ DecodeNumberField(int len, char *str, int fmask,
                        tm->tm_sec = 0;
                        tm->tm_min = atoi(str + 2);
                        *(str + 2) = '\0';
-                       tm->tm_hour = atoi(str + 0);
+                       tm->tm_hour = atoi(str);
 
                        return DTK_TIME;
                }
@@ -2706,10 +2873,8 @@ DecodeNumberField(int len, char *str, int fmask,
  * 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.
  */
-static int
+int
 DecodeTimezone(char *str, int *tzp)
 {
        int                     tz;
@@ -2723,7 +2888,7 @@ DecodeTimezone(char *str, int *tzp)
                return DTERR_BAD_FORMAT;
 
        errno = 0;
-       hr = strtoi(str + 1, &cp, 10);
+       hr = strtoint(str + 1, &cp, 10);
        if (errno == ERANGE)
                return DTERR_TZDISP_OVERFLOW;
 
@@ -2731,13 +2896,13 @@ DecodeTimezone(char *str, int *tzp)
        if (*cp == ':')
        {
                errno = 0;
-               min = strtoi(cp + 1, &cp, 10);
+               min = strtoint(cp + 1, &cp, 10);
                if (errno == ERANGE)
                        return DTERR_TZDISP_OVERFLOW;
                if (*cp == ':')
                {
                        errno = 0;
-                       sec = strtoi(cp + 1, &cp, 10);
+                       sec = strtoint(cp + 1, &cp, 10);
                        if (errno == ERANGE)
                                return DTERR_TZDISP_OVERFLOW;
                }
@@ -2772,14 +2937,75 @@ DecodeTimezone(char *str, int *tzp)
        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.
- *
- * NB: this must *not* ereport on failure;
- * see commands/variable.c.
  */
 int
 DecodeSpecial(int field, char *lowtoken, int *val)
@@ -2788,11 +3014,10 @@ DecodeSpecial(int field, char *lowtoken, int *val)
        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);
-               if (tp == NULL)
-                       tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
+               tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
        }
        if (tp == NULL)
        {
@@ -2803,30 +3028,19 @@ DecodeSpecial(int field, char *lowtoken, int *val)
        {
                datecache[field] = tp;
                type = tp->type;
-               switch (type)
-               {
-                       case TZ:
-                       case DTZ:
-                       case DTZMOD:
-                               *val = FROMVAL(tp);
-                               break;
-
-                       default:
-                               *val = tp->value;
-                               break;
-               }
+               *val = tp->value;
        }
 
        return type;
 }
 
 
-/* ClearPgTM
+/* ClearPgTm
  *
  * Zero out a pg_tm and associated fsec_t
  */
 static inline void
-ClearPgTm(struct pg_tm * tm, fsec_t *fsec)
+ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
 {
        tm->tm_year = 0;
        tm->tm_mon = 0;
@@ -2851,9 +3065,9 @@ ClearPgTm(struct pg_tm * tm, fsec_t *fsec)
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
-                          int *dtype, struct pg_tm * tm, fsec_t *fsec)
+                          int *dtype, struct pg_tm *tm, fsec_t *fsec)
 {
-       bool            is_before = FALSE;
+       bool            is_before = false;
        char       *cp;
        int                     fmask = 0,
                                tmask,
@@ -2920,7 +3134,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                 * handle signed float numbers and signed year-month values.
                                 */
 
-                               /* FALL THROUGH */
+                               /* FALLTHROUGH */
 
                        case DTK_DATE:
                        case DTK_NUMBER:
@@ -2961,7 +3175,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                }
 
                                errno = 0;
-                               val = strtoi(field[i], &cp, 10);
+                               val = strtoint(field[i], &cp, 10);
                                if (errno == ERANGE)
                                        return DTERR_FIELD_OVERFLOW;
 
@@ -2970,7 +3184,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                        /* SQL "years-months" syntax */
                                        int                     val2;
 
-                                       val2 = strtoi(cp + 1, &cp, 10);
+                                       val2 = strtoint(cp + 1, &cp, 10);
                                        if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
                                                return DTERR_FIELD_OVERFLOW;
                                        if (*cp != '\0')
@@ -2978,6 +3192,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                        type = DTK_MONTH;
                                        if (*field[i] == '-')
                                                val2 = -val2;
+                                       if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+                                               ((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+                                               return DTERR_FIELD_OVERFLOW;
                                        val = val * MONTHS_PER_YEAR + val2;
                                        fval = 0;
                                }
@@ -3001,11 +3218,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                switch (type)
                                {
                                        case DTK_MICROSEC:
-#ifdef HAVE_INT64_TIMESTAMP
                                                *fsec += rint(val + fval);
-#else
-                                               *fsec += (val + fval) * 1e-6;
-#endif
                                                tmask = DTK_M(MICROSECOND);
                                                break;
 
@@ -3013,21 +3226,13 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                                /* avoid overflowing the fsec field */
                                                tm->tm_sec += val / 1000;
                                                val -= (val / 1000) * 1000;
-#ifdef HAVE_INT64_TIMESTAMP
                                                *fsec += rint((val + fval) * 1000);
-#else
-                                               *fsec += (val + fval) * 1e-3;
-#endif
                                                tmask = DTK_M(MILLISECOND);
                                                break;
 
                                        case DTK_SECOND:
                                                tm->tm_sec += val;
-#ifdef HAVE_INT64_TIMESTAMP
                                                *fsec += rint(fval * 1000000);
-#else
-                                               *fsec += fval;
-#endif
 
                                                /*
                                                 * If any subseconds were specified, consider this
@@ -3117,7 +3322,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                                break;
 
                                        case AGO:
-                                               is_before = TRUE;
+                                               is_before = true;
                                                type = val;
                                                break;
 
@@ -3149,12 +3354,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
        {
                int                     sec;
 
-#ifdef HAVE_INT64_TIMESTAMP
                sec = *fsec / USECS_PER_SEC;
                *fsec -= sec * USECS_PER_SEC;
-#else
-               TMODULO(*fsec, sec, 1.0);
-#endif
                tm->tm_sec += sec;
        }
 
@@ -3290,7 +3491,7 @@ ISO8601IntegerWidth(char *fieldstart)
  */
 int
 DecodeISO8601Interval(char *str,
-                                         int *dtype, struct pg_tm * tm, fsec_t *fsec)
+                                         int *dtype, struct pg_tm *tm, fsec_t *fsec)
 {
        bool            datepart = true;
        bool            havefield = false;
@@ -3324,7 +3525,7 @@ DecodeISO8601Interval(char *str,
                        return dterr;
 
                /*
-                * Note: we could step off the end of the string here.  Code below
+                * Note: we could step off the end of the string here.  Code below
                 * *must* exit the loop if unit == '\0'.
                 */
                unit = *str++;
@@ -3364,6 +3565,7 @@ DecodeISO8601Interval(char *str,
                                                continue;
                                        }
                                        /* Else fall through to extended alternative format */
+                                       /* FALLTHROUGH */
                                case '-':               /* ISO 8601 4.4.3.3 Alternative Format,
                                                                 * Extended */
                                        if (havefield)
@@ -3442,6 +3644,7 @@ DecodeISO8601Interval(char *str,
                                                return 0;
                                        }
                                        /* Else fall through to extended alternative format */
+                                       /* FALLTHROUGH */
                                case ':':               /* ISO 8601 4.4.3.3 Alternative Format,
                                                                 * Extended */
                                        if (havefield)
@@ -3487,8 +3690,13 @@ DecodeISO8601Interval(char *str,
 
 /* DecodeUnits()
  * Decode text string using lookup table.
- * This routine supports time interval decoding
- * (hence, it need not recognize timezone names).
+ *
+ * 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)
@@ -3497,6 +3705,7 @@ DecodeUnits(int field, char *lowtoken, int *val)
        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);
@@ -3510,14 +3719,11 @@ DecodeUnits(int field, char *lowtoken, int *val)
        {
                deltacache[field] = tp;
                type = tp->type;
-               if (type == TZ || type == DTZ)
-                       *val = FROMVAL(tp);
-               else
-                       *val = tp->value;
+               *val = tp->value;
        }
 
        return type;
-}      /* DecodeUnits() */
+}                                                              /* DecodeUnits() */
 
 /*
  * Report an error detected by one of the datetime input processing routines.
@@ -3546,7 +3752,7 @@ DateTimeParseError(int dterr, const char *str, const char *datatype)
                                        (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
                                         errmsg("date/time field value out of range: \"%s\"",
                                                        str),
-                       errhint("Perhaps you need a different \"datestyle\" setting.")));
+                                        errhint("Perhaps you need a different \"datestyle\" setting.")));
                        break;
                case DTERR_INTERVAL_OVERFLOW:
                        ereport(ERROR,
@@ -3586,9 +3792,11 @@ datebsearch(const char *key, const datetkn *base, int nel)
                while (last >= base)
                {
                        position = base + ((last - base) >> 1);
-                       result = key[0] - position->token[0];
+                       /* 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;
@@ -3603,9 +3811,12 @@ datebsearch(const char *key, const datetkn *base, int nel)
 }
 
 /* EncodeTimezone()
- *             Append representation of a numeric timezone offset to str.
+ *             Copies representation of a numeric timezone offset to str.
+ *
+ * Returns a pointer to the new end of string.  No NUL terminator is put
+ * there; callers are responsible for NUL terminating str themselves.
  */
-static void
+static char *
 EncodeTimezone(char *str, int tz, int style)
 {
        int                     hour,
@@ -3618,23 +3829,33 @@ EncodeTimezone(char *str, int tz, int style)
        hour = min / MINS_PER_HOUR;
        min -= hour * MINS_PER_HOUR;
 
-       str += strlen(str);
        /* TZ is negated compared to sign we wish to display ... */
        *str++ = (tz <= 0 ? '+' : '-');
 
        if (sec != 0)
-               sprintf(str, "%02d:%02d:%02d", hour, min, sec);
+       {
+               str = pg_ltostr_zeropad(str, hour, 2);
+               *str++ = ':';
+               str = pg_ltostr_zeropad(str, min, 2);
+               *str++ = ':';
+               str = pg_ltostr_zeropad(str, sec, 2);
+       }
        else if (min != 0 || style == USE_XSD_DATES)
-               sprintf(str, "%02d:%02d", hour, min);
+       {
+               str = pg_ltostr_zeropad(str, hour, 2);
+               *str++ = ':';
+               str = pg_ltostr_zeropad(str, min, 2);
+       }
        else
-               sprintf(str, "%02d", hour);
+               str = pg_ltostr_zeropad(str, hour, 2);
+       return str;
 }
 
 /* EncodeDateOnly()
  * Encode date as local time.
  */
 void
-EncodeDateOnly(struct pg_tm * tm, int style, char *str)
+EncodeDateOnly(struct pg_tm *tm, int style, char *str)
 {
        Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
 
@@ -3643,48 +3864,70 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str)
                case USE_ISO_DATES:
                case USE_XSD_DATES:
                        /* compatible with ISO date formats */
-                       if (tm->tm_year > 0)
-                               sprintf(str, "%04d-%02d-%02d",
-                                               tm->tm_year, tm->tm_mon, tm->tm_mday);
-                       else
-                               sprintf(str, "%04d-%02d-%02d %s",
-                                               -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+                       *str++ = '-';
+                       str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       *str++ = '-';
+                       str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
                        break;
 
                case USE_SQL_DATES:
                        /* compatible with Oracle/Ingres date formats */
                        if (DateOrder == DATEORDER_DMY)
-                               sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
-                       else
-                               sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
-                       if (tm->tm_year > 0)
-                               sprintf(str + 5, "/%04d", tm->tm_year);
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                               *str++ = '/';
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       }
                        else
-                               sprintf(str + 5, "/%04d %s", -(tm->tm_year - 1), "BC");
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                               *str++ = '/';
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       }
+                       *str++ = '/';
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
                        break;
 
                case USE_GERMAN_DATES:
                        /* German-style date format */
-                       sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
-                       if (tm->tm_year > 0)
-                               sprintf(str + 5, ".%04d", tm->tm_year);
-                       else
-                               sprintf(str + 5, ".%04d %s", -(tm->tm_year - 1), "BC");
+                       str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       *str++ = '.';
+                       str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       *str++ = '.';
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
                        break;
 
                case USE_POSTGRES_DATES:
                default:
                        /* traditional date-only style for Postgres */
                        if (DateOrder == DATEORDER_DMY)
-                               sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
-                       else
-                               sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
-                       if (tm->tm_year > 0)
-                               sprintf(str + 5, "-%04d", tm->tm_year);
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                               *str++ = '-';
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       }
                        else
-                               sprintf(str + 5, "-%04d %s", -(tm->tm_year - 1), "BC");
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                               *str++ = '-';
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       }
+                       *str++ = '-';
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
                        break;
        }
+
+       if (tm->tm_year <= 0)
+       {
+               memcpy(str, " BC", 3);  /* Don't copy NUL */
+               str += 3;
+       }
+       *str = '\0';
 }
 
 
@@ -3697,15 +3940,16 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str)
  * output.
  */
 void
-EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str)
+EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str)
 {
-       sprintf(str, "%02d:%02d:", tm->tm_hour, tm->tm_min);
-       str += strlen(str);
-
-       AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
-
+       str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+       *str++ = ':';
+       str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+       *str++ = ':';
+       str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
        if (print_tz)
-               EncodeTimezone(str, tz, style);
+               str = EncodeTimezone(str, tz, style);
+       *str = '\0';
 }
 
 
@@ -3726,7 +3970,7 @@ EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style,
  *     XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz
  */
 void
-EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str)
+EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str)
 {
        int                     day;
 
@@ -3743,106 +3987,129 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
                case USE_ISO_DATES:
                case USE_XSD_DATES:
                        /* Compatible with ISO-8601 date formats */
-
-                       if (style == USE_ISO_DATES)
-                               sprintf(str, "%04d-%02d-%02d %02d:%02d:",
-                                               (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-                                               tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
-                       else
-                               sprintf(str, "%04d-%02d-%02dT%02d:%02d:",
-                                               (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-                                               tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
-
-                       AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+                       *str++ = '-';
+                       str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       *str++ = '-';
+                       str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       *str++ = (style == USE_ISO_DATES) ? ' ' : 'T';
+                       str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+                       *str++ = ':';
+                       str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+                       *str++ = ':';
+                       str = AppendTimestampSeconds(str, tm, fsec);
                        if (print_tz)
-                               EncodeTimezone(str, tz, style);
-
-                       if (tm->tm_year <= 0)
-                               sprintf(str + strlen(str), " BC");
+                               str = EncodeTimezone(str, tz, style);
                        break;
 
                case USE_SQL_DATES:
                        /* Compatible with Oracle/Ingres date formats */
-
                        if (DateOrder == DATEORDER_DMY)
-                               sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                               *str++ = '/';
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       }
                        else
-                               sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
-
-                       sprintf(str + 5, "/%04d %02d:%02d:",
-                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-                                       tm->tm_hour, tm->tm_min);
-
-                       AppendTimestampSeconds(str + strlen(str), tm, fsec);
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                               *str++ = '/';
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       }
+                       *str++ = '/';
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+                       *str++ = ' ';
+                       str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+                       *str++ = ':';
+                       str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+                       *str++ = ':';
+                       str = AppendTimestampSeconds(str, tm, fsec);
 
                        /*
                         * Note: the uses of %.*s in this function would be risky if the
                         * timezone names ever contain non-ASCII characters.  However, all
-                        * TZ abbreviations in the Olson database are plain ASCII.
+                        * TZ abbreviations in the IANA database are plain ASCII.
                         */
-
                        if (print_tz)
                        {
                                if (tzn)
-                                       sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
+                               {
+                                       sprintf(str, " %.*s", MAXTZLEN, tzn);
+                                       str += strlen(str);
+                               }
                                else
-                                       EncodeTimezone(str, tz, style);
+                                       str = EncodeTimezone(str, tz, style);
                        }
-
-                       if (tm->tm_year <= 0)
-                               sprintf(str + strlen(str), " BC");
                        break;
 
                case USE_GERMAN_DATES:
                        /* German variant on European style */
-
-                       sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
-
-                       sprintf(str + 5, ".%04d %02d:%02d:",
-                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
-                                       tm->tm_hour, tm->tm_min);
-
-                       AppendTimestampSeconds(str + strlen(str), tm, fsec);
+                       str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       *str++ = '.';
+                       str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                       *str++ = '.';
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
+                       *str++ = ' ';
+                       str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+                       *str++ = ':';
+                       str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+                       *str++ = ':';
+                       str = AppendTimestampSeconds(str, tm, fsec);
 
                        if (print_tz)
                        {
                                if (tzn)
-                                       sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
+                               {
+                                       sprintf(str, " %.*s", MAXTZLEN, tzn);
+                                       str += strlen(str);
+                               }
                                else
-                                       EncodeTimezone(str, tz, style);
+                                       str = EncodeTimezone(str, tz, style);
                        }
-
-                       if (tm->tm_year <= 0)
-                               sprintf(str + strlen(str), " BC");
                        break;
 
                case USE_POSTGRES_DATES:
                default:
                        /* Backward-compatible with traditional Postgres abstime dates */
-
                        day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
                        tm->tm_wday = j2day(day);
-
-                       strncpy(str, days[tm->tm_wday], 3);
-                       strcpy(str + 3, " ");
-
+                       memcpy(str, days[tm->tm_wday], 3);
+                       str += 3;
+                       *str++ = ' ';
                        if (DateOrder == DATEORDER_DMY)
-                               sprintf(str + 4, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
+                       {
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                               *str++ = ' ';
+                               memcpy(str, months[tm->tm_mon - 1], 3);
+                               str += 3;
+                       }
                        else
-                               sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
-
-                       sprintf(str + 10, " %02d:%02d:", tm->tm_hour, tm->tm_min);
-
-                       AppendTimestampSeconds(str + strlen(str), tm, fsec);
-
-                       sprintf(str + strlen(str), " %04d",
-                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1));
+                       {
+                               memcpy(str, months[tm->tm_mon - 1], 3);
+                               str += 3;
+                               *str++ = ' ';
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                       }
+                       *str++ = ' ';
+                       str = pg_ltostr_zeropad(str, tm->tm_hour, 2);
+                       *str++ = ':';
+                       str = pg_ltostr_zeropad(str, tm->tm_min, 2);
+                       *str++ = ':';
+                       str = AppendTimestampSeconds(str, tm, fsec);
+                       *str++ = ' ';
+                       str = pg_ltostr_zeropad(str,
+                                                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
 
                        if (print_tz)
                        {
                                if (tzn)
-                                       sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn);
+                               {
+                                       sprintf(str, " %.*s", MAXTZLEN, tzn);
+                                       str += strlen(str);
+                               }
                                else
                                {
                                        /*
@@ -3851,15 +4118,19 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char
                                         * avoid formatting something which would be rejected by
                                         * the date/time parser later. - thomas 2001-10-19
                                         */
-                                       sprintf(str + strlen(str), " ");
-                                       EncodeTimezone(str, tz, style);
+                                       *str++ = ' ';
+                                       str = EncodeTimezone(str, tz, style);
                                }
                        }
-
-                       if (tm->tm_year <= 0)
-                               sprintf(str + strlen(str), " BC");
                        break;
        }
+
+       if (tm->tm_year <= 0)
+       {
+               memcpy(str, " BC", 3);  /* Don't copy NUL */
+               str += 3;
+       }
+       *str = '\0';
 }
 
 
@@ -3896,7 +4167,7 @@ AddPostgresIntPart(char *cp, int value, const char *units,
         * tad bizarre but it's how it worked before...
         */
        *is_before = (value < 0);
-       *is_zero = FALSE;
+       *is_zero = false;
        return cp + strlen(cp);
 }
 
@@ -3916,7 +4187,7 @@ AddVerboseIntPart(char *cp, int value, const char *units,
        else if (*is_before)
                value = -value;
        sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
-       *is_zero = FALSE;
+       *is_zero = false;
        return cp + strlen(cp);
 }
 
@@ -3941,7 +4212,7 @@ AddVerboseIntPart(char *cp, int value, const char *units,
  * "day-time literal"s (that look like ('4 5:6:7')
  */
 void
-EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 {
        char       *cp = str;
        int                     year = tm->tm_year;
@@ -3950,8 +4221,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
        int                     hour = tm->tm_hour;
        int                     min = tm->tm_min;
        int                     sec = tm->tm_sec;
-       bool            is_before = FALSE;
-       bool            is_zero = TRUE;
+       bool            is_before = false;
+       bool            is_zero = true;
 
        /*
         * The sign of year and month are guaranteed to match, since they are
@@ -4014,7 +4285,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                                        day_sign, abs(mday),
                                                        sec_sign, abs(hour), abs(min));
                                        cp += strlen(cp);
-                                       AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       *cp = '\0';
                                }
                                else if (has_year_month)
                                {
@@ -4024,13 +4296,15 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                {
                                        sprintf(cp, "%d %d:%02d:", mday, hour, min);
                                        cp += strlen(cp);
-                                       AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       *cp = '\0';
                                }
                                else
                                {
                                        sprintf(cp, "%d:%02d:", hour, min);
                                        cp += strlen(cp);
-                                       AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       *cp = '\0';
                                }
                        }
                        break;
@@ -4056,8 +4330,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                        {
                                if (sec < 0 || fsec < 0)
                                        *cp++ = '-';
-                               AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
-                               cp += strlen(cp);
+                               cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
                                *cp++ = 'S';
                                *cp++ = '\0';
                        }
@@ -4083,7 +4356,8 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                                (minus ? "-" : (is_before ? "+" : "")),
                                                abs(hour), abs(min));
                                cp += strlen(cp);
-                               AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                               cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                               *cp = '\0';
                        }
                        break;
 
@@ -4103,17 +4377,16 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                if (sec < 0 || (sec == 0 && fsec < 0))
                                {
                                        if (is_zero)
-                                               is_before = TRUE;
+                                               is_before = true;
                                        else if (!is_before)
                                                *cp++ = '-';
                                }
                                else if (is_before)
                                        *cp++ = '-';
-                               AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
-                               cp += strlen(cp);
+                               cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
                                sprintf(cp, " sec%s",
                                                (abs(sec) != 1 || fsec != 0) ? "s" : "");
-                               is_zero = FALSE;
+                               is_zero = false;
                        }
                        /* identically zero? then put in a unitless zero... */
                        if (is_zero)
@@ -4127,7 +4400,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
 
 /*
  * We've been burnt by stupid errors in the ordering of the datetkn tables
- * once too often.     Arrange to check them during postmaster start.
+ * once too often.  Arrange to check them during postmaster start.
  */
 static bool
 CheckDateTokenTable(const char *tablename, const datetkn *base, int nel)
@@ -4135,15 +4408,26 @@ CheckDateTokenTable(const char *tablename, const datetkn *base, int nel)
        bool            ok = true;
        int                     i;
 
-       for (i = 1; i < nel; i++)
+       for (i = 0; i < nel; i++)
        {
-               if (strncmp(base[i - 1].token, base[i].token, TOKMAXLEN) >= 0)
+               /* 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, "ordering error in %s table: \"%.*s\" >= \"%.*s\"",
+                       elog(LOG, "token too long in %s table: \"%.*s\"",
                                 tablename,
-                                TOKMAXLEN, base[i - 1].token,
-                                TOKMAXLEN, base[i].token);
+                                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;
                }
        }
@@ -4164,22 +4448,28 @@ CheckDateTokenTables(void)
 }
 
 /*
- * Common code for temporal protransform functions.  Types time, timetz,
- * timestamp and timestamptz each have a range of allowed precisions.  An
- * unspecified precision is rigorously equivalent to the highest specifiable
- * precision.
+ * Common code for temporal prosupport functions: simplify, if possible,
+ * a call to a temporal type's length-coercion function.
+ *
+ * Types time, timetz, timestamp and timestamptz each have a range of allowed
+ * precisions.  An unspecified precision is rigorously equivalent to the
+ * highest specifiable precision.  We can replace the function call with a
+ * no-op RelabelType if it is coercing to the same or higher precision as the
+ * input is known to have.
+ *
+ * The input Node is always a FuncExpr, but to reduce the #include footprint
+ * of datetime.h, we declare it as Node *.
  *
  * Note: timestamp_scale throws an error when the typmod is out of range, but
  * we can't get there from a cast: our typmodin will have caught it already.
  */
 Node *
-TemporalTransform(int32 max_precis, Node *node)
+TemporalSimplify(int32 max_precis, Node *node)
 {
-       FuncExpr   *expr = (FuncExpr *) node;
+       FuncExpr   *expr = castNode(FuncExpr, node);
        Node       *ret = NULL;
        Node       *typmod;
 
-       Assert(IsA(expr, FuncExpr));
        Assert(list_length(expr->args) >= 2);
 
        typmod = (Node *) lsecond(expr->args);
@@ -4201,26 +4491,88 @@ TemporalTransform(int32 max_precis, Node *node)
 /*
  * 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.
+ * 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.
  */
-void
-ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl,
-                                          struct tzEntry *abbrevs, int n)
+TimeZoneAbbrevTable *
+ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
 {
-       datetkn    *newtbl = tbl->abbrevs;
+       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++)
        {
-               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);
+               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 offset", newtbl, n));
+       Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n));
+
+       return tbl;
 }
 
 /*
@@ -4231,16 +4583,46 @@ ConvertTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl,
 void
 InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
 {
-       int                     i;
+       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);
 
-       timezonetktbl = tbl->abbrevs;
-       sztimezonetktbl = tbl->numabbrevs;
+       dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value);
 
-       /* clear date cache in case it contains any stale timezone names */
-       for (i = 0; i < MAXDATEFIELDS; i++)
-               datecache[i] = NULL;
+       /* 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).
@@ -4254,7 +4636,10 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
        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;
@@ -4282,7 +4667,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
                 * build tupdesc for result tuples. This must match this function's
                 * pg_proc entry!
                 */
-               tupdesc = CreateTemplateTupleDesc(3, false);
+               tupdesc = CreateTemplateTupleDesc(3);
                TupleDescInitEntry(tupdesc, (AttrNumber) 1, "abbrev",
                                                   TEXTOID, -1, 0);
                TupleDescInitEntry(tupdesc, (AttrNumber) 2, "utc_offset",
@@ -4298,31 +4683,65 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
        funcctx = SRF_PERCALL_SETUP();
        pindex = (int *) funcctx->user_fctx;
 
-       if (*pindex >= sztimezonetktbl)
+       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.
         */
-       strncpy(buffer, timezonetktbl[*pindex].token, TOKMAXLEN);
-       buffer[TOKMAXLEN] = '\0';       /* may not be null-terminated */
+       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_min = (-1) * FROMVAL(&timezonetktbl[*pindex]);
+       tm.tm_sec = gmtoffset;
        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);
+       values[2] = BoolGetDatum(is_dst);
 
        (*pindex)++;
 
@@ -4375,7 +4794,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
                 * build tupdesc for result tuples. This must match this function's
                 * pg_proc entry!
                 */
-               tupdesc = CreateTemplateTupleDesc(4, false);
+               tupdesc = CreateTemplateTupleDesc(4);
                TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
                                                   TEXTOID, -1, 0);
                TupleDescInitEntry(tupdesc, (AttrNumber) 2, "abbrev",
@@ -4412,8 +4831,16 @@ pg_timezone_names(PG_FUNCTION_ARGS)
                                                 &tzoff, &tm, &fsec, &tzn, tz) != 0)
                        continue;                       /* ignore if conversion fails */
 
-               /* Ignore zic's rather silly "Factory" time zone */
-               if (tzn && strcmp(tzn, "Local time zone must be set--see zic manual page") == 0)
+               /*
+                * IANA's rather silly "Factory" time zone used to emit ridiculously
+                * long "abbreviations" such as "Local time zone must be set--see zic
+                * manual page" or "Local time zone must be set--use tzsetup".  While
+                * modern versions of tzdb emit the much saner "-00", it seems some
+                * benighted packagers are hacking the IANA data so that it continues
+                * to produce these strings.  To prevent producing a weirdly wide
+                * abbrev column, reject ridiculously long abbreviations.
+                */
+               if (tzn && strlen(tzn) > 31)
                        continue;
 
                /* Found a displayable zone */