]> 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 e91c470304fa7ca7f613de74cd7236a80dd8bfee..e38bd930543f9e1a1462d6b330e7e1637660884b 100644 (file)
@@ -3,47 +3,58 @@
  * datetime.c
  *       Support functions for date/time types.
  *
- * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.197 2008/11/09 00:28:34 tgl Exp $
+ *       src/backend/utils/adt/datetime.c
  *
  *-------------------------------------------------------------------------
  */
 #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/builtins.h"
+#include "utils/date.h"
 #include "utils/datetime.h"
 #include "utils/memutils.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 is2digits, bool bc,
-                                                struct pg_tm * tm);
-static void TrimTrailingZeros(char *str);
+                                          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] =
@@ -52,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};
 
 
@@ -63,48 +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" */
-       {"abstime", IGNORE_DTF, 0}, /* for pre-v6.1 "Invalid Abstime" */
        {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},
@@ -112,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},
@@ -126,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},
@@ -170,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},
@@ -178,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 */
@@ -222,8 +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 */
-       {"reltime", IGNORE_DTF, 0}, /* pre-v6.1 "Undefined Reltime" */
+       {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative */
        {"s", UNITS, DTK_SECOND},
        {"sec", UNITS, DTK_SECOND},
        {DSECOND, UNITS, DTK_SECOND},
@@ -231,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 */
@@ -248,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};
 
 
 /*
@@ -280,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
@@ -313,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)
@@ -337,31 +313,30 @@ j2date(int jd, int *year, int *month, int *day)
        *year = y - 4800;
        quad = julian * 2141 / 65536;
        *day = julian - 7834 * quad / 256;
-       *month = (quad + 10) % 12 + 1;
+       *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() */
 
 
 /*
@@ -370,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;
@@ -387,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;
 
@@ -399,31 +374,137 @@ GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp)
 }
 
 
-/* TrimTrailingZeros()
- * ... resulting from printing numbers with full precision.
+/*
+ * 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
-TrimTrailingZeros(char *str)
+static char *
+AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
 {
-       int                     len = strlen(str);
+       Assert(precision >= 0);
 
-#if 0
-       /* chop off trailing one to cope with interval rounding */
-       if (strcmp(str + len - 4, "0001") == 0)
-       {
-               len -= 4;
-               *(str + len) = '\0';
-       }
-#endif
+       if (fillzeros)
+               cp = pg_ltostr_zeropad(cp, Abs(sec), 2);
+       else
+               cp = pg_ltostr(cp, Abs(sec));
 
-       /* chop off trailing zeros... but leave at least 2 fractional digits */
-       while (*(str + len - 1) == '0' && *(str + len - 3) != '.')
+       /* fsec_t is just an int32 */
+       if (fsec != 0)
        {
-               len--;
-               *(str + len) = '\0';
+               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.
+ *
+ * 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)
+{
+       return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
+}
+
+/*
+ * Multiply frac by scale (to produce seconds) and add to *tm & *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)
+{
+       int                     sec;
+
+       if (frac == 0)
+               return;
+       frac *= scale;
+       sec = (int) frac;
+       tm->tm_sec += sec;
+       frac -= sec;
+       *fsec += rint(frac * 1000000);
+}
+
+/* As above, but initial scale produces days */
+static void
+AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+{
+       int                     extra_days;
+
+       if (frac == 0)
+               return;
+       frac *= scale;
+       extra_days = (int) frac;
+       tm->tm_mday += extra_days;
+       frac -= extra_days;
+       AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+}
+
+/* Fetch a fractional-second value with suitable error checking */
+static int
+ParseFractionalSecond(char *cp, fsec_t *fsec)
+{
+       double          frac;
+
+       /* Caller should always pass the start of the fraction part */
+       Assert(*cp == '.');
+       errno = 0;
+       frac = strtod(cp, &cp);
+       /* check for parse failure */
+       if (*cp != '\0' || errno != 0)
+               return DTERR_BAD_FORMAT;
+       *fsec = rint(frac * 1000000);
+       return 0;
 }
 
+
 /* ParseDateTime()
  *     Break string into tokens based on a date/time context.
  *     Returns 0 if successful, DTERR code if bogus input detected.
@@ -678,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,
@@ -688,10 +769,15 @@ DecodeDateTime(char **field, int *ftype, int nf,
        int                     val;
        int                     dterr;
        int                     mer = HR24;
-       bool            haveTextMonth = 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;
 
        /*
         * We'll insist on at least all of the date fields, but initialize the
@@ -712,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;
@@ -726,11 +813,13 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                return DTERR_BAD_FORMAT;
 
                                        errno = 0;
-                                       val = strtoi(field[i], &cp, 10);
-                                       if (errno == ERANGE)
+                                       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;
+
                                        /* Get the time zone from the end of the string */
                                        dterr = DecodeTimezone(cp, tzp);
                                        if (dterr)
@@ -740,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).
@@ -749,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))))
@@ -833,6 +923,17 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                break;
 
                        case DTK_TIME:
+
+                               /*
+                                * This might be an ISO time following a "t" field.
+                                */
+                               if (ptype != 0)
+                               {
+                                       /* Sanity check; should not fail this test */
+                                       if (ptype != DTK_TIME)
+                                               return DTERR_BAD_FORMAT;
+                                       ptype = 0;
+                               }
                                dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
                                                                   &tmask, tm, fsec);
                                if (dterr)
@@ -843,8 +944,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                 * DecodeTime()
                                 */
                                /* test for > 24:00:00 */
-                               if (tm->tm_hour > 24 ||
-                                       (tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0)))
+                               if (tm->tm_hour > HOURS_PER_DAY ||
+                                       (tm->tm_hour == HOURS_PER_DAY &&
+                                        (tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0)))
                                        return DTERR_FIELD_OVERFLOW;
                                break;
 
@@ -875,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;
 
@@ -943,16 +1045,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                        tmask = DTK_M(SECOND);
                                                        if (*cp == '.')
                                                        {
-                                                               double          frac;
-
-                                                               frac = strtod(cp, &cp);
-                                                               if (*cp != '\0')
-                                                                       return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                               *fsec = rint(frac * 1000000);
-#else
-                                                               *fsec = frac;
-#endif
+                                                               dterr = ParseFractionalSecond(cp, fsec);
+                                                               if (dterr)
+                                                                       return dterr;
                                                                tmask = DTK_ALL_SECS_M;
                                                        }
                                                        break;
@@ -965,29 +1060,27 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                        break;
 
                                                case DTK_JULIAN:
-                                                       /***
-                                                        * previous field was a label for "julian date"?
-                                                        ***/
+                                                       /* previous field was a label for "julian date" */
+                                                       if (val < 0)
+                                                               return DTERR_FIELD_OVERFLOW;
                                                        tmask = DTK_DATE_M;
                                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                                                       isjulian = true;
+
                                                        /* fractional Julian Day? */
                                                        if (*cp == '.')
                                                        {
                                                                double          time;
 
+                                                               errno = 0;
                                                                time = strtod(cp, &cp);
-                                                               if (*cp != '\0')
+                                                               if (*cp != '\0' || errno != 0)
                                                                        return DTERR_BAD_FORMAT;
-
-                                                               tmask |= DTK_TIME_M;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                               dt2time(time * USECS_PER_DAY,
+                                                               time *= USECS_PER_DAY;
+                                                               dt2time(time,
                                                                                &tm->tm_hour, &tm->tm_min,
                                                                                &tm->tm_sec, fsec);
-#else
-                                                               dt2time(time * SECS_PER_DAY, &tm->tm_hour,
-                                                                               &tm->tm_min, &tm->tm_sec, fsec);
-#endif
+                                                               tmask |= DTK_TIME_M;
                                                        }
                                                        break;
 
@@ -1041,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,
@@ -1064,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;
 
@@ -1074,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;
@@ -1091,32 +1190,26 @@ DecodeDateTime(char **field, int *ftype, int nf,
                                                        case DTK_YESTERDAY:
                                                                tmask = DTK_DATE_M;
                                                                *dtype = DTK_DATE;
-                                                               GetCurrentDateTime(tm);
-                                                               j2date(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - 1,
-                                                                       &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
-                                                               tm->tm_hour = 0;
-                                                               tm->tm_min = 0;
-                                                               tm->tm_sec = 0;
+                                                               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);
                                                                break;
 
                                                        case DTK_TODAY:
                                                                tmask = DTK_DATE_M;
                                                                *dtype = DTK_DATE;
-                                                               GetCurrentDateTime(tm);
-                                                               tm->tm_hour = 0;
-                                                               tm->tm_min = 0;
-                                                               tm->tm_sec = 0;
+                                                               GetCurrentDateTime(&cur_tm);
+                                                               tm->tm_year = cur_tm.tm_year;
+                                                               tm->tm_mon = cur_tm.tm_mon;
+                                                               tm->tm_mday = cur_tm.tm_mday;
                                                                break;
 
                                                        case DTK_TOMORROW:
                                                                tmask = DTK_DATE_M;
                                                                *dtype = DTK_DATE;
-                                                               GetCurrentDateTime(tm);
-                                                               j2date(date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1,
-                                                                       &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
-                                                               tm->tm_hour = 0;
-                                                               tm->tm_min = 0;
-                                                               tm->tm_sec = 0;
+                                                               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);
                                                                break;
 
                                                        case DTK_ZULU:
@@ -1148,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;
 
@@ -1162,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:
@@ -1175,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:
@@ -1257,20 +1356,20 @@ DecodeDateTime(char **field, int *ftype, int nf,
                if (tmask & fmask)
                        return DTERR_BAD_FORMAT;
                fmask |= tmask;
-       }                               /* end loop over fields */
+       }                                                       /* end loop over fields */
 
        /* do final checking/adjustment of Y/M/D fields */
-       dterr = ValidateDate(fmask, is2digits, bc, tm);
+       dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
        if (dterr)
                return dterr;
 
        /* handle AM/PM */
-       if (mer != HR24 && tm->tm_hour > 12)
+       if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2)
                return DTERR_FIELD_OVERFLOW;
-       if (mer == AM && tm->tm_hour == 12)
+       if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2)
                tm->tm_hour = 0;
-       else if (mer == PM && tm->tm_hour != 12)
-               tm->tm_hour += 12;
+       else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
+               tm->tm_hour += HOURS_PER_DAY / 2;
 
        /* do additional checking for full date specs... */
        if (*dtype == DTK_DATE)
@@ -1295,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)))
                {
                        /*
@@ -1315,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;
@@ -1341,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;
@@ -1388,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;
        }
 
@@ -1408,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,
@@ -1454,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,
@@ -1463,10 +1724,14 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
        int                     i;
        int                     val;
        int                     dterr;
-       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;
@@ -1590,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)
                                {
@@ -1611,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;
 
@@ -1679,16 +1944,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                        tmask = DTK_M(SECOND);
                                                        if (*cp == '.')
                                                        {
-                                                               double          frac;
-
-                                                               frac = strtod(cp, &cp);
-                                                               if (*cp != '\0')
-                                                                       return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                               *fsec = rint(frac * 1000000);
-#else
-                                                               *fsec = frac;
-#endif
+                                                               dterr = ParseFractionalSecond(cp, fsec);
+                                                               if (dterr)
+                                                                       return dterr;
                                                                tmask = DTK_ALL_SECS_M;
                                                        }
                                                        break;
@@ -1701,27 +1959,26 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                                                        break;
 
                                                case DTK_JULIAN:
-                                                       /***
-                                                        * previous field was a label for "julian date"?
-                                                        ***/
+                                                       /* previous field was a label for "julian date" */
+                                                       if (val < 0)
+                                                               return DTERR_FIELD_OVERFLOW;
                                                        tmask = DTK_DATE_M;
                                                        j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+                                                       isjulian = true;
+
                                                        if (*cp == '.')
                                                        {
                                                                double          time;
 
+                                                               errno = 0;
                                                                time = strtod(cp, &cp);
-                                                               if (*cp != '\0')
+                                                               if (*cp != '\0' || errno != 0)
                                                                        return DTERR_BAD_FORMAT;
-
+                                                               time *= USECS_PER_DAY;
+                                                               dt2time(time,
+                                                                               &tm->tm_hour, &tm->tm_min,
+                                                                               &tm->tm_sec, fsec);
                                                                tmask |= DTK_TIME_M;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                               dt2time(time * USECS_PER_DAY,
-                                                               &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
-#else
-                                                               dt2time(time * SECS_PER_DAY,
-                                                               &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
-#endif
                                                        }
                                                        break;
 
@@ -1802,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);
@@ -1814,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;
 
@@ -1824,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;
@@ -1862,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:
@@ -1875,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;
 
@@ -1883,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:
@@ -1946,34 +2206,31 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                if (tmask & fmask)
                        return DTERR_BAD_FORMAT;
                fmask |= tmask;
-       }                               /* end loop over fields */
+       }                                                       /* end loop over fields */
 
        /* do final checking/adjustment of Y/M/D fields */
-       dterr = ValidateDate(fmask, is2digits, bc, tm);
+       dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
        if (dterr)
                return dterr;
 
        /* handle AM/PM */
-       if (mer != HR24 && tm->tm_hour > 12)
+       if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2)
                return DTERR_FIELD_OVERFLOW;
-       if (mer == AM && tm->tm_hour == 12)
+       if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2)
                tm->tm_hour = 0;
-       else if (mer == PM && tm->tm_hour != 12)
-               tm->tm_hour += 12;
+       else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
+               tm->tm_hour += HOURS_PER_DAY / 2;
 
-       if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > 59 ||
-               tm->tm_sec < 0 || tm->tm_sec > 60 || tm->tm_hour > 24 ||
+       /*
+        * 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 */
-#ifdef HAVE_INT64_TIMESTAMP
-               (tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0 ||
-                                                          *fsec > INT64CONST(0))) ||
-               *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC
-#else
-               (tm->tm_hour == 24 && (tm->tm_min > 0 || tm->tm_sec > 0 ||
-                                                          *fsec > 0)) ||
-               *fsec < 0 || *fsec >= 1
-#endif
-               )
+               (tm->tm_hour == HOURS_PER_DAY &&
+                (tm->tm_min > 0 || tm->tm_sec > 0 || *fsec > 0)) ||
+               *fsec < INT64CONST(0) || *fsec > USECS_PER_SEC)
                return DTERR_FIELD_OVERFLOW;
 
        if ((fmask & DTK_TIME_M) != DTK_TIME_M)
@@ -2005,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,
@@ -2021,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;
@@ -2042,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;
@@ -2066,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))
                {
@@ -2101,7 +2396,7 @@ DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
                        {
                                case MONTH:
                                        tm->tm_mon = val;
-                                       haveTextMonth = TRUE;
+                                       haveTextMonth = true;
                                        break;
 
                                default:
@@ -2152,12 +2447,17 @@ 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
-ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
+int
+ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
+                        struct pg_tm *tm)
 {
        if (fmask & DTK_M(YEAR))
        {
-               if (bc)
+               if (isjulian)
+               {
+                       /* tm_year is correct and should not be touched */
+               }
+               else if (bc)
                {
                        /* there is no year zero in AD/BC notation */
                        if (tm->tm_year <= 0)
@@ -2167,8 +2467,8 @@ ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
                }
                else if (is2digits)
                {
-                       /* allow 2-digit input for 1970-2069 AD; 00 is allowed */
-                       if (tm->tm_year < 0)                            /* just paranoia */
+                       /* process 1 or 2-digit input as 1970-2069 AD, allow '0' and '00' */
+                       if (tm->tm_year < 0)    /* just paranoia */
                                return DTERR_FIELD_OVERFLOW;
                        if (tm->tm_year < 70)
                                tm->tm_year += 2000;
@@ -2228,21 +2528,21 @@ ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
  */
 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;
 
        *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;
-       str = cp + 1;
        errno = 0;
-       tm->tm_min = strtoi(str, &cp, 10);
+       tm->tm_min = strtoint(cp + 1, &cp, 10);
        if (errno == ERANGE)
                return DTERR_FIELD_OVERFLOW;
        if (*cp == '\0')
@@ -2260,43 +2560,26 @@ DecodeTime(char *str, int fmask, int range,
        else if (*cp == '.')
        {
                /* always assume mm:ss.sss is MINUTE TO SECOND */
-               double          frac;
-
-               str = cp;
-               frac = strtod(str, &cp);
-               if (*cp != '\0')
-                       return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
-               *fsec = rint(frac * 1000000);
-#else
-               *fsec = frac;
-#endif
+               dterr = ParseFractionalSecond(cp, fsec);
+               if (dterr)
+                       return dterr;
                tm->tm_sec = tm->tm_min;
                tm->tm_min = tm->tm_hour;
                tm->tm_hour = 0;
        }
        else if (*cp == ':')
        {
-               str = cp + 1;
                errno = 0;
-               tm->tm_sec = strtoi(str, &cp, 10);
+               tm->tm_sec = strtoint(cp + 1, &cp, 10);
                if (errno == ERANGE)
                        return DTERR_FIELD_OVERFLOW;
                if (*cp == '\0')
                        *fsec = 0;
                else if (*cp == '.')
                {
-                       double          frac;
-
-                       str = cp;
-                       frac = strtod(str, &cp);
-                       if (*cp != '\0')
-                               return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
-                       *fsec = rint(frac * 1000000);
-#else
-                       *fsec = frac;
-#endif
+                       dterr = ParseFractionalSecond(cp, fsec);
+                       if (dterr)
+                               return dterr;
                }
                else
                        return DTERR_BAD_FORMAT;
@@ -2305,16 +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 > 59 ||
-               tm->tm_sec < 0 || tm->tm_sec > 60 || *fsec < INT64CONST(0) ||
-               *fsec >= USECS_PER_SEC)
+       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 > 59 ||
-               tm->tm_sec < 0 || tm->tm_sec > 60 || *fsec < 0 || *fsec >= 1)
-               return DTERR_FIELD_OVERFLOW;
-#endif
 
        return 0;
 }
@@ -2326,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;
@@ -2335,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)
@@ -2343,8 +2621,6 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
 
        if (*cp == '.')
        {
-               double          frac;
-
                /*
                 * More than two digits before decimal point? Then could be a date or
                 * a run-together time: 2001.360 20011225 040506.789
@@ -2360,15 +2636,10 @@ DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
                        return 0;
                }
 
-               frac = strtod(cp, &cp);
-               if (*cp != '\0')
-                       return DTERR_BAD_FORMAT;
-#ifdef HAVE_INT64_TIMESTAMP
-               *fsec = rint(frac * 1000000);
-#else
-               *fsec = frac;
-#endif
-       }
+               dterr = ParseFractionalSecond(cp, fsec);
+               if (dterr)
+                       return dterr;
+       }
        else if (*cp != '\0')
                return DTERR_BAD_FORMAT;
 
@@ -2389,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.
@@ -2422,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)
@@ -2453,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
                                {
@@ -2518,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;
 
@@ -2528,43 +2799,39 @@ DecodeNumberField(int len, char *str, int fmask,
         */
        if ((cp = strchr(str, '.')) != NULL)
        {
+               /*
+                * Can we use ParseFractionalSecond here?  Not clear whether trailing
+                * junk should be rejected ...
+                */
                double          frac;
 
+               errno = 0;
                frac = strtod(cp, NULL);
-#ifdef HAVE_INT64_TIMESTAMP
+               if (errno != 0)
+                       return DTERR_BAD_FORMAT;
                *fsec = rint(frac * 1000000);
-#else
-               *fsec = frac;
-#endif
+               /* Now truncate off the fraction for further processing */
                *cp = '\0';
                len = strlen(str);
        }
        /* 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;
                }
@@ -2581,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;
                }
@@ -2592,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;
                }
@@ -2606,13 +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.
- *
- * Note: we allow timezone offsets up to 13:59.  There are places that
- * use +1300 summer time.
  */
-static int
+int
 DecodeTimezone(char *str, int *tzp)
 {
        int                     tz;
@@ -2626,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;
 
@@ -2634,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;
                }
@@ -2655,11 +2917,12 @@ DecodeTimezone(char *str, int *tzp)
        else
                min = 0;
 
-       if (hr < 0 || hr > 14)
+       /* Range-check the values; see notes in datatype/timestamp.h */
+       if (hr < 0 || hr > MAX_TZDISP_HOUR)
                return DTERR_TZDISP_OVERFLOW;
-       if (min < 0 || min >= 60)
+       if (min < 0 || min >= MINS_PER_HOUR)
                return DTERR_TZDISP_OVERFLOW;
-       if (sec < 0 || sec >= 60)
+       if (sec < 0 || sec >= SECS_PER_MINUTE)
                return DTERR_TZDISP_OVERFLOW;
 
        tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE + sec;
@@ -2674,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)
@@ -2690,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)
        {
@@ -2705,27 +3028,34 @@ 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
+ *
+ * Zero out a pg_tm and associated fsec_t
+ */
+static inline void
+ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+{
+       tm->tm_year = 0;
+       tm->tm_mon = 0;
+       tm->tm_mday = 0;
+       tm->tm_hour = 0;
+       tm->tm_min = 0;
+       tm->tm_sec = 0;
+       *fsec = 0;
+}
+
+
 /* DecodeInterval()
  * Interpret previously parsed fields for general time interval.
  * Returns 0 if successful, DTERR code if bogus input detected.
+ * dtype, tm, fsec are output parameters.
  *
  * Allow "date" field DTK_DATE since this could be just
  *     an unsigned floating point number. - thomas 1997-11-16
@@ -2735,9 +3065,9 @@ DecodeSpecial(int field, char *lowtoken, int *val)
  */
 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,
@@ -2748,15 +3078,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
        double          fval;
 
        *dtype = DTK_DELTA;
-
        type = IGNORE_DTF;
-       tm->tm_year = 0;
-       tm->tm_mon = 0;
-       tm->tm_mday = 0;
-       tm->tm_hour = 0;
-       tm->tm_min = 0;
-       tm->tm_sec = 0;
-       *fsec = 0;
+       ClearPgTm(tm, fsec);
 
        /* read through list backwards to pick up units before values */
        for (i = nf - 1; i >= 0; i--)
@@ -2774,19 +3097,18 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                        case DTK_TZ:
 
                                /*
-                                * Timezone is a token with a leading sign character and
-                                * at least one digit; there could be ':', '.', '-'
-                                * embedded in it as well.
+                                * Timezone means a token with a leading sign character and at
+                                * least one digit; there could be ':', '.', '-' embedded in
+                                * it as well.
                                 */
                                Assert(*field[i] == '-' || *field[i] == '+');
 
                                /*
-                                * Try for hh:mm or hh:mm:ss.  If not, fall through to
-                                * DTK_NUMBER case, which can handle signed float numbers
-                                * and signed year-month values.
+                                * Check for signed hh:mm or hh:mm:ss.  If so, process exactly
+                                * like DTK_TIME case above, plus handling the sign.
                                 */
                                if (strchr(field[i] + 1, ':') != NULL &&
-                                       DecodeTime(field[i] + 1, fmask, INTERVAL_FULL_RANGE,
+                                       DecodeTime(field[i] + 1, fmask, range,
                                                           &tmask, tm, fsec) == 0)
                                {
                                        if (*field[i] == '-')
@@ -2804,10 +3126,15 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                         * are reading right to left.
                                         */
                                        type = DTK_DAY;
-                                       tmask = DTK_M(TZ);
                                        break;
                                }
-                               /* FALL THROUGH */
+
+                               /*
+                                * Otherwise, fall through to DTK_NUMBER case, which can
+                                * handle signed float numbers and signed year-month values.
+                                */
+
+                               /* FALLTHROUGH */
 
                        case DTK_DATE:
                        case DTK_NUMBER:
@@ -2828,17 +3155,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                                        break;
                                                case INTERVAL_MASK(HOUR):
                                                case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
-                                               case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
-                                               case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
                                                        type = DTK_HOUR;
                                                        break;
                                                case INTERVAL_MASK(MINUTE):
                                                case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
+                                               case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
                                                        type = DTK_MINUTE;
                                                        break;
                                                case INTERVAL_MASK(SECOND):
-                                               case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
                                                case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
+                                               case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
+                                               case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
                                                        type = DTK_SECOND;
                                                        break;
                                                default:
@@ -2848,16 +3175,16 @@ 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;
 
                                if (*cp == '-')
                                {
                                        /* SQL "years-months" syntax */
-                                       int             val2;
+                                       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')
@@ -2865,13 +3192,17 @@ 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;
                                }
                                else if (*cp == '.')
                                {
+                                       errno = 0;
                                        fval = strtod(cp, &cp);
-                                       if (*cp != '\0')
+                                       if (*cp != '\0' || errno != 0)
                                                return DTERR_BAD_FORMAT;
 
                                        if (*field[i] == '-')
@@ -2887,30 +3218,21 @@ 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;
 
                                        case DTK_MILLISEC:
-#ifdef HAVE_INT64_TIMESTAMP
+                                               /* avoid overflowing the fsec field */
+                                               tm->tm_sec += val / 1000;
+                                               val -= (val / 1000) * 1000;
                                                *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
@@ -2924,110 +3246,32 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
                                        case DTK_MINUTE:
                                                tm->tm_min += val;
-                                               if (fval != 0)
-                                               {
-                                                       int                     sec;
-
-                                                       fval *= SECS_PER_MINUTE;
-                                                       sec = fval;
-                                                       tm->tm_sec += sec;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                       *fsec += rint((fval - sec) * 1000000);
-#else
-                                                       *fsec += fval - sec;
-#endif
-                                               }
+                                               AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
                                                tmask = DTK_M(MINUTE);
                                                break;
 
                                        case DTK_HOUR:
                                                tm->tm_hour += val;
-                                               if (fval != 0)
-                                               {
-                                                       int                     sec;
-
-                                                       fval *= SECS_PER_HOUR;
-                                                       sec = fval;
-                                                       tm->tm_sec += sec;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                       *fsec += rint((fval - sec) * 1000000);
-#else
-                                                       *fsec += fval - sec;
-#endif
-                                               }
+                                               AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
                                                tmask = DTK_M(HOUR);
-                                               type = DTK_DAY;
+                                               type = DTK_DAY; /* set for next field */
                                                break;
 
                                        case DTK_DAY:
                                                tm->tm_mday += val;
-                                               if (fval != 0)
-                                               {
-                                                       int                     sec;
-
-                                                       fval *= SECS_PER_DAY;
-                                                       sec = fval;
-                                                       tm->tm_sec += sec;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                       *fsec += rint((fval - sec) * 1000000);
-#else
-                                                       *fsec += fval - sec;
-#endif
-                                               }
-                                               tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
+                                               AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                                               tmask = DTK_M(DAY);
                                                break;
 
                                        case DTK_WEEK:
                                                tm->tm_mday += val * 7;
-                                               if (fval != 0)
-                                               {
-                                                       int                     extra_days;
-
-                                                       fval *= 7;
-                                                       extra_days = (int32) fval;
-                                                       tm->tm_mday += extra_days;
-                                                       fval -= extra_days;
-                                                       if (fval != 0)
-                                                       {
-                                                               int                     sec;
-
-                                                               fval *= SECS_PER_DAY;
-                                                               sec = fval;
-                                                               tm->tm_sec += sec;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                               *fsec += rint((fval - sec) * 1000000);
-#else
-                                                               *fsec += fval - sec;
-#endif
-                                                       }
-                                               }
-                                               tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
+                                               AdjustFractDays(fval, tm, fsec, 7);
+                                               tmask = DTK_M(WEEK);
                                                break;
 
                                        case DTK_MONTH:
                                                tm->tm_mon += val;
-                                               if (fval != 0)
-                                               {
-                                                       int                     day;
-
-                                                       fval *= DAYS_PER_MONTH;
-                                                       day = fval;
-                                                       tm->tm_mday += day;
-                                                       fval -= day;
-                                                       if (fval != 0)
-                                                       {
-                                                               int                     sec;
-
-                                                               fval *= SECS_PER_DAY;
-                                                               sec = fval;
-                                                               tm->tm_sec += sec;
-#ifdef HAVE_INT64_TIMESTAMP
-                                                               *fsec += rint((fval - sec) * 1000000);
-#else
-                                                               *fsec += fval - sec;
-#endif
-                                                       }
-                                               }
+                                               AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
                                                tmask = DTK_M(MONTH);
                                                break;
 
@@ -3035,28 +3279,28 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                                tm->tm_year += val;
                                                if (fval != 0)
                                                        tm->tm_mon += fval * MONTHS_PER_YEAR;
-                                               tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+                                               tmask = DTK_M(YEAR);
                                                break;
 
                                        case DTK_DECADE:
                                                tm->tm_year += val * 10;
                                                if (fval != 0)
                                                        tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
-                                               tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+                                               tmask = DTK_M(DECADE);
                                                break;
 
                                        case DTK_CENTURY:
                                                tm->tm_year += val * 100;
                                                if (fval != 0)
                                                        tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
-                                               tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+                                               tmask = DTK_M(CENTURY);
                                                break;
 
                                        case DTK_MILLENNIUM:
                                                tm->tm_year += val * 1000;
                                                if (fval != 0)
                                                        tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
-                                               tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+                                               tmask = DTK_M(MILLENNIUM);
                                                break;
 
                                        default:
@@ -3078,12 +3322,12 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                                break;
 
                                        case AGO:
-                                               is_before = TRUE;
+                                               is_before = true;
                                                type = val;
                                                break;
 
                                        case RESERV:
-                                               tmask = (DTK_DATE_M || DTK_TIME_M);
+                                               tmask = (DTK_DATE_M | DTK_TIME_M);
                                                *dtype = val;
                                                break;
 
@@ -3110,18 +3354,14 @@ 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;
        }
 
        /*----------
         * The SQL standard defines the interval literal
-        *   '-1 1:00:00'
+        *       '-1 1:00:00'
         * to mean "negative 1 days and negative 1 hours", while Postgres
         * traditionally treats this as meaning "negative 1 days and positive
         * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
@@ -3131,14 +3371,14 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
         * This protects us against misinterpreting postgres-style dump output,
         * since the postgres-style output code has always put an explicit sign on
         * all fields following a negative field.  But note that SQL-spec output
-        * is ambiguous and can be misinterpreted on load!  (So it's best practice
+        * is ambiguous and can be misinterpreted on load!      (So it's best practice
         * to dump in postgres style, not SQL style.)
         *----------
         */
        if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
        {
                /* Check for additional explicit signs */
-               bool    more_signs = false;
+               bool            more_signs = false;
 
                for (i = 1; i < nf; i++)
                {
@@ -3152,8 +3392,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                if (!more_signs)
                {
                        /*
-                        * Rather than re-determining which field was field[0], just
-                        * force 'em all negative.
+                        * Rather than re-determining which field was field[0], just force
+                        * 'em all negative.
                         */
                        if (*fsec > 0)
                                *fsec = -(*fsec);
@@ -3188,10 +3428,275 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 }
 
 
+/*
+ * Helper functions to avoid duplicated code in DecodeISO8601Interval.
+ *
+ * Parse a decimal value and break it into integer and fractional parts.
+ * Returns 0 or DTERR code.
+ */
+static int
+ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+{
+       double          val;
+
+       if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
+               return DTERR_BAD_FORMAT;
+       errno = 0;
+       val = strtod(str, endptr);
+       /* did we not see anything that looks like a double? */
+       if (*endptr == str || errno != 0)
+               return DTERR_BAD_FORMAT;
+       /* watch out for overflow */
+       if (val < INT_MIN || val > INT_MAX)
+               return DTERR_FIELD_OVERFLOW;
+       /* be very sure we truncate towards zero (cf dtrunc()) */
+       if (val >= 0)
+               *ipart = (int) floor(val);
+       else
+               *ipart = (int) -floor(-val);
+       *fpart = val - *ipart;
+       return 0;
+}
+
+/*
+ * Determine number of integral digits in a valid ISO 8601 number field
+ * (we should ignore sign and any fraction part)
+ */
+static int
+ISO8601IntegerWidth(char *fieldstart)
+{
+       /* We might have had a leading '-' */
+       if (*fieldstart == '-')
+               fieldstart++;
+       return strspn(fieldstart, "0123456789");
+}
+
+
+/* DecodeISO8601Interval()
+ *     Decode an ISO 8601 time interval of the "format with designators"
+ *     (section 4.4.3.2) or "alternative format" (section 4.4.3.3)
+ *     Examples:  P1D  for 1 day
+ *                        PT1H for 1 hour
+ *                        P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min
+ *                        P0002-06-07T01:30:00 the same value in alternative format
+ *
+ * Returns 0 if successful, DTERR code if bogus input detected.
+ * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
+ * ISO8601, otherwise this could cause unexpected error messages.
+ * dtype, tm, fsec are output parameters.
+ *
+ *     A couple exceptions from the spec:
+ *      - a week field ('W') may coexist with other units
+ *      - allows decimals in fields other than the least significant unit.
+ */
+int
+DecodeISO8601Interval(char *str,
+                                         int *dtype, struct pg_tm *tm, fsec_t *fsec)
+{
+       bool            datepart = true;
+       bool            havefield = false;
+
+       *dtype = DTK_DELTA;
+       ClearPgTm(tm, fsec);
+
+       if (strlen(str) < 2 || str[0] != 'P')
+               return DTERR_BAD_FORMAT;
+
+       str++;
+       while (*str)
+       {
+               char       *fieldstart;
+               int                     val;
+               double          fval;
+               char            unit;
+               int                     dterr;
+
+               if (*str == 'T')                /* T indicates the beginning of the time part */
+               {
+                       datepart = false;
+                       havefield = false;
+                       str++;
+                       continue;
+               }
+
+               fieldstart = str;
+               dterr = ParseISO8601Number(str, &str, &val, &fval);
+               if (dterr)
+                       return dterr;
+
+               /*
+                * Note: we could step off the end of the string here.  Code below
+                * *must* exit the loop if unit == '\0'.
+                */
+               unit = *str++;
+
+               if (datepart)
+               {
+                       switch (unit)           /* before T: Y M W D */
+                       {
+                               case 'Y':
+                                       tm->tm_year += val;
+                                       tm->tm_mon += (fval * MONTHS_PER_YEAR);
+                                       break;
+                               case 'M':
+                                       tm->tm_mon += val;
+                                       AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                                       break;
+                               case 'W':
+                                       tm->tm_mday += val * 7;
+                                       AdjustFractDays(fval, tm, fsec, 7);
+                                       break;
+                               case 'D':
+                                       tm->tm_mday += val;
+                                       AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                                       break;
+                               case 'T':               /* ISO 8601 4.4.3.3 Alternative Format / Basic */
+                               case '\0':
+                                       if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
+                                       {
+                                               tm->tm_year += val / 10000;
+                                               tm->tm_mon += (val / 100) % 100;
+                                               tm->tm_mday += val % 100;
+                                               AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                                               if (unit == '\0')
+                                                       return 0;
+                                               datepart = false;
+                                               havefield = false;
+                                               continue;
+                                       }
+                                       /* Else fall through to extended alternative format */
+                                       /* FALLTHROUGH */
+                               case '-':               /* ISO 8601 4.4.3.3 Alternative Format,
+                                                                * Extended */
+                                       if (havefield)
+                                               return DTERR_BAD_FORMAT;
+
+                                       tm->tm_year += val;
+                                       tm->tm_mon += (fval * MONTHS_PER_YEAR);
+                                       if (unit == '\0')
+                                               return 0;
+                                       if (unit == 'T')
+                                       {
+                                               datepart = false;
+                                               havefield = false;
+                                               continue;
+                                       }
+
+                                       dterr = ParseISO8601Number(str, &str, &val, &fval);
+                                       if (dterr)
+                                               return dterr;
+                                       tm->tm_mon += val;
+                                       AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                                       if (*str == '\0')
+                                               return 0;
+                                       if (*str == 'T')
+                                       {
+                                               datepart = false;
+                                               havefield = false;
+                                               continue;
+                                       }
+                                       if (*str != '-')
+                                               return DTERR_BAD_FORMAT;
+                                       str++;
+
+                                       dterr = ParseISO8601Number(str, &str, &val, &fval);
+                                       if (dterr)
+                                               return dterr;
+                                       tm->tm_mday += val;
+                                       AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                                       if (*str == '\0')
+                                               return 0;
+                                       if (*str == 'T')
+                                       {
+                                               datepart = false;
+                                               havefield = false;
+                                               continue;
+                                       }
+                                       return DTERR_BAD_FORMAT;
+                               default:
+                                       /* not a valid date unit suffix */
+                                       return DTERR_BAD_FORMAT;
+                       }
+               }
+               else
+               {
+                       switch (unit)           /* after T: H M S */
+                       {
+                               case 'H':
+                                       tm->tm_hour += val;
+                                       AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+                                       break;
+                               case 'M':
+                                       tm->tm_min += val;
+                                       AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+                                       break;
+                               case 'S':
+                                       tm->tm_sec += val;
+                                       AdjustFractSeconds(fval, tm, fsec, 1);
+                                       break;
+                               case '\0':              /* ISO 8601 4.4.3.3 Alternative Format */
+                                       if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
+                                       {
+                                               tm->tm_hour += val / 10000;
+                                               tm->tm_min += (val / 100) % 100;
+                                               tm->tm_sec += val % 100;
+                                               AdjustFractSeconds(fval, tm, fsec, 1);
+                                               return 0;
+                                       }
+                                       /* Else fall through to extended alternative format */
+                                       /* FALLTHROUGH */
+                               case ':':               /* ISO 8601 4.4.3.3 Alternative Format,
+                                                                * Extended */
+                                       if (havefield)
+                                               return DTERR_BAD_FORMAT;
+
+                                       tm->tm_hour += val;
+                                       AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+                                       if (unit == '\0')
+                                               return 0;
+
+                                       dterr = ParseISO8601Number(str, &str, &val, &fval);
+                                       if (dterr)
+                                               return dterr;
+                                       tm->tm_min += val;
+                                       AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+                                       if (*str == '\0')
+                                               return 0;
+                                       if (*str != ':')
+                                               return DTERR_BAD_FORMAT;
+                                       str++;
+
+                                       dterr = ParseISO8601Number(str, &str, &val, &fval);
+                                       if (dterr)
+                                               return dterr;
+                                       tm->tm_sec += val;
+                                       AdjustFractSeconds(fval, tm, fsec, 1);
+                                       if (*str == '\0')
+                                               return 0;
+                                       return DTERR_BAD_FORMAT;
+
+                               default:
+                                       /* not a valid time unit suffix */
+                                       return DTERR_BAD_FORMAT;
+                       }
+               }
+
+               havefield = true;
+       }
+
+       return 0;
+}
+
+
 /* 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)
@@ -3200,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);
@@ -3213,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.
@@ -3249,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,
@@ -3280,32 +3783,40 @@ DateTimeParseError(int dterr, const char *str, const char *datatype)
 static const datetkn *
 datebsearch(const char *key, const datetkn *base, int nel)
 {
-       const datetkn *last = base + nel - 1,
-                          *position;
-       int                     result;
-
-       while (last >= base)
+       if (nel > 0)
        {
-               position = base + ((last - base) >> 1);
-               result = key[0] - position->token[0];
-               if (result == 0)
+               const datetkn *last = base + nel - 1,
+                                  *position;
+               int                     result;
+
+               while (last >= base)
                {
-                       result = strncmp(key, position->token, TOKMAXLEN);
+                       position = base + ((last - base) >> 1);
+                       /* precheck the first character for a bit of extra speed */
+                       result = (int) key[0] - (int) position->token[0];
                        if (result == 0)
-                               return position;
+                       {
+                               /* use strncmp so that we match truncated tokens */
+                               result = strncmp(key, position->token, TOKMAXLEN);
+                               if (result == 0)
+                                       return position;
+                       }
+                       if (result < 0)
+                               last = position - 1;
+                       else
+                               base = position + 1;
                }
-               if (result < 0)
-                       last = position - 1;
-               else
-                       base = position + 1;
        }
        return NULL;
 }
 
 /* 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,
@@ -3318,327 +3829,287 @@ 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.
  */
-int
-EncodeDateOnly(struct pg_tm * tm, int style, char *str)
+void
+EncodeDateOnly(struct pg_tm *tm, int style, char *str)
 {
-       if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
-               return -1;
+       Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
 
        switch (style)
        {
                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);
+                       {
+                               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);
-                       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_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;
        }
 
-       return TRUE;
-}      /* EncodeDateOnly() */
+       if (tm->tm_year <= 0)
+       {
+               memcpy(str, " BC", 3);  /* Don't copy NUL */
+               str += 3;
+       }
+       *str = '\0';
+}
 
 
 /* EncodeTimeOnly()
  * Encode time fields only.
+ *
+ * tm and fsec are the value to encode, print_tz determines whether to include
+ * a time zone (the difference between time and timetz types), tz is the
+ * numeric time zone offset, style is the date style, str is where to write the
+ * output.
  */
-int
-EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, int *tzp, int style, char *str)
+void
+EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str)
 {
-       if (tm->tm_hour < 0 || tm->tm_hour > HOURS_PER_DAY)
-               return -1;
-
-       sprintf(str, "%02d:%02d", tm->tm_hour, tm->tm_min);
-
-       /*
-        * Print fractional seconds if any.  The fractional field widths here
-        * should be equal to the larger of MAX_TIME_PRECISION and
-        * MAX_TIMESTAMP_PRECISION.
-        */
-       if (fsec != 0)
-       {
-#ifdef HAVE_INT64_TIMESTAMP
-               sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
-#else
-               sprintf(str + strlen(str), ":%013.10f", tm->tm_sec + fsec);
-#endif
-               TrimTrailingZeros(str);
-       }
-       else
-               sprintf(str + strlen(str), ":%02d", tm->tm_sec);
-
-       if (tzp != NULL)
-               EncodeTimezone(str, *tzp, style);
-
-       return TRUE;
-}      /* EncodeTimeOnly() */
+       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)
+               str = EncodeTimezone(str, tz, style);
+       *str = '\0';
+}
 
 
 /* EncodeDateTime()
  * Encode date and time interpreted as local time.
- * Support several date styles:
+ *
+ * tm and fsec are the value to encode, print_tz determines whether to include
+ * a time zone (the difference between timestamp and timestamptz types), tz is
+ * the numeric time zone offset, tzn is the textual time zone, which if
+ * specified will be used instead of tz by some styles, style is the date
+ * style, str is where to write the output.
+ *
+ * Supported date styles:
  *     Postgres - day mon hh:mm:ss yyyy tz
  *     SQL - mm/dd/yyyy hh:mm:ss.ss tz
  *     ISO - yyyy-mm-dd hh:mm:ss+/-tz
  *     German - dd.mm.yyyy hh:mm:ss tz
  *     XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz
- * Variants (affects order of month and day for Postgres and SQL styles):
- *     US - mm/dd/yyyy
- *     European - dd/mm/yyyy
  */
-int
-EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style, char *str)
+void
+EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str)
 {
        int                     day;
 
+       Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
+
        /*
-        * Why are we checking only the month field? Change this to an assert...
-        * if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR) return -1;
+        * Negative tm_isdst means we have no valid time zone translation.
         */
-       Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
+       if (tm->tm_isdst < 0)
+               print_tz = false;
 
        switch (style)
        {
                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);
-
-
-                       /*
-                        * Print fractional seconds if any.  The field widths here should
-                        * be at least equal to MAX_TIMESTAMP_PRECISION.
-                        *
-                        * In float mode, don't print fractional seconds before 1 AD,
-                        * since it's unlikely there's any precision left ...
-                        */
-#ifdef HAVE_INT64_TIMESTAMP
-                       if (fsec != 0)
-                       {
-                               sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
-                               TrimTrailingZeros(str);
-                       }
-#else
-                       if (fsec != 0 && tm->tm_year > 0)
-                       {
-                               sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
-                               TrimTrailingZeros(str);
-                       }
-#endif
-                       else
-                               sprintf(str + strlen(str), ":%02d", tm->tm_sec);
-
-                       /*
-                        * tzp == NULL indicates that we don't want *any* time zone info
-                        * in the output string. *tzn != NULL indicates that we have alpha
-                        * time zone info available. tm_isdst != -1 indicates that we have
-                        * a valid time zone translation.
-                        */
-                       if (tzp != NULL && tm->tm_isdst >= 0)
-                               EncodeTimezone(str, *tzp, style);
-
-                       if (tm->tm_year <= 0)
-                               sprintf(str + strlen(str), " 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);
+                       *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)
+                               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);
-                       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);
-
-                       /*
-                        * Print fractional seconds if any.  The field widths here should
-                        * be at least equal to MAX_TIMESTAMP_PRECISION.
-                        *
-                        * In float mode, don't print fractional seconds before 1 AD,
-                        * since it's unlikely there's any precision left ...
-                        */
-#ifdef HAVE_INT64_TIMESTAMP
-                       if (fsec != 0)
                        {
-                               sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
-                               TrimTrailingZeros(str);
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                               *str++ = '/';
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
                        }
-#else
-                       if (fsec != 0 && tm->tm_year > 0)
+                       else
                        {
-                               sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
-                               TrimTrailingZeros(str);
+                               str = pg_ltostr_zeropad(str, tm->tm_mon, 2);
+                               *str++ = '/';
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
                        }
-#endif
-                       else
-                               sprintf(str + strlen(str), ":%02d", tm->tm_sec);
+                       *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 (tzp != NULL && tm->tm_isdst >= 0)
+                       /*
+                        * 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 IANA database are plain ASCII.
+                        */
+                       if (print_tz)
                        {
-                               if (*tzn != NULL)
-                                       sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn);
+                               if (tzn)
+                               {
+                                       sprintf(str, " %.*s", MAXTZLEN, tzn);
+                                       str += strlen(str);
+                               }
                                else
-                                       EncodeTimezone(str, *tzp, 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);
-
-                       /*
-                        * Print fractional seconds if any.  The field widths here should
-                        * be at least equal to MAX_TIMESTAMP_PRECISION.
-                        *
-                        * In float mode, don't print fractional seconds before 1 AD,
-                        * since it's unlikely there's any precision left ...
-                        */
-#ifdef HAVE_INT64_TIMESTAMP
-                       if (fsec != 0)
-                       {
-                               sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
-                               TrimTrailingZeros(str);
-                       }
-#else
-                       if (fsec != 0 && tm->tm_year > 0)
+                       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)
                        {
-                               sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
-                               TrimTrailingZeros(str);
-                       }
-#endif
-                       else
-                               sprintf(str + strlen(str), ":%02d", tm->tm_sec);
-
-                       if (tzp != NULL && tm->tm_isdst >= 0)
-                       {
-                               if (*tzn != NULL)
-                                       sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn);
+                               if (tzn)
+                               {
+                                       sprintf(str, " %.*s", MAXTZLEN, tzn);
+                                       str += strlen(str);
+                               }
                                else
-                                       EncodeTimezone(str, *tzp, 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]);
-                       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);
-
-                       /*
-                        * Print fractional seconds if any.  The field widths here should
-                        * be at least equal to MAX_TIMESTAMP_PRECISION.
-                        *
-                        * In float mode, don't print fractional seconds before 1 AD,
-                        * since it's unlikely there's any precision left ...
-                        */
-#ifdef HAVE_INT64_TIMESTAMP
-                       if (fsec != 0)
                        {
-                               sprintf(str + strlen(str), ":%02d.%06d", tm->tm_sec, fsec);
-                               TrimTrailingZeros(str);
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
+                               *str++ = ' ';
+                               memcpy(str, months[tm->tm_mon - 1], 3);
+                               str += 3;
                        }
-#else
-                       if (fsec != 0 && tm->tm_year > 0)
+                       else
                        {
-                               sprintf(str + strlen(str), ":%09.6f", tm->tm_sec + fsec);
-                               TrimTrailingZeros(str);
+                               memcpy(str, months[tm->tm_mon - 1], 3);
+                               str += 3;
+                               *str++ = ' ';
+                               str = pg_ltostr_zeropad(str, tm->tm_mday, 2);
                        }
-#endif
-                       else
-                               sprintf(str + strlen(str), ":%02d", tm->tm_sec);
-
-                       sprintf(str + strlen(str), " %04d",
-                                       (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1));
-
-                       if (tzp != NULL && tm->tm_isdst >= 0)
+                       *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 != NULL)
-                                       sprintf(str + strlen(str), " %.*s", MAXTZLEN, *tzn);
+                               if (tzn)
+                               {
+                                       sprintf(str, " %.*s", MAXTZLEN, tzn);
+                                       str += strlen(str);
+                               }
                                else
                                {
                                        /*
@@ -3647,40 +4118,77 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, int *tzp, char **tzn, int style,
                                         * avoid formatting something which would be rejected by
                                         * the date/time parser later. - thomas 2001-10-19
                                         */
-                                       sprintf(str + strlen(str), " ");
-                                       EncodeTimezone(str, *tzp, style);
+                                       *str++ = ' ';
+                                       str = EncodeTimezone(str, tz, style);
                                }
                        }
-
-                       if (tm->tm_year <= 0)
-                               sprintf(str + strlen(str), " BC");
                        break;
        }
 
-       return TRUE;
+       if (tm->tm_year <= 0)
+       {
+               memcpy(str, " BC", 3);  /* Don't copy NUL */
+               str += 3;
+       }
+       *str = '\0';
 }
 
 
 /*
- * Helper function to avoid duplicated code in EncodeInterval below.
- * Note that any sign is stripped from the input seconds values.
+ * Helper functions to avoid duplicated code in EncodeInterval.
  */
-static void
-AppendSeconds(char *cp, int sec, fsec_t fsec)
+
+/* Append an ISO-8601-style interval field, but only if value isn't zero */
+static char *
+AddISO8601IntPart(char *cp, int value, char units)
 {
-       if (fsec == 0)
-       {
-               sprintf(cp, ":%02d", abs(sec));
-       }
-       else
+       if (value == 0)
+               return cp;
+       sprintf(cp, "%d%c", value, units);
+       return cp + strlen(cp);
+}
+
+/* Append a postgres-style interval field, but only if value isn't zero */
+static char *
+AddPostgresIntPart(char *cp, int value, const char *units,
+                                  bool *is_zero, bool *is_before)
+{
+       if (value == 0)
+               return cp;
+       sprintf(cp, "%s%s%d %s%s",
+                       (!*is_zero) ? " " : "",
+                       (*is_before && value > 0) ? "+" : "",
+                       value,
+                       units,
+                       (value != 1) ? "s" : "");
+
+       /*
+        * Each nonzero field sets is_before for (only) the next one.  This is a
+        * tad bizarre but it's how it worked before...
+        */
+       *is_before = (value < 0);
+       *is_zero = false;
+       return cp + strlen(cp);
+}
+
+/* Append a verbose-style interval field, but only if value isn't zero */
+static char *
+AddVerboseIntPart(char *cp, int value, const char *units,
+                                 bool *is_zero, bool *is_before)
+{
+       if (value == 0)
+               return cp;
+       /* first nonzero value sets is_before */
+       if (*is_zero)
        {
-#ifdef HAVE_INT64_TIMESTAMP
-               sprintf(cp, ":%02d.%06d", abs(sec), Abs(fsec));
-#else
-               sprintf(cp, ":%012.9f", fabs(sec + fsec));
-#endif
-               TrimTrailingZeros(cp);
+               *is_before = (value < 0);
+               value = abs(value);
        }
+       else if (*is_before)
+               value = -value;
+       sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+       *is_zero = false;
+       return cp + strlen(cp);
 }
 
 
@@ -3695,7 +4203,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec)
  * Actually, afaik, ISO 8601 does specify formats for "time
  * intervals...[of the]...format with time-unit designators", which
  * are pretty ugly.  The format looks something like
- *     P1Y1M1DT1H1M1.12345S
+ *        P1Y1M1DT1H1M1.12345S
  * but useful for exchanging data with computers instead of humans.
  * - ron 2003-07-14
  *
@@ -3703,42 +4211,42 @@ AppendSeconds(char *cp, int sec, fsec_t fsec)
  * "year-month literal"s (that look like '2-3') and
  * "day-time literal"s (that look like ('4 5:6:7')
  */
-int
-EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
+void
+EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 {
        char       *cp = str;
        int                     year = tm->tm_year;
-       int                     mon  = tm->tm_mon;
+       int                     mon = tm->tm_mon;
        int                     mday = tm->tm_mday;
        int                     hour = tm->tm_hour;
-       int                     min  = tm->tm_min;
-       int                     sec  = tm->tm_sec;
-       bool            is_before = FALSE;
-       bool            is_nonzero = FALSE;
+       int                     min = tm->tm_min;
+       int                     sec = tm->tm_sec;
+       bool            is_before = false;
+       bool            is_zero = true;
 
        /*
         * The sign of year and month are guaranteed to match, since they are
         * stored internally as "month". But we'll need to check for is_before and
-        * is_nonzero when determining the signs of day and hour/minute/seconds
+        * is_zero when determining the signs of day and hour/minute/seconds
         * fields.
         */
        switch (style)
        {
-               /* SQL Standard interval format */
+                       /* SQL Standard interval format */
                case INTSTYLE_SQL_STANDARD:
                        {
-                               bool has_negative = year < 0 || mon  < 0 ||
-                                                                       mday < 0 || hour < 0 ||
-                                                                       min  < 0 || sec  < 0 || fsec < 0;
-                               bool has_positive = year > 0 || mon  > 0 ||
-                                                                       mday > 0 || hour > 0 ||
-                                                                       min  > 0 || sec  > 0 || fsec > 0;
-                               bool has_year_month = year != 0 || mon  != 0;
-                               bool has_day_time   = mday != 0 || hour != 0 ||
-                                                                         min  != 0 || sec  != 0 || fsec != 0;
-                               bool has_day        = mday != 0;
-                               bool sql_standard_value = !(has_negative && has_positive) &&
-                                                                                 !(has_year_month && has_day_time);
+                               bool            has_negative = year < 0 || mon < 0 ||
+                               mday < 0 || hour < 0 ||
+                               min < 0 || sec < 0 || fsec < 0;
+                               bool            has_positive = year > 0 || mon > 0 ||
+                               mday > 0 || hour > 0 ||
+                               min > 0 || sec > 0 || fsec > 0;
+                               bool            has_year_month = year != 0 || mon != 0;
+                               bool            has_day_time = mday != 0 || hour != 0 ||
+                               min != 0 || sec != 0 || fsec != 0;
+                               bool            has_day = mday != 0;
+                               bool            sql_standard_value = !(has_negative && has_positive) &&
+                               !(has_year_month && has_day_time);
 
                                /*
                                 * SQL Standard wants only 1 "<sign>" preceding the whole
@@ -3748,11 +4256,11 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                {
                                        *cp++ = '-';
                                        year = -year;
-                                       mon  = -mon;
+                                       mon = -mon;
                                        mday = -mday;
                                        hour = -hour;
-                                       min  = -min;
-                                       sec  = -sec;
+                                       min = -min;
+                                       sec = -sec;
                                        fsec = -fsec;
                                }
 
@@ -3763,21 +4271,22 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                else if (!sql_standard_value)
                                {
                                        /*
-                                        * For non sql-standard interval values,
-                                        * force outputting the signs to avoid
-                                        * ambiguities with intervals with mixed
-                                        * sign components.
+                                        * For non sql-standard interval values, force outputting
+                                        * the signs to avoid ambiguities with intervals with
+                                        * mixed sign components.
                                         */
-                                       char year_sign = (year < 0 || mon < 0) ? '-' : '+';
-                                       char day_sign = (mday < 0) ? '-' : '+';
-                                       char sec_sign = (hour < 0 || min < 0 || sec < 0 || fsec < 0) ? '-' : '+';
+                                       char            year_sign = (year < 0 || mon < 0) ? '-' : '+';
+                                       char            day_sign = (mday < 0) ? '-' : '+';
+                                       char            sec_sign = (hour < 0 || min < 0 ||
+                                                                                       sec < 0 || fsec < 0) ? '-' : '+';
 
-                                       sprintf(cp, "%c%d-%d %c%d %c%d:%02d",
+                                       sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
                                                        year_sign, abs(year), abs(mon),
                                                        day_sign, abs(mday),
                                                        sec_sign, abs(hour), abs(min));
                                        cp += strlen(cp);
-                                       AppendSeconds(cp, sec, fsec);
+                                       cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       *cp = '\0';
                                }
                                else if (has_year_month)
                                {
@@ -3785,221 +4294,113 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str)
                                }
                                else if (has_day)
                                {
-                                       sprintf(cp, "%d %d:%02d", mday, hour, min);
+                                       sprintf(cp, "%d %d:%02d:", mday, hour, min);
                                        cp += strlen(cp);
-                                       AppendSeconds(cp, sec, fsec);
+                                       cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       *cp = '\0';
                                }
                                else
                                {
-                                       sprintf(cp, "%d:%02d", hour, min);
+                                       sprintf(cp, "%d:%02d:", hour, min);
                                        cp += strlen(cp);
-                                       AppendSeconds(cp, sec, fsec);
+                                       cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                                       *cp = '\0';
                                }
                        }
                        break;
 
-               /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
-               case INTSTYLE_POSTGRES:
-                       if (tm->tm_year != 0)
+                       /* ISO 8601 "time-intervals by duration only" */
+               case INTSTYLE_ISO_8601:
+                       /* special-case zero to avoid printing nothing */
+                       if (year == 0 && mon == 0 && mday == 0 &&
+                               hour == 0 && min == 0 && sec == 0 && fsec == 0)
                        {
-                               sprintf(cp, "%d year%s",
-                                               tm->tm_year, (tm->tm_year != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               is_before = (tm->tm_year < 0);
-                               is_nonzero = TRUE;
+                               sprintf(cp, "PT0S");
+                               break;
                        }
-
-                       if (tm->tm_mon != 0)
+                       *cp++ = 'P';
+                       cp = AddISO8601IntPart(cp, year, 'Y');
+                       cp = AddISO8601IntPart(cp, mon, 'M');
+                       cp = AddISO8601IntPart(cp, mday, 'D');
+                       if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
+                               *cp++ = 'T';
+                       cp = AddISO8601IntPart(cp, hour, 'H');
+                       cp = AddISO8601IntPart(cp, min, 'M');
+                       if (sec != 0 || fsec != 0)
                        {
-                               sprintf(cp, "%s%s%d mon%s", is_nonzero ? " " : "",
-                                               (is_before && tm->tm_mon > 0) ? "+" : "",
-                                               tm->tm_mon, (tm->tm_mon != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               is_before = (tm->tm_mon < 0);
-                               is_nonzero = TRUE;
+                               if (sec < 0 || fsec < 0)
+                                       *cp++ = '-';
+                               cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+                               *cp++ = 'S';
+                               *cp++ = '\0';
                        }
+                       break;
 
-                       if (tm->tm_mday != 0)
-                       {
-                               sprintf(cp, "%s%s%d day%s", is_nonzero ? " " : "",
-                                               (is_before && tm->tm_mday > 0) ? "+" : "",
-                                               tm->tm_mday, (tm->tm_mday != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               is_before = (tm->tm_mday < 0);
-                               is_nonzero = TRUE;
-                       }
+                       /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
+               case INTSTYLE_POSTGRES:
+                       cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
 
-                       if (!is_nonzero || tm->tm_hour != 0 || tm->tm_min != 0 ||
-                               tm->tm_sec != 0 || fsec != 0)
+                       /*
+                        * Ideally we should spell out "month" like we do for "year" and
+                        * "day".  However, for backward compatibility, we can't easily
+                        * fix this.  bjm 2011-05-24
+                        */
+                       cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
+                       cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
+                       if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
                        {
-                               int                     minus = (tm->tm_hour < 0 || tm->tm_min < 0 ||
-                                                                        tm->tm_sec < 0 || fsec < 0);
+                               bool            minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
 
-                               sprintf(cp, "%s%s%02d:%02d", is_nonzero ? " " : "",
+                               sprintf(cp, "%s%s%02d:%02d:",
+                                               is_zero ? "" : " ",
                                                (minus ? "-" : (is_before ? "+" : "")),
-                                               abs(tm->tm_hour), abs(tm->tm_min));
-                               cp += strlen(cp);
-                               AppendSeconds(cp, tm->tm_sec, fsec);
-                               cp += strlen(cp);
-                               is_nonzero = TRUE;
-                       }
-                       /* identically zero? then put in a unitless zero... */
-                       if (!is_nonzero)
-                       {
-                               strcat(cp, "0");
+                                               abs(hour), abs(min));
                                cp += strlen(cp);
+                               cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+                               *cp = '\0';
                        }
                        break;
 
-               /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
+                       /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
                case INTSTYLE_POSTGRES_VERBOSE:
                default:
-                       strcpy(cp, "@ ");
-                       cp += strlen(cp);
-
-                       if (tm->tm_year != 0)
-                       {
-                               int                     year = tm->tm_year;
-
-                               if (tm->tm_year < 0)
-                                       year = -year;
-
-                               sprintf(cp, "%d year%s", year,
-                                               (year != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               is_before = (tm->tm_year < 0);
-                               is_nonzero = TRUE;
-                       }
-
-                       if (tm->tm_mon != 0)
-                       {
-                               int                     mon = tm->tm_mon;
-
-                               if (is_before || (!is_nonzero && tm->tm_mon < 0))
-                                       mon = -mon;
-
-                               sprintf(cp, "%s%d mon%s", is_nonzero ? " " : "", mon,
-                                               (mon != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (tm->tm_mon < 0);
-                               is_nonzero = TRUE;
-                       }
-
-                       if (tm->tm_mday != 0)
-                       {
-                               int                     day = tm->tm_mday;
-
-                               if (is_before || (!is_nonzero && tm->tm_mday < 0))
-                                       day = -day;
-
-                               sprintf(cp, "%s%d day%s", is_nonzero ? " " : "", day,
-                                               (day != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (tm->tm_mday < 0);
-                               is_nonzero = TRUE;
-                       }
-                       if (tm->tm_hour != 0)
-                       {
-                               int                     hour = tm->tm_hour;
-
-                               if (is_before || (!is_nonzero && tm->tm_hour < 0))
-                                       hour = -hour;
-
-                               sprintf(cp, "%s%d hour%s", is_nonzero ? " " : "", hour,
-                                               (hour != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (tm->tm_hour < 0);
-                               is_nonzero = TRUE;
-                       }
-
-                       if (tm->tm_min != 0)
-                       {
-                               int                     min = tm->tm_min;
-
-                               if (is_before || (!is_nonzero && tm->tm_min < 0))
-                                       min = -min;
-
-                               sprintf(cp, "%s%d min%s", is_nonzero ? " " : "", min,
-                                               (min != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (tm->tm_min < 0);
-                               is_nonzero = TRUE;
-                       }
-
-                       /* fractional seconds? */
-                       if (fsec != 0)
+                       strcpy(cp, "@");
+                       cp++;
+                       cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before);
+                       cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before);
+                       cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
+                       cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
+                       cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
+                       if (sec != 0 || fsec != 0)
                        {
-                               fsec_t          sec;
-
-#ifdef HAVE_INT64_TIMESTAMP
-                               sec = fsec;
-                               if (is_before || (!is_nonzero && tm->tm_sec < 0))
+                               *cp++ = ' ';
+                               if (sec < 0 || (sec == 0 && fsec < 0))
                                {
-                                       tm->tm_sec = -tm->tm_sec;
-                                       sec = -sec;
-                                       is_before = TRUE;
+                                       if (is_zero)
+                                               is_before = true;
+                                       else if (!is_before)
+                                               *cp++ = '-';
                                }
-                               else if (!is_nonzero && tm->tm_sec == 0 && fsec < 0)
-                               {
-                                       sec = -sec;
-                                       is_before = TRUE;
-                               }
-                               sprintf(cp, "%s%d.%02d secs", is_nonzero ? " " : "",
-                                               tm->tm_sec, abs((int) rint(sec / 10000.0)));
-                               cp += strlen(cp);
-#else
-                               fsec += tm->tm_sec;
-                               sec = fsec;
-                               if (is_before || (!is_nonzero && fsec < 0))
-                                       sec = -sec;
-
-                               sprintf(cp, "%s%.2f secs", is_nonzero ? " " : "", sec);
-                               cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (fsec < 0);
-#endif
-                               is_nonzero = TRUE;
-                       }
-                       /* otherwise, integer seconds only? */
-                       else if (tm->tm_sec != 0)
-                       {
-                               int                     sec = tm->tm_sec;
-
-                               if (is_before || (!is_nonzero && tm->tm_sec < 0))
-                                       sec = -sec;
-
-                               sprintf(cp, "%s%d sec%s", is_nonzero ? " " : "", sec,
-                                               (sec != 1) ? "s" : "");
-                               cp += strlen(cp);
-                               if (!is_nonzero)
-                                       is_before = (tm->tm_sec < 0);
-                               is_nonzero = TRUE;
+                               else if (is_before)
+                                       *cp++ = '-';
+                               cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+                               sprintf(cp, " sec%s",
+                                               (abs(sec) != 1 || fsec != 0) ? "s" : "");
+                               is_zero = false;
                        }
                        /* identically zero? then put in a unitless zero... */
-                       if (!is_nonzero)
-                       {
-                               strcat(cp, "0");
-                               cp += strlen(cp);
-                       }
+                       if (is_zero)
+                               strcat(cp, " 0");
                        if (is_before)
-                       {
                                strcat(cp, " ago");
-                               cp += strlen(cp);
-                       }
                        break;
        }
-
-       return 0;
-}      /* EncodeInterval() */
+}
 
 
 /*
  * 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)
@@ -4007,14 +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, "token too long in %s table: \"%.*s\"",
+                                tablename,
+                                TOKMAXLEN + 1, base[i].token);
+                       ok = false;
+                       break;                          /* don't risk applying strcmp */
+               }
+               /* check for out of order */
+               if (i > 0 &&
+                       strcmp(base[i - 1].token, base[i].token) >= 0)
                {
-                       elog(LOG, "ordering error in %s table: \"%.*s\" >= \"%.*s\"",
+                       elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"",
                                 tablename,
-                                TOKMAXLEN, base[i - 1].token,
-                                TOKMAXLEN, base[i].token);
+                                base[i - 1].token,
+                                base[i].token);
                        ok = false;
                }
        }
@@ -4034,44 +4447,182 @@ CheckDateTokenTables(void)
        return ok;
 }
 
+/*
+ * 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 *
+TemporalSimplify(int32 max_precis, Node *node)
+{
+       FuncExpr   *expr = castNode(FuncExpr, node);
+       Node       *ret = NULL;
+       Node       *typmod;
+
+       Assert(list_length(expr->args) >= 2);
+
+       typmod = (Node *) lsecond(expr->args);
+
+       if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
+       {
+               Node       *source = (Node *) linitial(expr->args);
+               int32           old_precis = exprTypmod(source);
+               int32           new_precis = DatumGetInt32(((Const *) typmod)->constvalue);
+
+               if (new_precis < 0 || new_precis == max_precis ||
+                       (old_precis >= 0 && new_precis >= old_precis))
+                       ret = relabel_to_typmod(source, new_precis);
+       }
+
+       return ret;
+}
+
 /*
  * 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.  This data is in a temporary memory
- * context and must be copied to somewhere permanent.
+ * 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
-InstallTimeZoneAbbrevs(tzEntry *abbrevs, int n)
+TimeZoneAbbrevTable *
+ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
 {
-       datetkn    *newtbl;
+       TimeZoneAbbrevTable *tbl;
+       Size            tbl_size;
        int                     i;
 
-       /*
-        * Copy the data into TopMemoryContext and convert to datetkn format.
-        */
-       newtbl = (datetkn *) MemoryContextAlloc(TopMemoryContext,
-                                                                                       n * sizeof(datetkn));
+       /* 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++)
        {
-               strncpy(newtbl[i].token, abbrevs[i].abbrev, TOKMAXLEN);
-               newtbl[i].type = abbrevs[i].is_dst ? DTZ : TZ;
-               TOVAL(&newtbl[i], abbrevs[i].offset / 60);
+               struct tzEntry *abbr = abbrevs + i;
+
+               if (abbr->zone != NULL)
+               {
+                       Size            dsize;
+
+                       dsize = offsetof(DynamicZoneAbbrev, zone) +
+                               strlen(abbr->zone) + 1;
+                       tbl_size += MAXALIGN(dsize);
+               }
        }
 
+       /* Alloc the result ... */
+       tbl = malloc(tbl_size);
+       if (!tbl)
+               return NULL;
+
+       /* ... and fill it in */
+       tbl->tblsize = tbl_size;
+       tbl->numabbrevs = n;
+       /* in this loop, tbl_size reprises the space calculation above */
+       tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
+               n * sizeof(datetkn);
+       tbl_size = MAXALIGN(tbl_size);
+       for (i = 0; i < n; i++)
+       {
+               struct tzEntry *abbr = abbrevs + i;
+               datetkn    *dtoken = tbl->abbrevs + i;
+
+               /* use strlcpy to truncate name if necessary */
+               strlcpy(dtoken->token, abbr->abbrev, TOKMAXLEN + 1);
+               if (abbr->zone != NULL)
+               {
+                       /* Allocate a DynamicZoneAbbrev for this abbreviation */
+                       DynamicZoneAbbrev *dtza;
+                       Size            dsize;
+
+                       dtza = (DynamicZoneAbbrev *) ((char *) tbl + tbl_size);
+                       dtza->tz = NULL;
+                       strcpy(dtza->zone, abbr->zone);
+
+                       dtoken->type = DYNTZ;
+                       /* value is offset from table start to DynamicZoneAbbrev */
+                       dtoken->value = (int32) tbl_size;
+
+                       dsize = offsetof(DynamicZoneAbbrev, zone) +
+                               strlen(abbr->zone) + 1;
+                       tbl_size += MAXALIGN(dsize);
+               }
+               else
+               {
+                       dtoken->type = abbr->is_dst ? DTZ : TZ;
+                       dtoken->value = abbr->offset;
+               }
+       }
+
+       /* Assert the two loops above agreed on size calculations */
+       Assert(tbl->tblsize == tbl_size);
+
        /* Check the ordering, if testing */
-       Assert(CheckDateTokenTable("timezone offset", newtbl, n));
+       Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n));
+
+       return tbl;
+}
 
-       /* Now safe to replace existing table (if any) */
-       if (timezonetktbl)
-               pfree(timezonetktbl);
-       timezonetktbl = newtbl;
-       sztimezonetktbl = n;
+/*
+ * Install a TimeZoneAbbrevTable as the active table.
+ *
+ * Caller is responsible that the passed table doesn't go away while in use.
+ */
+void
+InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
+{
+       zoneabbrevtbl = tbl;
+       /* reset abbrevcache, which may contain pointers into old table */
+       memset(abbrevcache, 0, sizeof(abbrevcache));
+}
 
-       /* clear date cache in case it contains any stale timezone names */
-       for (i = 0; i < MAXDATEFIELDS; i++)
-               datecache[i] = NULL;
+/*
+ * Helper subroutine to locate pg_tz timezone for a dynamic abbreviation.
+ */
+static pg_tz *
+FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
+{
+       DynamicZoneAbbrev *dtza;
+
+       /* Just some sanity checks to prevent indexing off into nowhere */
+       Assert(tp->type == DYNTZ);
+       Assert(tp->value > 0 && tp->value < tbl->tblsize);
+
+       dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value);
+
+       /* Look up the underlying zone if we haven't already */
+       if (dtza->tz == NULL)
+       {
+               dtza->tz = pg_tzset(dtza->zone);
+
+               /*
+                * Ideally we'd let the caller ereport instead of doing it here, but
+                * then there is no way to report the bad time zone name.
+                */
+               if (dtza->tz == NULL)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                        errmsg("time zone \"%s\" not recognized",
+                                                       dtza->zone),
+                                        errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
+                                                          tp->token)));
+       }
+       return dtza->tz;
 }
 
+
 /*
  * This set-returning function reads all the available time zone abbreviations
  * and returns a set of (abbrev, utc_offset, is_dst).
@@ -4085,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;
@@ -4113,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",
@@ -4129,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)++;
 
@@ -4181,7 +4769,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
        int                     tzoff;
        struct pg_tm tm;
        fsec_t          fsec;
-       char       *tzn;
+       const char *tzn;
        Interval   *resInterval;
        struct pg_tm itm;
 
@@ -4206,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",
@@ -4243,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 */