Re: Fix overflow in DecodeInterval - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Fix overflow in DecodeInterval
Date
Msg-id 1228358.1648858010@sss.pgh.pa.us
Whole thread Raw
In response to Re: Fix overflow in DecodeInterval  (Joseph Koshakow <koshy44@gmail.com>)
Responses Re: Fix overflow in DecodeInterval  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
Joseph Koshakow <koshy44@gmail.com> writes:
> * The existing code for rounding had a lot of int to double
> casting and vice versa. I *think* that doubles are able to completely
> represent the range of ints. However doubles are not able to represent
> the full range of int64. After making the change I started noticing
> a lot of lossy behavior. One thought I had was to change the doubles
> to long doubles, but I wasn't able to figure out if long doubles could
> completely represent the range of int64. Especially since their size
> varies depending on the architecture. Does anyone know the answer to
> this?

I agree that relying on long double is not a great plan.  However,
I'm not seeing where there's a problem.  AFAICS the revised code
only uses doubles to represent fractions from the input, ie if you
write "123.456 hours" then the ".456" is carried around for awhile
as a float.  This does not seem likely to pose any real-world
problem; do you have a counterexample?

Anyway, I've spent today reviewing the code and cleaning up things
I didn't like, and attached is a v10.  I almost feel that this is
committable, but there is one thing that is bothering me.  The
part of DecodeInterval that does strange things with signs in the
INTSTYLE_SQL_STANDARD case (starting about line 3400 in datetime.c
before this patch, or line 3600 after) used to separately force the
hour, minute, second, and microsecond fields to negative.
Now it forces the merged tm_usec field to negative.  It seems to
me that this could give a different answer than before, if the
h/m/s/us values had been of different signs before they got merged.
However, I don't think that that situation is possible in SQL-spec-
compliant input, so it may not be a problem.  Again, a counterexample
would be interesting.

            regards, tom lane

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ba0ec35ac5..dae90e4a9e 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/pg_type.h"
+#include "common/int.h"
 #include "common/string.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -37,17 +38,31 @@ static int    DecodeNumber(int flen, char *field, bool haveTextMonth,
 static int    DecodeNumberField(int len, char *str,
                               int fmask, int *tmask,
                               struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int    DecodeTimeCommon(char *str, int fmask, int range,
+                             int *tmask, struct pg_itm *itm);
 static int    DecodeTime(char *str, int fmask, int range,
                        int *tmask, struct pg_tm *tm, fsec_t *fsec);
+static int    DecodeTimeForInterval(char *str, int fmask, int range,
+                                  int *tmask, struct pg_itm_in *itm_in);
 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 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 bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
+static bool AdjustFractMicroseconds(double frac, int64 scale,
+                                    struct pg_itm_in *itm_in);
+static bool AdjustFractDays(double frac, int scale,
+                            struct pg_itm_in *itm_in);
+static bool AdjustFractYears(double frac, int scale,
+                             struct pg_itm_in *itm_in);
+static bool AdjustMicroseconds(int64 val, double fval, int64 scale,
+                               struct pg_itm_in *itm_in);
+static bool AdjustDays(int64 val, int scale,
+                       struct pg_itm_in *itm_in);
+static bool AdjustMonths(int64 val, struct pg_itm_in *itm_in);
+static bool AdjustYears(int64 val, int scale,
+                        struct pg_itm_in *itm_in);
 static int    DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
                                             pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -425,7 +440,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
  * 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.
+ * Note that any sign is stripped from the input sec and fsec values.
  */
 static char *
 AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
@@ -471,7 +486,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)

         /*
          * 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(),
+         * enough to print the number.  We punt the problem to pg_ultostr(),
          * which will generate a correct answer in the minimum valid width.
          */
         if (value)
@@ -496,39 +511,163 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
     return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
 }

+
+/*
+ * Add val * multiplier to *sum.
+ * Returns true if successful, false on overflow.
+ */
+static bool
+int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
+{
+    int64        product;
+
+    if (pg_mul_s64_overflow(val, multiplier, &product) ||
+        pg_add_s64_overflow(*sum, product, sum))
+        return false;
+    return 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.
+ * Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractMicroseconds(double frac, int64 scale,
+                        struct pg_itm_in *itm_in)
 {
-    int            sec;
+    int64        usec;

+    /* Fast path for common case */
     if (frac == 0)
-        return;
+        return true;
+
+    /*
+     * We assume the input frac is less than 1, so overflow of frac or usec is
+     * not an issue for interesting values of scale.
+     */
     frac *= scale;
-    sec = (int) frac;
-    tm->tm_sec += sec;
-    frac -= sec;
-    *fsec += rint(frac * 1000000);
+    usec = (int64) frac;
+
+    /* Round off any fractional microsecond */
+    frac -= usec;
+    if (frac > 0.5)
+        usec++;
+    else if (frac < -0.5)
+        usec--;
+
+    return !pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec);
 }

-/* As above, but initial scale produces days */
-static void
-AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+/*
+ * Multiply frac by scale (to produce days).  Add the integral part of the
+ * result to itm_in->tm_mday, the fractional part to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustFractDays(double frac, int scale,
+                struct pg_itm_in *itm_in)
 {
     int            extra_days;

+    /* Fast path for common case */
     if (frac == 0)
-        return;
+        return true;
+
+    /*
+     * We assume the input frac is less than 1, so overflow of frac or
+     * extra_days is not an issue.
+     */
     frac *= scale;
     extra_days = (int) frac;
-    tm->tm_mday += extra_days;
+
+    /* ... but this could overflow, if tm_mday is already nonzero */
+    if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday))
+        return false;
+
+    /* Handle any fractional day */
     frac -= extra_days;
-    AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+    return AdjustFractMicroseconds(frac, USECS_PER_DAY, itm_in);
 }

+/*
+ * Multiply frac by scale (to produce years), then further scale up to months.
+ * Add the integral part of the result to itm_in->tm_mon, discarding any
+ * fractional part.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustFractYears(double frac, int scale,
+                 struct pg_itm_in *itm_in)
+{
+    /*
+     * As above, we assume abs(frac) < 1, so this can't overflow for any
+     * interesting value of scale.
+     */
+    int            extra_months = (int) rint(frac * scale * MONTHS_PER_YEAR);
+
+    return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon);
+}
+
+/*
+ * Add (val + fval) * scale to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustMicroseconds(int64 val, double fval, int64 scale,
+                   struct pg_itm_in *itm_in)
+{
+    /* Handle the integer part */
+    if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+        return false;
+    /* Handle the float part */
+    return AdjustFractMicroseconds(fval, scale, itm_in);
+}
+
+/*
+ * Multiply val by scale (to produce days) and add to itm_in->tm_mday.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustDays(int64 val, int scale, struct pg_itm_in *itm_in)
+{
+    int            days;
+
+    if (val < INT_MIN || val > INT_MAX)
+        return false;
+    return !pg_mul_s32_overflow((int32) val, scale, &days) &&
+        !pg_add_s32_overflow(itm_in->tm_mday, days, &itm_in->tm_mday);
+}
+
+/*
+ * Add val to itm_in->tm_mon (no need for scale here, as val is always
+ * in months already).
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustMonths(int64 val, struct pg_itm_in *itm_in)
+{
+    if (val < INT_MIN || val > INT_MAX)
+        return false;
+    return !pg_add_s32_overflow(itm_in->tm_mon, (int32) val, &itm_in->tm_mon);
+}
+
+/*
+ * Multiply val by scale (to produce years) and add to itm_in->tm_year.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustYears(int64 val, int scale,
+            struct pg_itm_in *itm_in)
+{
+    int            years;
+
+    if (val < INT_MIN || val > INT_MAX)
+        return false;
+    return !pg_mul_s32_overflow((int32) val, scale, &years) &&
+        !pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year);
+}
+
+
 /* Fetch a fractional-second value with suitable error checking */
 static int
 ParseFractionalSecond(char *cp, fsec_t *fsec)
@@ -2548,79 +2687,143 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
 }


-/* DecodeTime()
+/* DecodeTimeCommon()
  * Decode time string which includes delimiters.
  * Return 0 if okay, a DTERR code if not.
+ * tmask and itm are output parameters.
  *
- * Only check the lower limit on hours, since this same code can be
- * used to represent time spans.
+ * This code is shared between the timestamp and interval cases.
+ * We return a struct pg_itm (of which only the tm_usec, tm_sec, tm_min,
+ * and tm_hour fields are used) and let the wrapper functions below
+ * convert and range-check as necessary.
  */
 static int
-DecodeTime(char *str, int fmask, int range,
-           int *tmask, struct pg_tm *tm, fsec_t *fsec)
+DecodeTimeCommon(char *str, int fmask, int range,
+                 int *tmask, struct pg_itm *itm)
 {
     char       *cp;
     int            dterr;
+    fsec_t        fsec = 0;

     *tmask = DTK_TIME_M;

     errno = 0;
-    tm->tm_hour = strtoint(str, &cp, 10);
+    itm->tm_hour = strtoi64(str, &cp, 10);
     if (errno == ERANGE)
         return DTERR_FIELD_OVERFLOW;
     if (*cp != ':')
         return DTERR_BAD_FORMAT;
     errno = 0;
-    tm->tm_min = strtoint(cp + 1, &cp, 10);
+    itm->tm_min = strtoint(cp + 1, &cp, 10);
     if (errno == ERANGE)
         return DTERR_FIELD_OVERFLOW;
     if (*cp == '\0')
     {
-        tm->tm_sec = 0;
-        *fsec = 0;
+        itm->tm_sec = 0;
         /* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
         if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
         {
-            tm->tm_sec = tm->tm_min;
-            tm->tm_min = tm->tm_hour;
-            tm->tm_hour = 0;
+            if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
+                return DTERR_FIELD_OVERFLOW;
+            itm->tm_sec = itm->tm_min;
+            itm->tm_min = (int) itm->tm_hour;
+            itm->tm_hour = 0;
         }
     }
     else if (*cp == '.')
     {
         /* always assume mm:ss.sss is MINUTE TO SECOND */
-        dterr = ParseFractionalSecond(cp, fsec);
+        dterr = ParseFractionalSecond(cp, &fsec);
         if (dterr)
             return dterr;
-        tm->tm_sec = tm->tm_min;
-        tm->tm_min = tm->tm_hour;
-        tm->tm_hour = 0;
+        if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
+            return DTERR_FIELD_OVERFLOW;
+        itm->tm_sec = itm->tm_min;
+        itm->tm_min = (int) itm->tm_hour;
+        itm->tm_hour = 0;
     }
     else if (*cp == ':')
     {
         errno = 0;
-        tm->tm_sec = strtoint(cp + 1, &cp, 10);
+        itm->tm_sec = strtoint(cp + 1, &cp, 10);
         if (errno == ERANGE)
             return DTERR_FIELD_OVERFLOW;
-        if (*cp == '\0')
-            *fsec = 0;
-        else if (*cp == '.')
+        if (*cp == '.')
         {
-            dterr = ParseFractionalSecond(cp, fsec);
+            dterr = ParseFractionalSecond(cp, &fsec);
             if (dterr)
                 return dterr;
         }
-        else
+        else if (*cp != '\0')
             return DTERR_BAD_FORMAT;
     }
     else
         return DTERR_BAD_FORMAT;

-    /* do a sanity check */
-    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)
+    /* do a sanity check; but caller must check the range of tm_hour */
+    if (itm->tm_hour < 0 ||
+        itm->tm_min < 0 || itm->tm_min > MINS_PER_HOUR - 1 ||
+        itm->tm_sec < 0 || itm->tm_sec > SECS_PER_MINUTE ||
+        fsec < 0 || fsec > USECS_PER_SEC)
+        return DTERR_FIELD_OVERFLOW;
+
+    itm->tm_usec = (int) fsec;
+
+    return 0;
+}
+
+/* DecodeTime()
+ * Decode time string which includes delimiters.
+ * Return 0 if okay, a DTERR code if not.
+ *
+ * This version is used for timestamps.  The results are returned into
+ * the tm_hour/tm_min/tm_sec fields of *tm, and microseconds into *fsec.
+ */
+static int
+DecodeTime(char *str, int fmask, int range,
+           int *tmask, struct pg_tm *tm, fsec_t *fsec)
+{
+    struct pg_itm itm;
+    int            dterr;
+
+    dterr = DecodeTimeCommon(str, fmask, range,
+                             tmask, &itm);
+    if (dterr)
+        return dterr;
+
+    if (itm.tm_hour > INT_MAX)
+        return DTERR_FIELD_OVERFLOW;
+    tm->tm_hour = (int) itm.tm_hour;
+    tm->tm_min = itm.tm_min;
+    tm->tm_sec = itm.tm_sec;
+    *fsec = itm.tm_usec;
+
+    return 0;
+}
+
+/* DecodeTimeForInterval()
+ * Decode time string which includes delimiters.
+ * Return 0 if okay, a DTERR code if not.
+ *
+ * This version is used for intervals.  The results are returned into
+ * itm_in->tm_usec.
+ */
+static int
+DecodeTimeForInterval(char *str, int fmask, int range,
+                      int *tmask, struct pg_itm_in *itm_in)
+{
+    struct pg_itm itm;
+    int            dterr;
+
+    dterr = DecodeTimeCommon(str, fmask, range,
+                             tmask, &itm);
+    if (dterr)
+        return dterr;
+
+    itm_in->tm_usec = itm.tm_usec;
+    if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+        !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+        !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
         return DTERR_FIELD_OVERFLOW;

     return 0;
@@ -3064,27 +3267,24 @@ DecodeSpecial(int field, char *lowtoken, int *val)
 }


-/* ClearPgTm
+/* ClearPgItmIn
  *
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_itm_in
  */
 static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgItmIn(struct pg_itm_in *itm_in)
 {
-    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;
+    itm_in->tm_usec = 0;
+    itm_in->tm_mday = 0;
+    itm_in->tm_mon = 0;
+    itm_in->tm_year = 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.
+ * dtype and itm_in are output parameters.
  *
  * Allow "date" field DTK_DATE since this could be just
  *    an unsigned floating point number. - thomas 1997-11-16
@@ -3094,21 +3294,22 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
-               int *dtype, struct pg_tm *tm, fsec_t *fsec)
+               int *dtype, struct pg_itm_in *itm_in)
 {
     bool        is_before = false;
     char       *cp;
     int            fmask = 0,
                 tmask,
-                type;
+                type,
+                uval;
     int            i;
     int            dterr;
-    int            val;
+    int64        val;
     double        fval;

     *dtype = DTK_DELTA;
     type = IGNORE_DTF;
-    ClearPgTm(tm, fsec);
+    ClearPgItmIn(itm_in);

     /* read through list backwards to pick up units before values */
     for (i = nf - 1; i >= 0; i--)
@@ -3116,8 +3317,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
         switch (ftype[i])
         {
             case DTK_TIME:
-                dterr = DecodeTime(field[i], fmask, range,
-                                   &tmask, tm, fsec);
+                dterr = DecodeTimeForInterval(field[i], fmask, range,
+                                              &tmask, itm_in);
                 if (dterr)
                     return dterr;
                 type = DTK_DAY;
@@ -3137,16 +3338,15 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                  * like DTK_TIME case above, plus handling the sign.
                  */
                 if (strchr(field[i] + 1, ':') != NULL &&
-                    DecodeTime(field[i] + 1, fmask, range,
-                               &tmask, tm, fsec) == 0)
+                    DecodeTimeForInterval(field[i] + 1, fmask, range,
+                                          &tmask, itm_in) == 0)
                 {
                     if (*field[i] == '-')
                     {
-                        /* flip the sign on all fields */
-                        tm->tm_hour = -tm->tm_hour;
-                        tm->tm_min = -tm->tm_min;
-                        tm->tm_sec = -tm->tm_sec;
-                        *fsec = -(*fsec);
+                        /* flip the sign on time field */
+                        if (itm_in->tm_usec == PG_INT64_MIN)
+                            return DTERR_FIELD_OVERFLOW;
+                        itm_in->tm_usec = -itm_in->tm_usec;
                     }

                     /*
@@ -3204,7 +3404,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                 }

                 errno = 0;
-                val = strtoint(field[i], &cp, 10);
+                val = strtoi64(field[i], &cp, 10);
                 if (errno == ERANGE)
                     return DTERR_FIELD_OVERFLOW;

@@ -3221,10 +3421,10 @@ 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)
+                    if (pg_mul_s64_overflow(val, MONTHS_PER_YEAR, &val))
+                        return DTERR_FIELD_OVERFLOW;
+                    if (pg_add_s64_overflow(val, val2, &val))
                         return DTERR_FIELD_OVERFLOW;
-                    val = val * MONTHS_PER_YEAR + val2;
                     fval = 0;
                 }
                 else if (*cp == '.')
@@ -3247,21 +3447,20 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                 switch (type)
                 {
                     case DTK_MICROSEC:
-                        *fsec += rint(val + fval);
+                        if (!AdjustMicroseconds(val, fval, 1, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MICROSECOND);
                         break;

                     case DTK_MILLISEC:
-                        /* avoid overflowing the fsec field */
-                        tm->tm_sec += val / 1000;
-                        val -= (val / 1000) * 1000;
-                        *fsec += rint((val + fval) * 1000);
+                        if (!AdjustMicroseconds(val, fval, 1000, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MILLISECOND);
                         break;

                     case DTK_SECOND:
-                        tm->tm_sec += val;
-                        *fsec += rint(fval * 1000000);
+                        if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+                            return DTERR_FIELD_OVERFLOW;

                         /*
                          * If any subseconds were specified, consider this
@@ -3274,57 +3473,64 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                         break;

                     case DTK_MINUTE:
-                        tm->tm_min += val;
-                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+                        if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MINUTE);
                         break;

                     case DTK_HOUR:
-                        tm->tm_hour += val;
-                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+                        if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(HOUR);
                         type = DTK_DAY; /* set for next field */
                         break;

                     case DTK_DAY:
-                        tm->tm_mday += val;
-                        AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                        if (!AdjustDays(val, 1, itm_in) ||
+                            !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(DAY);
                         break;

                     case DTK_WEEK:
-                        tm->tm_mday += val * 7;
-                        AdjustFractDays(fval, tm, fsec, 7);
+                        if (!AdjustDays(val, 7, itm_in) ||
+                            !AdjustFractDays(fval, 7, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(WEEK);
                         break;

                     case DTK_MONTH:
-                        tm->tm_mon += val;
-                        AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                        if (!AdjustMonths(val, itm_in) ||
+                            !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MONTH);
                         break;

                     case DTK_YEAR:
-                        tm->tm_year += val;
-                        tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+                        if (!AdjustYears(val, 1, itm_in) ||
+                            !AdjustFractYears(fval, 1, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(YEAR);
                         break;

                     case DTK_DECADE:
-                        tm->tm_year += val * 10;
-                        tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+                        if (!AdjustYears(val, 10, itm_in) ||
+                            !AdjustFractYears(fval, 10, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(DECADE);
                         break;

                     case DTK_CENTURY:
-                        tm->tm_year += val * 100;
-                        tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+                        if (!AdjustYears(val, 100, itm_in) ||
+                            !AdjustFractYears(fval, 100, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(CENTURY);
                         break;

                     case DTK_MILLENNIUM:
-                        tm->tm_year += val * 1000;
-                        tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+                        if (!AdjustYears(val, 1000, itm_in) ||
+                            !AdjustFractYears(fval, 1000, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MILLENNIUM);
                         break;

@@ -3335,7 +3541,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,

             case DTK_STRING:
             case DTK_SPECIAL:
-                type = DecodeUnits(i, field[i], &val);
+                type = DecodeUnits(i, field[i], &uval);
                 if (type == IGNORE_DTF)
                     continue;

@@ -3343,17 +3549,17 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                 switch (type)
                 {
                     case UNITS:
-                        type = val;
+                        type = uval;
                         break;

                     case AGO:
                         is_before = true;
-                        type = val;
+                        type = uval;
                         break;

                     case RESERV:
                         tmask = (DTK_DATE_M | DTK_TIME_M);
-                        *dtype = val;
+                        *dtype = uval;
                         break;

                     default:
@@ -3374,16 +3580,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
     if (fmask == 0)
         return DTERR_BAD_FORMAT;

-    /* ensure fractional seconds are fractional */
-    if (*fsec != 0)
-    {
-        int            sec;
-
-        sec = *fsec / USECS_PER_SEC;
-        *fsec -= sec * USECS_PER_SEC;
-        tm->tm_sec += sec;
-    }
-
     /*----------
      * The SQL standard defines the interval literal
      *     '-1 1:00:00'
@@ -3420,33 +3616,30 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
              * Rather than re-determining which field was field[0], just force
              * 'em all negative.
              */
-            if (*fsec > 0)
-                *fsec = -(*fsec);
-            if (tm->tm_sec > 0)
-                tm->tm_sec = -tm->tm_sec;
-            if (tm->tm_min > 0)
-                tm->tm_min = -tm->tm_min;
-            if (tm->tm_hour > 0)
-                tm->tm_hour = -tm->tm_hour;
-            if (tm->tm_mday > 0)
-                tm->tm_mday = -tm->tm_mday;
-            if (tm->tm_mon > 0)
-                tm->tm_mon = -tm->tm_mon;
-            if (tm->tm_year > 0)
-                tm->tm_year = -tm->tm_year;
+            if (itm_in->tm_usec > 0)
+                itm_in->tm_usec = -itm_in->tm_usec;
+            if (itm_in->tm_mday > 0)
+                itm_in->tm_mday = -itm_in->tm_mday;
+            if (itm_in->tm_mon > 0)
+                itm_in->tm_mon = -itm_in->tm_mon;
+            if (itm_in->tm_year > 0)
+                itm_in->tm_year = -itm_in->tm_year;
         }
     }

     /* finally, AGO negates everything */
     if (is_before)
     {
-        *fsec = -(*fsec);
-        tm->tm_sec = -tm->tm_sec;
-        tm->tm_min = -tm->tm_min;
-        tm->tm_hour = -tm->tm_hour;
-        tm->tm_mday = -tm->tm_mday;
-        tm->tm_mon = -tm->tm_mon;
-        tm->tm_year = -tm->tm_year;
+        if (itm_in->tm_usec == PG_INT64_MIN ||
+            itm_in->tm_mday == INT_MIN ||
+            itm_in->tm_mon == INT_MIN ||
+            itm_in->tm_year == INT_MIN)
+            return DTERR_FIELD_OVERFLOW;
+
+        itm_in->tm_usec = -itm_in->tm_usec;
+        itm_in->tm_mday = -itm_in->tm_mday;
+        itm_in->tm_mon = -itm_in->tm_mon;
+        itm_in->tm_year = -itm_in->tm_year;
     }

     return 0;
@@ -3460,26 +3653,35 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
  * Returns 0 or DTERR code.
  */
 static int
-ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
-    double        val;
+    int            sign = 1;

-    if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
-        return DTERR_BAD_FORMAT;
+    *ipart = 0;
+    *fpart = 0.0;
+
+    /* Parse sign if there is any */
+    if (*str == '-')
+    {
+        sign = -1;
+        str++;
+    }
+
+    *endptr = str;
     errno = 0;
-    val = strtod(str, endptr);
-    /* did we not see anything that looks like a double? */
+
+    /* Parse int64 part if there is any */
+    if (isdigit((unsigned char) **endptr))
+        *ipart = strtoi64(*endptr, endptr, 10) * sign;
+
+    /* Parse fractional part if there is any */
+    if (**endptr == '.')
+        *fpart = strtod(*endptr, endptr) * sign;
+
+    /* did we not see anything that looks like a number? */
     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;
 }

@@ -3508,7 +3710,7 @@ ISO8601IntegerWidth(char *fieldstart)
  * 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.
+ * dtype and itm_in are output parameters.
  *
  *    A couple exceptions from the spec:
  *     - a week field ('W') may coexist with other units
@@ -3516,13 +3718,13 @@ ISO8601IntegerWidth(char *fieldstart)
  */
 int
 DecodeISO8601Interval(char *str,
-                      int *dtype, struct pg_tm *tm, fsec_t *fsec)
+                      int *dtype, struct pg_itm_in *itm_in)
 {
     bool        datepart = true;
     bool        havefield = false;

     *dtype = DTK_DELTA;
-    ClearPgTm(tm, fsec);
+    ClearPgItmIn(itm_in);

     if (strlen(str) < 2 || str[0] != 'P')
         return DTERR_BAD_FORMAT;
@@ -3531,7 +3733,7 @@ DecodeISO8601Interval(char *str,
     while (*str)
     {
         char       *fieldstart;
-        int            val;
+        int64        val;
         double        fval;
         char        unit;
         int            dterr;
@@ -3560,29 +3762,34 @@ DecodeISO8601Interval(char *str,
             switch (unit)        /* before T: Y M W D */
             {
                 case 'Y':
-                    tm->tm_year += val;
-                    tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+                    if (!AdjustYears(val, 1, itm_in) ||
+                        !AdjustFractYears(fval, 1, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'M':
-                    tm->tm_mon += val;
-                    AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                    if (!AdjustMonths(val, itm_in) ||
+                        !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'W':
-                    tm->tm_mday += val * 7;
-                    AdjustFractDays(fval, tm, fsec, 7);
+                    if (!AdjustDays(val, 7, itm_in) ||
+                        !AdjustFractDays(fval, 7, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'D':
-                    tm->tm_mday += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                    if (!AdjustDays(val, 1, itm_in) ||
+                        !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     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 (!AdjustYears(val / 10000, 1, itm_in) ||
+                            !AdjustMonths((val / 100) % 100, itm_in) ||
+                            !AdjustDays(val % 100, 1, itm_in) ||
+                            !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         if (unit == '\0')
                             return 0;
                         datepart = false;
@@ -3596,8 +3803,9 @@ DecodeISO8601Interval(char *str,
                     if (havefield)
                         return DTERR_BAD_FORMAT;

-                    tm->tm_year += val;
-                    tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+                    if (!AdjustYears(val, 1, itm_in) ||
+                        !AdjustFractYears(fval, 1, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     if (unit == '\0')
                         return 0;
                     if (unit == 'T')
@@ -3610,8 +3818,9 @@ DecodeISO8601Interval(char *str,
                     dterr = ParseISO8601Number(str, &str, &val, &fval);
                     if (dterr)
                         return dterr;
-                    tm->tm_mon += val;
-                    AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                    if (!AdjustMonths(val, itm_in) ||
+                        !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     if (*str == '\0')
                         return 0;
                     if (*str == 'T')
@@ -3627,8 +3836,9 @@ DecodeISO8601Interval(char *str,
                     dterr = ParseISO8601Number(str, &str, &val, &fval);
                     if (dterr)
                         return dterr;
-                    tm->tm_mday += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                    if (!AdjustDays(val, 1, itm_in) ||
+                        !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     if (*str == '\0')
                         return 0;
                     if (*str == 'T')
@@ -3648,24 +3858,25 @@ DecodeISO8601Interval(char *str,
             switch (unit)        /* after T: H M S */
             {
                 case 'H':
-                    tm->tm_hour += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+                    if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'M':
-                    tm->tm_min += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+                    if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'S':
-                    tm->tm_sec += val;
-                    AdjustFractSeconds(fval, tm, fsec, 1);
+                    if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     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);
+                        if (!AdjustMicroseconds(val / 10000, 0, USECS_PER_HOUR, itm_in) ||
+                            !AdjustMicroseconds((val / 100) % 100, 0, USECS_PER_MINUTE, itm_in) ||
+                            !AdjustMicroseconds(val % 100, 0, USECS_PER_SEC, itm_in) ||
+                            !AdjustFractMicroseconds(fval, 1, itm_in))
+                            return DTERR_FIELD_OVERFLOW;
                         return 0;
                     }
                     /* Else fall through to extended alternative format */
@@ -3675,16 +3886,16 @@ DecodeISO8601Interval(char *str,
                     if (havefield)
                         return DTERR_BAD_FORMAT;

-                    tm->tm_hour += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+                    if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     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 (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     if (*str == '\0')
                         return 0;
                     if (*str != ':')
@@ -3694,8 +3905,8 @@ DecodeISO8601Interval(char *str,
                     dterr = ParseISO8601Number(str, &str, &val, &fval);
                     if (dterr)
                         return dterr;
-                    tm->tm_sec += val;
-                    AdjustFractSeconds(fval, tm, fsec, 1);
+                    if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
                     if (*str == '\0')
                         return 0;
                     return DTERR_BAD_FORMAT;
@@ -4166,25 +4377,25 @@ EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char

 /* Append an ISO-8601-style interval field, but only if value isn't zero */
 static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
 {
     if (value == 0)
         return cp;
-    sprintf(cp, "%d%c", value, units);
+    sprintf(cp, "%lld%c", (long long) 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,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
                    bool *is_zero, bool *is_before)
 {
     if (value == 0)
         return cp;
-    sprintf(cp, "%s%s%d %s%s",
+    sprintf(cp, "%s%s%lld %s%s",
             (!*is_zero) ? " " : "",
             (*is_before && value > 0) ? "+" : "",
-            value,
+            (long long) value,
             units,
             (value != 1) ? "s" : "");

@@ -4199,7 +4410,7 @@ AddPostgresIntPart(char *cp, int value, const char *units,

 /* Append a verbose-style interval field, but only if value isn't zero */
 static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
                   bool *is_zero, bool *is_before)
 {
     if (value == 0)
@@ -4208,11 +4419,11 @@ AddVerboseIntPart(char *cp, int value, const char *units,
     if (*is_zero)
     {
         *is_before = (value < 0);
-        value = abs(value);
+        value = Abs(value);
     }
     else if (*is_before)
         value = -value;
-    sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+    sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s");
     *is_zero = false;
     return cp + strlen(cp);
 }
@@ -4238,15 +4449,16 @@ AddVerboseIntPart(char *cp, int value, const char *units,
  * "day-time literal"s (that look like ('4 5:6:7')
  */
 void
-EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_itm *itm, int style, char *str)
 {
     char       *cp = str;
-    int            year = tm->tm_year;
-    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;
+    int            year = itm->tm_year;
+    int            mon = itm->tm_mon;
+    int64        mday = itm->tm_mday;    /* tm_mday could be INT_MIN */
+    int64        hour = itm->tm_hour;
+    int            min = itm->tm_min;
+    int            sec = itm->tm_sec;
+    int            fsec = itm->tm_usec;
     bool        is_before = false;
     bool        is_zero = true;

@@ -4306,10 +4518,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
                     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%lld %c%lld:%02d:",
                             year_sign, abs(year), abs(mon),
-                            day_sign, abs(mday),
-                            sec_sign, abs(hour), abs(min));
+                            day_sign, (long long) Abs(mday),
+                            sec_sign, (long long) Abs(hour), abs(min));
                     cp += strlen(cp);
                     cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                     *cp = '\0';
@@ -4320,14 +4532,15 @@ 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, "%lld %lld:%02d:",
+                            (long long) mday, (long long) hour, min);
                     cp += strlen(cp);
                     cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                     *cp = '\0';
                 }
                 else
                 {
-                    sprintf(cp, "%d:%02d:", hour, min);
+                    sprintf(cp, "%lld:%02d:", (long long) hour, min);
                     cp += strlen(cp);
                     cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                     *cp = '\0';
@@ -4377,10 +4590,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
             {
                 bool        minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);

-                sprintf(cp, "%s%s%02d:%02d:",
+                sprintf(cp, "%s%s%02lld:%02d:",
                         is_zero ? "" : " ",
                         (minus ? "-" : (is_before ? "+" : "")),
-                        abs(hour), abs(min));
+                        (long long) Abs(hour), abs(min));
                 cp += strlen(cp);
                 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
                 *cp = '\0';
@@ -4668,7 +4881,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
     int            gmtoffset;
     bool        is_dst;
     unsigned char *p;
-    struct pg_tm tm;
+    struct pg_itm_in itm_in;
     Interval   *resInterval;

     /* stuff done only on the first call of the function */
@@ -4761,11 +4974,11 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)

     values[0] = CStringGetTextDatum(buffer);

-    /* Convert offset (in seconds) to an interval */
-    MemSet(&tm, 0, sizeof(struct pg_tm));
-    tm.tm_sec = gmtoffset;
+    /* Convert offset (in seconds) to an interval; can't overflow */
+    MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+    itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC;
     resInterval = (Interval *) palloc(sizeof(Interval));
-    tm2interval(&tm, 0, resInterval);
+    (void) itmin2interval(&itm_in, resInterval);
     values[1] = IntervalPGetDatum(resInterval);

     values[2] = BoolGetDatum(is_dst);
@@ -4795,7 +5008,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
     fsec_t        fsec;
     const char *tzn;
     Interval   *resInterval;
-    struct pg_tm itm;
+    struct pg_itm_in itm_in;

     SetSingleFuncCall(fcinfo, 0);

@@ -4831,10 +5044,11 @@ pg_timezone_names(PG_FUNCTION_ARGS)
         values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
         values[1] = CStringGetTextDatum(tzn ? tzn : "");

-        MemSet(&itm, 0, sizeof(struct pg_tm));
-        itm.tm_sec = -tzoff;
+        /* Convert tzoff to an interval; can't overflow */
+        MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+        itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC;
         resInterval = (Interval *) palloc(sizeof(Interval));
-        tm2interval(&itm, 0, resInterval);
+        (void) itmin2interval(&itm_in, resInterval);
         values[2] = IntervalPGetDatum(resInterval);

         values[3] = BoolGetDatum(tm.tm_isdst > 0);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index ac74333be5..843b07d7d2 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -491,11 +491,28 @@ typedef struct

 /* ----------
  * Datetime to char conversion
+ *
+ * To support intervals as well as timestamps, we use a custom "tm" struct
+ * that is almost like struct pg_tm, but has a 64-bit tm_hour field.
+ * We omit the tm_isdst and tm_zone fields, which are not used here.
  * ----------
  */
+struct fmt_tm
+{
+    int            tm_sec;
+    int            tm_min;
+    int64        tm_hour;
+    int            tm_mday;
+    int            tm_mon;
+    int            tm_year;
+    int            tm_wday;
+    int            tm_yday;
+    long int    tm_gmtoff;
+};
+
 typedef struct TmToChar
 {
-    struct pg_tm tm;            /* classic 'tm' struct */
+    struct fmt_tm tm;            /* almost the classic 'tm' struct */
     fsec_t        fsec;            /* fractional seconds */
     const char *tzn;            /* timezone */
 } TmToChar;
@@ -504,12 +521,25 @@ typedef struct TmToChar
 #define tmtcTzn(_X) ((_X)->tzn)
 #define tmtcFsec(_X)    ((_X)->fsec)

+/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
+#define COPY_tm(_DST, _SRC) \
+do {    \
+    (_DST)->tm_sec = (_SRC)->tm_sec; \
+    (_DST)->tm_min = (_SRC)->tm_min; \
+    (_DST)->tm_hour = (_SRC)->tm_hour; \
+    (_DST)->tm_mday = (_SRC)->tm_mday; \
+    (_DST)->tm_mon = (_SRC)->tm_mon; \
+    (_DST)->tm_year = (_SRC)->tm_year; \
+    (_DST)->tm_wday = (_SRC)->tm_wday; \
+    (_DST)->tm_yday = (_SRC)->tm_yday; \
+    (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
+} while(0)
+
+/* Caution: this is used to zero both pg_tm and fmt_tm structs */
 #define ZERO_tm(_X) \
 do {    \
-    (_X)->tm_sec  = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \
-    (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \
-    (_X)->tm_mday = (_X)->tm_mon  = 1; \
-    (_X)->tm_zone = NULL; \
+    memset(_X, 0, sizeof(*(_X))); \
+    (_X)->tm_mday = (_X)->tm_mon = 1; \
 } while(0)

 #define ZERO_tmtc(_X) \
@@ -2649,7 +2679,7 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
 {
     FormatNode *n;
     char       *s;
-    struct pg_tm *tm = &in->tm;
+    struct fmt_tm *tm = &in->tm;
     int            i;

     /* cache localized days and months */
@@ -2698,16 +2728,17 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                  * display time as shown on a 12-hour clock, even for
                  * intervals
                  */
-                sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-                        tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
-                        tm->tm_hour % (HOURS_PER_DAY / 2));
+                sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+                        tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
+                        (long long) (HOURS_PER_DAY / 2) :
+                        (long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
                 if (S_THth(n->suffix))
                     str_numth(s, s, S_TH_TYPE(n->suffix));
                 s += strlen(s);
                 break;
             case DCH_HH24:
-                sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
-                        tm->tm_hour);
+                sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+                        (long long) tm->tm_hour);
                 if (S_THth(n->suffix))
                     str_numth(s, s, S_TH_TYPE(n->suffix));
                 s += strlen(s);
@@ -2755,9 +2786,10 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
                 break;
 #undef DCH_to_char_fsec
             case DCH_SSSS:
-                sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
-                        tm->tm_min * SECS_PER_MINUTE +
-                        tm->tm_sec);
+                sprintf(s, "%lld",
+                        (long long) (tm->tm_hour * SECS_PER_HOUR +
+                                     tm->tm_min * SECS_PER_MINUTE +
+                                     tm->tm_sec));
                 if (S_THth(n->suffix))
                     str_numth(s, s, S_TH_TYPE(n->suffix));
                 s += strlen(s);
@@ -4088,7 +4120,8 @@ timestamp_to_char(PG_FUNCTION_ARGS)
     text       *fmt = PG_GETARG_TEXT_PP(1),
                *res;
     TmToChar    tmtc;
-    struct pg_tm *tm;
+    struct pg_tm tt;
+    struct fmt_tm *tm;
     int            thisdate;

     if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4097,10 +4130,11 @@ timestamp_to_char(PG_FUNCTION_ARGS)
     ZERO_tmtc(&tmtc);
     tm = tmtcTm(&tmtc);

-    if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0)
+    if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
         ereport(ERROR,
                 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                  errmsg("timestamp out of range")));
+    COPY_tm(tm, &tt);

     thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
     tm->tm_wday = (thisdate + 1) % 7;
@@ -4120,7 +4154,8 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
                *res;
     TmToChar    tmtc;
     int            tz;
-    struct pg_tm *tm;
+    struct pg_tm tt;
+    struct fmt_tm *tm;
     int            thisdate;

     if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
@@ -4129,10 +4164,11 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
     ZERO_tmtc(&tmtc);
     tm = tmtcTm(&tmtc);

-    if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
+    if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
         ereport(ERROR,
                 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                  errmsg("timestamp out of range")));
+    COPY_tm(tm, &tt);

     thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
     tm->tm_wday = (thisdate + 1) % 7;
@@ -4156,7 +4192,9 @@ interval_to_char(PG_FUNCTION_ARGS)
     text       *fmt = PG_GETARG_TEXT_PP(1),
                *res;
     TmToChar    tmtc;
-    struct pg_tm *tm;
+    struct fmt_tm *tm;
+    struct pg_itm tt,
+               *itm = &tt;

     if (VARSIZE_ANY_EXHDR(fmt) <= 0)
         PG_RETURN_NULL();
@@ -4164,8 +4202,14 @@ interval_to_char(PG_FUNCTION_ARGS)
     ZERO_tmtc(&tmtc);
     tm = tmtcTm(&tmtc);

-    if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
-        PG_RETURN_NULL();
+    interval2itm(*it, itm);
+    tmtc.fsec = itm->tm_usec;
+    tm->tm_sec = itm->tm_sec;
+    tm->tm_min = itm->tm_min;
+    tm->tm_hour = itm->tm_hour;
+    tm->tm_mday = itm->tm_mday;
+    tm->tm_mon = itm->tm_mon;
+    tm->tm_year = itm->tm_year;

     /* wday is meaningless, yday approximates the total span in days */
     tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ae36ff3328..1f2ddfb73a 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -888,9 +888,8 @@ interval_in(PG_FUNCTION_ARGS)
 #endif
     int32        typmod = PG_GETARG_INT32(2);
     Interval   *result;
-    fsec_t        fsec;
-    struct pg_tm tt,
-               *tm = &tt;
+    struct pg_itm_in tt,
+               *itm_in = &tt;
     int            dtype;
     int            nf;
     int            range;
@@ -899,13 +898,10 @@ interval_in(PG_FUNCTION_ARGS)
     int            ftype[MAXDATEFIELDS];
     char        workbuf[256];

-    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;
+    itm_in->tm_year = 0;
+    itm_in->tm_mon = 0;
+    itm_in->tm_mday = 0;
+    itm_in->tm_usec = 0;

     if (typmod >= 0)
         range = INTERVAL_RANGE(typmod);
@@ -916,12 +912,12 @@ interval_in(PG_FUNCTION_ARGS)
                           ftype, MAXDATEFIELDS, &nf);
     if (dterr == 0)
         dterr = DecodeInterval(field, ftype, nf, range,
-                               &dtype, tm, &fsec);
+                               &dtype, itm_in);

     /* if those functions think it's a bad format, try ISO8601 style */
     if (dterr == DTERR_BAD_FORMAT)
         dterr = DecodeISO8601Interval(str,
-                                      &dtype, tm, &fsec);
+                                      &dtype, itm_in);

     if (dterr != 0)
     {
@@ -935,7 +931,7 @@ interval_in(PG_FUNCTION_ARGS)
     switch (dtype)
     {
         case DTK_DELTA:
-            if (tm2interval(tm, fsec, result) != 0)
+            if (itmin2interval(itm_in, result) != 0)
                 ereport(ERROR,
                         (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                          errmsg("interval out of range")));
@@ -959,15 +955,12 @@ interval_out(PG_FUNCTION_ARGS)
 {
     Interval   *span = PG_GETARG_INTERVAL_P(0);
     char       *result;
-    struct pg_tm tt,
-               *tm = &tt;
-    fsec_t        fsec;
+    struct pg_itm tt,
+               *itm = &tt;
     char        buf[MAXDATELEN + 1];

-    if (interval2tm(*span, tm, &fsec) != 0)
-        elog(ERROR, "could not convert interval to tm");
-
-    EncodeInterval(tm, fsec, IntervalStyle, buf);
+    interval2itm(*span, itm);
+    EncodeInterval(itm, IntervalStyle, buf);

     result = pstrdup(buf);
     PG_RETURN_CSTRING(result);
@@ -1959,50 +1952,77 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result)
 }


-/* interval2tm()
- * Convert an interval data type to a tm structure.
+/* interval2itm()
+ * Convert an Interval to a pg_itm structure.
+ * Note: overflow is not possible, because the pg_itm fields are
+ * wide enough for all possible conversion results.
  */
-int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+void
+interval2itm(Interval span, struct pg_itm *itm)
 {
     TimeOffset    time;
     TimeOffset    tfrac;

-    tm->tm_year = span.month / MONTHS_PER_YEAR;
-    tm->tm_mon = span.month % MONTHS_PER_YEAR;
-    tm->tm_mday = span.day;
+    itm->tm_year = span.month / MONTHS_PER_YEAR;
+    itm->tm_mon = span.month % MONTHS_PER_YEAR;
+    itm->tm_mday = span.day;
     time = span.time;

     tfrac = time / USECS_PER_HOUR;
     time -= tfrac * USECS_PER_HOUR;
-    tm->tm_hour = tfrac;
-    if (!SAMESIGN(tm->tm_hour, tfrac))
-        ereport(ERROR,
-                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                 errmsg("interval out of range")));
+    itm->tm_hour = tfrac;
     tfrac = time / USECS_PER_MINUTE;
     time -= tfrac * USECS_PER_MINUTE;
-    tm->tm_min = tfrac;
+    itm->tm_min = (int) tfrac;
     tfrac = time / USECS_PER_SEC;
-    *fsec = time - (tfrac * USECS_PER_SEC);
-    tm->tm_sec = tfrac;
+    time -= tfrac * USECS_PER_SEC;
+    itm->tm_sec = (int) tfrac;
+    itm->tm_usec = (int) time;
+}

+/* itm2interval()
+ * Convert a pg_itm structure to an Interval.
+ * Returns 0 if OK, -1 on overflow.
+ */
+int
+itm2interval(struct pg_itm *itm, Interval *span)
+{
+    int64        total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+
+    if (total_months > INT_MAX || total_months < INT_MIN)
+        return -1;
+    span->month = (int32) total_months;
+    span->day = itm->tm_mday;
+    if (pg_mul_s64_overflow(itm->tm_hour, USECS_PER_HOUR,
+                            &span->time))
+        return -1;
+    /* tm_min, tm_sec are 32 bits, so intermediate products can't overflow */
+    if (pg_add_s64_overflow(span->time, itm->tm_min * USECS_PER_MINUTE,
+                            &span->time))
+        return -1;
+    if (pg_add_s64_overflow(span->time, itm->tm_sec * USECS_PER_SEC,
+                            &span->time))
+        return -1;
+    if (pg_add_s64_overflow(span->time, itm->tm_usec,
+                            &span->time))
+        return -1;
     return 0;
 }

+/* itmin2interval()
+ * Convert a pg_itm_in structure to an Interval.
+ * Returns 0 if OK, -1 on overflow.
+ */
 int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+itmin2interval(struct pg_itm_in *itm_in, Interval *span)
 {
-    double        total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
+    int64        total_months = (int64) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon;

     if (total_months > INT_MAX || total_months < INT_MIN)
         return -1;
-    span->month = total_months;
-    span->day = tm->tm_mday;
-    span->time = (((((tm->tm_hour * INT64CONST(60)) +
-                     tm->tm_min) * INT64CONST(60)) +
-                   tm->tm_sec) * USECS_PER_SEC) + fsec;
-
+    span->month = (int32) total_months;
+    span->day = itm_in->tm_mday;
+    span->time = itm_in->tm_usec;
     return 0;
 }

@@ -3601,10 +3621,9 @@ timestamp_age(PG_FUNCTION_ARGS)
     Timestamp    dt1 = PG_GETARG_TIMESTAMP(0);
     Timestamp    dt2 = PG_GETARG_TIMESTAMP(1);
     Interval   *result;
-    fsec_t        fsec,
-                fsec1,
+    fsec_t        fsec1,
                 fsec2;
-    struct pg_tm tt,
+    struct pg_itm tt,
                *tm = &tt;
     struct pg_tm tt1,
                *tm1 = &tt1;
@@ -3617,7 +3636,7 @@ timestamp_age(PG_FUNCTION_ARGS)
         timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
     {
         /* form the symbolic difference */
-        fsec = fsec1 - fsec2;
+        tm->tm_usec = fsec1 - fsec2;
         tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
         tm->tm_min = tm1->tm_min - tm2->tm_min;
         tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
@@ -3628,7 +3647,7 @@ timestamp_age(PG_FUNCTION_ARGS)
         /* flip sign if necessary... */
         if (dt1 < dt2)
         {
-            fsec = -fsec;
+            tm->tm_usec = -tm->tm_usec;
             tm->tm_sec = -tm->tm_sec;
             tm->tm_min = -tm->tm_min;
             tm->tm_hour = -tm->tm_hour;
@@ -3638,9 +3657,9 @@ timestamp_age(PG_FUNCTION_ARGS)
         }

         /* propagate any negative fields into the next higher field */
-        while (fsec < 0)
+        while (tm->tm_usec < 0)
         {
-            fsec += USECS_PER_SEC;
+            tm->tm_usec += USECS_PER_SEC;
             tm->tm_sec--;
         }

@@ -3685,7 +3704,7 @@ timestamp_age(PG_FUNCTION_ARGS)
         /* recover sign if necessary... */
         if (dt1 < dt2)
         {
-            fsec = -fsec;
+            tm->tm_usec = -tm->tm_usec;
             tm->tm_sec = -tm->tm_sec;
             tm->tm_min = -tm->tm_min;
             tm->tm_hour = -tm->tm_hour;
@@ -3694,7 +3713,7 @@ timestamp_age(PG_FUNCTION_ARGS)
             tm->tm_year = -tm->tm_year;
         }

-        if (tm2interval(tm, fsec, result) != 0)
+        if (itm2interval(tm, result) != 0)
             ereport(ERROR,
                     (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                      errmsg("interval out of range")));
@@ -3720,10 +3739,9 @@ timestamptz_age(PG_FUNCTION_ARGS)
     TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
     TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
     Interval   *result;
-    fsec_t        fsec,
-                fsec1,
+    fsec_t        fsec1,
                 fsec2;
-    struct pg_tm tt,
+    struct pg_itm tt,
                *tm = &tt;
     struct pg_tm tt1,
                *tm1 = &tt1;
@@ -3738,7 +3756,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
         timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
     {
         /* form the symbolic difference */
-        fsec = fsec1 - fsec2;
+        tm->tm_usec = fsec1 - fsec2;
         tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
         tm->tm_min = tm1->tm_min - tm2->tm_min;
         tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
@@ -3749,7 +3767,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
         /* flip sign if necessary... */
         if (dt1 < dt2)
         {
-            fsec = -fsec;
+            tm->tm_usec = -tm->tm_usec;
             tm->tm_sec = -tm->tm_sec;
             tm->tm_min = -tm->tm_min;
             tm->tm_hour = -tm->tm_hour;
@@ -3759,9 +3777,9 @@ timestamptz_age(PG_FUNCTION_ARGS)
         }

         /* propagate any negative fields into the next higher field */
-        while (fsec < 0)
+        while (tm->tm_usec < 0)
         {
-            fsec += USECS_PER_SEC;
+            tm->tm_usec += USECS_PER_SEC;
             tm->tm_sec--;
         }

@@ -3810,7 +3828,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
         /* recover sign if necessary... */
         if (dt1 < dt2)
         {
-            fsec = -fsec;
+            tm->tm_usec = -tm->tm_usec;
             tm->tm_sec = -tm->tm_sec;
             tm->tm_min = -tm->tm_min;
             tm->tm_hour = -tm->tm_hour;
@@ -3819,7 +3837,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
             tm->tm_year = -tm->tm_year;
         }

-        if (tm2interval(tm, fsec, result) != 0)
+        if (itm2interval(tm, result) != 0)
             ereport(ERROR,
                     (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                      errmsg("interval out of range")));
@@ -4306,8 +4324,7 @@ interval_trunc(PG_FUNCTION_ARGS)
     int            type,
                 val;
     char       *lowunits;
-    fsec_t        fsec;
-    struct pg_tm tt,
+    struct pg_itm tt,
                *tm = &tt;

     result = (Interval *) palloc(sizeof(Interval));
@@ -4320,7 +4337,7 @@ interval_trunc(PG_FUNCTION_ARGS)

     if (type == UNITS)
     {
-        if (interval2tm(*interval, tm, &fsec) == 0)
+        interval2itm(*interval, tm);
         {
             switch (val)
             {
@@ -4355,10 +4372,10 @@ interval_trunc(PG_FUNCTION_ARGS)
                     tm->tm_sec = 0;
                     /* FALL THRU */
                 case DTK_SECOND:
-                    fsec = 0;
+                    tm->tm_usec = 0;
                     break;
                 case DTK_MILLISEC:
-                    fsec = (fsec / 1000) * 1000;
+                    tm->tm_usec = (tm->tm_usec / 1000) * 1000;
                     break;
                 case DTK_MICROSEC:
                     break;
@@ -4371,13 +4388,11 @@ interval_trunc(PG_FUNCTION_ARGS)
                              (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
             }

-            if (tm2interval(tm, fsec, result) != 0)
+            if (itm2interval(tm, result) != 0)
                 ereport(ERROR,
                         (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                          errmsg("interval out of range")));
         }
-        else
-            elog(ERROR, "could not convert interval to tm");
     }
     else
     {
@@ -5189,8 +5204,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
     int            type,
                 val;
     char       *lowunits;
-    fsec_t        fsec;
-    struct pg_tm tt,
+    struct pg_itm tt,
                *tm = &tt;

     lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
@@ -5203,12 +5217,12 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)

     if (type == UNITS)
     {
-        if (interval2tm(*interval, tm, &fsec) == 0)
+        interval2itm(*interval, tm);
         {
             switch (val)
             {
                 case DTK_MICROSEC:
-                    intresult = tm->tm_sec * INT64CONST(1000000) + fsec;
+                    intresult = tm->tm_sec * INT64CONST(1000000) + tm->tm_usec;
                     break;

                 case DTK_MILLISEC:
@@ -5217,9 +5231,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
                          * tm->tm_sec * 1000 + fsec / 1000
                          * = (tm->tm_sec * 1'000'000 + fsec) / 1000
                          */
-                        PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3));
+                        PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec,
3));
                     else
-                        PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
+                        PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + tm->tm_usec / 1000.0);
                     break;

                 case DTK_SECOND:
@@ -5228,9 +5242,9 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
                          * tm->tm_sec + fsec / 1'000'000
                          * = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
                          */
-                        PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6));
+                        PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec,
6));
                     else
-                        PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
+                        PG_RETURN_FLOAT8(tm->tm_sec + tm->tm_usec / 1000000.0);
                     break;

                 case DTK_MINUTE:
@@ -5280,11 +5294,6 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
                     intresult = 0;
             }
         }
-        else
-        {
-            elog(ERROR, "could not convert interval to tm");
-            intresult = 0;
-        }
     }
     else if (type == RESERV && val == DTK_EPOCH)
     {
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 5fa38d20d8..d155f1b03b 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -40,6 +40,10 @@ typedef int64 TimestampTz;
 typedef int64 TimeOffset;
 typedef int32 fsec_t;            /* fractional seconds (in microseconds) */

+
+/*
+ * Storage format for type interval.
+ */
 typedef struct
 {
     TimeOffset    time;            /* all time units other than days, months and
@@ -48,6 +52,41 @@ typedef struct
     int32        month;            /* months and years, after time for alignment */
 } Interval;

+/*
+ * Data structure representing a broken-down interval.
+ *
+ * For historical reasons, this is modeled on struct pg_tm for timestamps.
+ * Unlike the situation for timestamps, there's no magic interpretation
+ * needed for months or years: they're just zero or not.  Note that fields
+ * can be negative; however, because of the divisions done while converting
+ * from struct Interval, only tm_mday could be INT_MIN.  This is important
+ * because we may need to negate the values in some code paths.
+ */
+struct pg_itm
+{
+    int            tm_usec;
+    int            tm_sec;
+    int            tm_min;
+    int64        tm_hour;        /* needs to be wide */
+    int            tm_mday;
+    int            tm_mon;
+    int            tm_year;
+};
+
+/*
+ * Data structure for decoding intervals.  We could just use struct pg_itm,
+ * but then the requirement for tm_usec to be 64 bits would propagate to
+ * places where it's not really needed.  Also, omitting the fields that
+ * aren't used during decoding seems like a good error-prevention measure.
+ */
+struct pg_itm_in
+{
+    int64        tm_usec;        /* needs to be wide */
+    int            tm_mday;
+    int            tm_mon;
+    int            tm_year;
+};
+

 /* Limits on the "precision" option (typmod) for these data types */
 #define MAX_TIMESTAMP_PRECISION 6
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..441d7847c1 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -23,6 +23,8 @@
 typedef int64 pg_time_t;

 /*
+ * Data structure representing a broken-down timestamp.
+ *
  * CAUTION: the IANA timezone library (src/timezone/) follows the POSIX
  * convention that tm_mon counts from 0 and tm_year is relative to 1900.
  * However, Postgres' datetime functions generally treat tm_mon as counting
@@ -44,6 +46,7 @@ struct pg_tm
     const char *tm_zone;
 };

+/* These structs are opaque outside the timezone library */
 typedef struct pg_tz pg_tz;
 typedef struct pg_tzenum pg_tzenum;

diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..0801858d60 100644
--- a/src/include/utils/datetime.h
+++ b/src/include/utils/datetime.h
@@ -300,9 +300,9 @@ extern int    DecodeTimeOnly(char **field, int *ftype,
                            int nf, int *dtype,
                            struct pg_tm *tm, fsec_t *fsec, int *tzp);
 extern int    DecodeInterval(char **field, int *ftype, int nf, int range,
-                           int *dtype, struct pg_tm *tm, fsec_t *fsec);
+                           int *dtype, struct pg_itm_in *itm_in);
 extern int    DecodeISO8601Interval(char *str,
-                                  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+                                  int *dtype, struct pg_itm_in *itm_in);

 extern void DateTimeParseError(int dterr, const char *str,
                                const char *datatype) pg_attribute_noreturn();
@@ -315,7 +315,7 @@ extern int    DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
 extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str);
 extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
 extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char
*str);
-extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str);
+extern void EncodeInterval(struct pg_itm *itm, int style, char *str);
 extern void EncodeSpecialTimestamp(Timestamp dt, char *str);

 extern int    ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c1a74f8e2b..d33421d380 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -88,8 +88,9 @@ extern int    timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
                          fsec_t *fsec, const char **tzn, pg_tz *attimezone);
 extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec);

-extern int    interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec);
-extern int    tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);
+extern void interval2itm(Interval span, struct pg_itm *itm);
+extern int    itm2interval(struct pg_itm *itm, Interval *span);
+extern int    itmin2interval(struct pg_itm_in *itm_in, Interval *span);

 extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 146f7c55d0..00ffe0e2be 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1079,3 +1079,614 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)

+-- test time fields using entire 64 bit microseconds range
+SELECT INTERVAL '2562047788.01521550194 hours';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-2562047788.01521550222 hours';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '153722867280.912930117 minutes';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-153722867280.912930133 minutes';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854.775807 seconds';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854.775808 seconds';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854775.807 milliseconds';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854775.808 milliseconds';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL '9223372036854775807 microseconds';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL '-9223372036854775808 microseconds';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL 'PT2562047788H54.775807S';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT-2562047788H-54.775808S';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SELECT INTERVAL 'PT2562047788:00:54.775807';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT2562047788.0152155019444';
+             interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+SELECT INTERVAL 'PT-2562047788.0152155022222';
+               interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+-- overflow each date/time field
+SELECT INTERVAL '2147483648 years';
+ERROR:  interval field value out of range: "2147483648 years"
+LINE 1: SELECT INTERVAL '2147483648 years';
+                        ^
+SELECT INTERVAL '-2147483649 years';
+ERROR:  interval field value out of range: "-2147483649 years"
+LINE 1: SELECT INTERVAL '-2147483649 years';
+                        ^
+SELECT INTERVAL '2147483648 months';
+ERROR:  interval field value out of range: "2147483648 months"
+LINE 1: SELECT INTERVAL '2147483648 months';
+                        ^
+SELECT INTERVAL '-2147483649 months';
+ERROR:  interval field value out of range: "-2147483649 months"
+LINE 1: SELECT INTERVAL '-2147483649 months';
+                        ^
+SELECT INTERVAL '2147483648 days';
+ERROR:  interval field value out of range: "2147483648 days"
+LINE 1: SELECT INTERVAL '2147483648 days';
+                        ^
+SELECT INTERVAL '-2147483649 days';
+ERROR:  interval field value out of range: "-2147483649 days"
+LINE 1: SELECT INTERVAL '-2147483649 days';
+                        ^
+SELECT INTERVAL '2562047789 hours';
+ERROR:  interval field value out of range: "2562047789 hours"
+LINE 1: SELECT INTERVAL '2562047789 hours';
+                        ^
+SELECT INTERVAL '-2562047789 hours';
+ERROR:  interval field value out of range: "-2562047789 hours"
+LINE 1: SELECT INTERVAL '-2562047789 hours';
+                        ^
+SELECT INTERVAL '153722867281 minutes';
+ERROR:  interval field value out of range: "153722867281 minutes"
+LINE 1: SELECT INTERVAL '153722867281 minutes';
+                        ^
+SELECT INTERVAL '-153722867281 minutes';
+ERROR:  interval field value out of range: "-153722867281 minutes"
+LINE 1: SELECT INTERVAL '-153722867281 minutes';
+                        ^
+SELECT INTERVAL '9223372036855 seconds';
+ERROR:  interval field value out of range: "9223372036855 seconds"
+LINE 1: SELECT INTERVAL '9223372036855 seconds';
+                        ^
+SELECT INTERVAL '-9223372036855 seconds';
+ERROR:  interval field value out of range: "-9223372036855 seconds"
+LINE 1: SELECT INTERVAL '-9223372036855 seconds';
+                        ^
+SELECT INTERVAL '9223372036854777 millisecond';
+ERROR:  interval field value out of range: "9223372036854777 millisecond"
+LINE 1: SELECT INTERVAL '9223372036854777 millisecond';
+                        ^
+SELECT INTERVAL '-9223372036854777 millisecond';
+ERROR:  interval field value out of range: "-9223372036854777 millisecond"
+LINE 1: SELECT INTERVAL '-9223372036854777 millisecond';
+                        ^
+SELECT INTERVAL '9223372036854775808 microsecond';
+ERROR:  interval field value out of range: "9223372036854775808 microsecond"
+LINE 1: SELECT INTERVAL '9223372036854775808 microsecond';
+                        ^
+SELECT INTERVAL '-9223372036854775809 microsecond';
+ERROR:  interval field value out of range: "-9223372036854775809 microsecond"
+LINE 1: SELECT INTERVAL '-9223372036854775809 microsecond';
+                        ^
+SELECT INTERVAL 'P2147483648';
+ERROR:  interval field value out of range: "P2147483648"
+LINE 1: SELECT INTERVAL 'P2147483648';
+                        ^
+SELECT INTERVAL 'P-2147483649';
+ERROR:  interval field value out of range: "P-2147483649"
+LINE 1: SELECT INTERVAL 'P-2147483649';
+                        ^
+SELECT INTERVAL 'P1-2147483647-2147483647';
+ERROR:  interval out of range
+LINE 1: SELECT INTERVAL 'P1-2147483647-2147483647';
+                        ^
+SELECT INTERVAL 'PT2562047789';
+ERROR:  interval field value out of range: "PT2562047789"
+LINE 1: SELECT INTERVAL 'PT2562047789';
+                        ^
+SELECT INTERVAL 'PT-2562047789';
+ERROR:  interval field value out of range: "PT-2562047789"
+LINE 1: SELECT INTERVAL 'PT-2562047789';
+                        ^
+-- overflow with date/time unit aliases
+SELECT INTERVAL '2147483647 weeks';
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: SELECT INTERVAL '2147483647 weeks';
+                        ^
+SELECT INTERVAL '-2147483648 weeks';
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: SELECT INTERVAL '-2147483648 weeks';
+                        ^
+SELECT INTERVAL '2147483647 decades';
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: SELECT INTERVAL '2147483647 decades';
+                        ^
+SELECT INTERVAL '-2147483648 decades';
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: SELECT INTERVAL '-2147483648 decades';
+                        ^
+SELECT INTERVAL '2147483647 centuries';
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: SELECT INTERVAL '2147483647 centuries';
+                        ^
+SELECT INTERVAL '-2147483648 centuries';
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: SELECT INTERVAL '-2147483648 centuries';
+                        ^
+SELECT INTERVAL '2147483647 millennium';
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: SELECT INTERVAL '2147483647 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 millennium';
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 millennium';
+                        ^
+SELECT INTERVAL '1 week 2147483647 days';
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: SELECT INTERVAL '1 week 2147483647 days';
+                        ^
+SELECT INTERVAL '-1 week -2147483648 days';
+ERROR:  interval field value out of range: "-1 week -2147483648 days"
+LINE 1: SELECT INTERVAL '-1 week -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 1 week';
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: SELECT INTERVAL '2147483647 days 1 week';
+                        ^
+SELECT INTERVAL '-2147483648 days -1 week';
+ERROR:  interval field value out of range: "-2147483648 days -1 week"
+LINE 1: SELECT INTERVAL '-2147483648 days -1 week';
+                        ^
+SELECT INTERVAL 'P1W2147483647D';
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: SELECT INTERVAL 'P1W2147483647D';
+                        ^
+SELECT INTERVAL 'P-1W-2147483648D';
+ERROR:  interval field value out of range: "P-1W-2147483648D"
+LINE 1: SELECT INTERVAL 'P-1W-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D1W';
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: SELECT INTERVAL 'P2147483647D1W';
+                        ^
+SELECT INTERVAL 'P-2147483648D-1W';
+ERROR:  interval field value out of range: "P-2147483648D-1W"
+LINE 1: SELECT INTERVAL 'P-2147483648D-1W';
+                        ^
+SELECT INTERVAL '1 decade 2147483647 years';
+ERROR:  interval field value out of range: "1 decade 2147483647 years"
+LINE 1: SELECT INTERVAL '1 decade 2147483647 years';
+                        ^
+SELECT INTERVAL '1 century 2147483647 years';
+ERROR:  interval field value out of range: "1 century 2147483647 years"
+LINE 1: SELECT INTERVAL '1 century 2147483647 years';
+                        ^
+SELECT INTERVAL '1 millennium 2147483647 years';
+ERROR:  interval field value out of range: "1 millennium 2147483647 years"
+LINE 1: SELECT INTERVAL '1 millennium 2147483647 years';
+                        ^
+SELECT INTERVAL '-1 decade -2147483648 years';
+ERROR:  interval field value out of range: "-1 decade -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 decade -2147483648 years';
+                        ^
+SELECT INTERVAL '-1 century -2147483648 years';
+ERROR:  interval field value out of range: "-1 century -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 century -2147483648 years';
+                        ^
+SELECT INTERVAL '-1 millennium -2147483648 years';
+ERROR:  interval field value out of range: "-1 millennium -2147483648 years"
+LINE 1: SELECT INTERVAL '-1 millennium -2147483648 years';
+                        ^
+SELECT INTERVAL '2147483647 years 1 decade';
+ERROR:  interval field value out of range: "2147483647 years 1 decade"
+LINE 1: SELECT INTERVAL '2147483647 years 1 decade';
+                        ^
+SELECT INTERVAL '2147483647 years 1 century';
+ERROR:  interval field value out of range: "2147483647 years 1 century"
+LINE 1: SELECT INTERVAL '2147483647 years 1 century';
+                        ^
+SELECT INTERVAL '2147483647 years 1 millennium';
+ERROR:  interval field value out of range: "2147483647 years 1 millennium"
+LINE 1: SELECT INTERVAL '2147483647 years 1 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 decade';
+ERROR:  interval field value out of range: "-2147483648 years -1 decade"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 decade';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 century';
+ERROR:  interval field value out of range: "-2147483648 years -1 century"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 century';
+                        ^
+SELECT INTERVAL '-2147483648 years -1 millennium';
+ERROR:  interval field value out of range: "-2147483648 years -1 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 years -1 millennium';
+                        ^
+-- overflowing with fractional fields - postgres format
+SELECT INTERVAL '0.1 millennium 2147483647 months';
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 millennium 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 centuries 2147483647 months';
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 centuries 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 decades 2147483647 months';
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 decades 2147483647 months';
+                        ^
+SELECT INTERVAL '0.1 yrs 2147483647 months';
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: SELECT INTERVAL '0.1 yrs 2147483647 months';
+                        ^
+SELECT INTERVAL '-0.1 millennium -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 millennium -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 millennium -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 centuries -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 centuries -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 centuries -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 decades -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 decades -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 decades -2147483648 months';
+                        ^
+SELECT INTERVAL '-0.1 yrs -2147483648 months';
+ERROR:  interval field value out of range: "-0.1 yrs -2147483648 months"
+LINE 1: SELECT INTERVAL '-0.1 yrs -2147483648 months';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 millennium';
+ERROR:  interval field value out of range: "2147483647 months 0.1 millennium"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 millennium';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 centuries';
+ERROR:  interval field value out of range: "2147483647 months 0.1 centuries"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 centuries';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 decades';
+ERROR:  interval field value out of range: "2147483647 months 0.1 decades"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 decades';
+                        ^
+SELECT INTERVAL '2147483647 months 0.1 yrs';
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: SELECT INTERVAL '2147483647 months 0.1 yrs';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 millennium';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 millennium"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 millennium';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 centuries';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 centuries"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 centuries';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 decades';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 decades"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 decades';
+                        ^
+SELECT INTERVAL '-2147483648 months -0.1 yrs';
+ERROR:  interval field value out of range: "-2147483648 months -0.1 yrs"
+LINE 1: SELECT INTERVAL '-2147483648 months -0.1 yrs';
+                        ^
+SELECT INTERVAL '0.1 months 2147483647 days';
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: SELECT INTERVAL '0.1 months 2147483647 days';
+                        ^
+SELECT INTERVAL '-0.1 months -2147483648 days';
+ERROR:  interval field value out of range: "-0.1 months -2147483648 days"
+LINE 1: SELECT INTERVAL '-0.1 months -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 0.1 months';
+ERROR:  interval field value out of range: "2147483647 days 0.1 months"
+LINE 1: SELECT INTERVAL '2147483647 days 0.1 months';
+                        ^
+SELECT INTERVAL '-2147483648 days -0.1 months';
+ERROR:  interval field value out of range: "-2147483648 days -0.1 months"
+LINE 1: SELECT INTERVAL '-2147483648 days -0.1 months';
+                        ^
+SELECT INTERVAL '0.5 weeks 2147483647 days';
+ERROR:  interval field value out of range: "0.5 weeks 2147483647 days"
+LINE 1: SELECT INTERVAL '0.5 weeks 2147483647 days';
+                        ^
+SELECT INTERVAL '-0.5 weeks -2147483648 days';
+ERROR:  interval field value out of range: "-0.5 weeks -2147483648 days"
+LINE 1: SELECT INTERVAL '-0.5 weeks -2147483648 days';
+                        ^
+SELECT INTERVAL '2147483647 days 0.5 weeks';
+ERROR:  interval field value out of range: "2147483647 days 0.5 weeks"
+LINE 1: SELECT INTERVAL '2147483647 days 0.5 weeks';
+                        ^
+SELECT INTERVAL '-2147483648 days -0.5 weeks';
+ERROR:  interval field value out of range: "-2147483648 days -0.5 weeks"
+LINE 1: SELECT INTERVAL '-2147483648 days -0.5 weeks';
+                        ^
+SELECT INTERVAL '0.01 months 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.01 months 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.01 months 9223372036854775807 microsecond...
+                        ^
+SELECT INTERVAL '-0.01 months -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.01 months -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.01 months -9223372036854775808 microseco...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.01 months';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.01 months"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.01 month...
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.01 months';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.01 months"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.01 mon...
+                        ^
+SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 weeks 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds'...
+                        ^
+SELECT INTERVAL '-0.1 weeks -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 weeks -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.1 weeks -9223372036854775808 microsecond...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 weeks"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks'...
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 weeks';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 weeks"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.1 week...
+                        ^
+SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+ERROR:  interval field value out of range: "0.1 days 9223372036854775807 microseconds"
+LINE 1: SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+                        ^
+SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds';
+ERROR:  interval field value out of range: "-0.1 days -9223372036854775808 microseconds"
+LINE 1: SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds...
+                        ^
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+ERROR:  interval field value out of range: "9223372036854775807 microseconds 0.1 days"
+LINE 1: SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds -0.1 days"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days...
+                        ^
+-- overflowing with fractional fields - ISO8601 format
+SELECT INTERVAL 'P0.1Y2147483647M';
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: SELECT INTERVAL 'P0.1Y2147483647M';
+                        ^
+SELECT INTERVAL 'P-0.1Y-2147483648M';
+ERROR:  interval field value out of range: "P-0.1Y-2147483648M"
+LINE 1: SELECT INTERVAL 'P-0.1Y-2147483648M';
+                        ^
+SELECT INTERVAL 'P2147483647M0.1Y';
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: SELECT INTERVAL 'P2147483647M0.1Y';
+                        ^
+SELECT INTERVAL 'P-2147483648M-0.1Y';
+ERROR:  interval field value out of range: "P-2147483648M-0.1Y"
+LINE 1: SELECT INTERVAL 'P-2147483648M-0.1Y';
+                        ^
+SELECT INTERVAL 'P0.1M2147483647D';
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: SELECT INTERVAL 'P0.1M2147483647D';
+                        ^
+SELECT INTERVAL 'P-0.1M-2147483648D';
+ERROR:  interval field value out of range: "P-0.1M-2147483648D"
+LINE 1: SELECT INTERVAL 'P-0.1M-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D0.1M';
+ERROR:  interval field value out of range: "P2147483647D0.1M"
+LINE 1: SELECT INTERVAL 'P2147483647D0.1M';
+                        ^
+SELECT INTERVAL 'P-2147483648D-0.1M';
+ERROR:  interval field value out of range: "P-2147483648D-0.1M"
+LINE 1: SELECT INTERVAL 'P-2147483648D-0.1M';
+                        ^
+SELECT INTERVAL 'P0.5W2147483647D';
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: SELECT INTERVAL 'P0.5W2147483647D';
+                        ^
+SELECT INTERVAL 'P-0.5W-2147483648D';
+ERROR:  interval field value out of range: "P-0.5W-2147483648D"
+LINE 1: SELECT INTERVAL 'P-0.5W-2147483648D';
+                        ^
+SELECT INTERVAL 'P2147483647D0.5W';
+ERROR:  interval field value out of range: "P2147483647D0.5W"
+LINE 1: SELECT INTERVAL 'P2147483647D0.5W';
+                        ^
+SELECT INTERVAL 'P-2147483648D-0.5W';
+ERROR:  interval field value out of range: "P-2147483648D-0.5W"
+LINE 1: SELECT INTERVAL 'P-2147483648D-0.5W';
+                        ^
+SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.01MT2562047788H54.775807S"
+LINE 1: SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+                        ^
+SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.01MT-2562047788H-54.775808S"
+LINE 1: SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+                        ^
+SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+ERROR:  interval field value out of range: "P0.1DT2562047788H54.775807S"
+LINE 1: SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+                        ^
+SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+ERROR:  interval field value out of range: "P-0.1DT-2562047788H-54.775808S"
+LINE 1: SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+                        ^
+SELECT INTERVAL 'PT2562047788.1H54.775807S';
+ERROR:  interval field value out of range: "PT2562047788.1H54.775807S"
+LINE 1: SELECT INTERVAL 'PT2562047788.1H54.775807S';
+                        ^
+SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788.1H-54.775808S"
+LINE 1: SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+                        ^
+SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+ERROR:  interval field value out of range: "PT2562047788H0.1M54.775807S"
+LINE 1: SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+                        ^
+SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+ERROR:  interval field value out of range: "PT-2562047788H-0.1M-54.775808S"
+LINE 1: SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+                        ^
+-- overflowing with fractional fields - ISO8601 alternative format
+SELECT INTERVAL 'P0.1-2147483647-00';
+ERROR:  interval field value out of range: "P0.1-2147483647-00"
+LINE 1: SELECT INTERVAL 'P0.1-2147483647-00';
+                        ^
+SELECT INTERVAL 'P00-0.1-2147483647';
+ERROR:  interval field value out of range: "P00-0.1-2147483647"
+LINE 1: SELECT INTERVAL 'P00-0.1-2147483647';
+                        ^
+SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-0.01-00T2562047788:00:54.775807"
+LINE 1: SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+                        ^
+SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+ERROR:  interval field value out of range: "P00-00-0.1T2562047788:00:54.775807"
+LINE 1: SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+                        ^
+SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+ERROR:  interval field value out of range: "PT2562047788.1:00:54.775807"
+LINE 1: SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+                        ^
+SELECT INTERVAL 'PT2562047788:01.:54.775807';
+ERROR:  interval field value out of range: "PT2562047788:01.:54.775807"
+LINE 1: SELECT INTERVAL 'PT2562047788:01.:54.775807';
+                        ^
+-- overflowing with fractional fields - SQL standard format
+SELECT INTERVAL '0.1 2562047788:0:54.775807';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775807"
+LINE 1: SELECT INTERVAL '0.1 2562047788:0:54.775807';
+                        ^
+SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+ERROR:  interval field value out of range: "0.1 2562047788:0:54.775808 ago"
+LINE 1: SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+                        ^
+SELECT INTERVAL '2562047788.1:0:54.775807';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775807"
+LINE 1: SELECT INTERVAL '2562047788.1:0:54.775807';
+                        ^
+SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+ERROR:  interval field value out of range: "2562047788.1:0:54.775808 ago"
+LINE 1: SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+                        ^
+SELECT INTERVAL '2562047788:0.1:54.775807';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775807"
+LINE 1: SELECT INTERVAL '2562047788:0.1:54.775807';
+                        ^
+SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+ERROR:  invalid input syntax for type interval: "2562047788:0.1:54.775808 ago"
+LINE 1: SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+                        ^
+-- overflowing using AGO with INT_MIN
+SELECT INTERVAL '-2147483648 months ago';
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: SELECT INTERVAL '-2147483648 months ago';
+                        ^
+SELECT INTERVAL '-2147483648 days ago';
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: SELECT INTERVAL '-2147483648 days ago';
+                        ^
+SELECT INTERVAL '-9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-9223372036854775808 microseconds ago"
+LINE 1: SELECT INTERVAL '-9223372036854775808 microseconds ago';
+                        ^
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+ERROR:  interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
+LINE 1: SELECT INTERVAL '-2147483648 months -2147483648 days -922337...
+                        ^
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                              interval
+--------------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                                   interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+(1 row)
+
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                     interval
+---------------------------------------------------
+ -178956970-8 -2147483648 -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+                      interval
+-----------------------------------------------------
+ P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index c31f0eec05..fc924d5bde 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -367,3 +367,187 @@ SELECT f1,

 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
+
+-- test time fields using entire 64 bit microseconds range
+SELECT INTERVAL '2562047788.01521550194 hours';
+SELECT INTERVAL '-2562047788.01521550222 hours';
+SELECT INTERVAL '153722867280.912930117 minutes';
+SELECT INTERVAL '-153722867280.912930133 minutes';
+SELECT INTERVAL '9223372036854.775807 seconds';
+SELECT INTERVAL '-9223372036854.775808 seconds';
+SELECT INTERVAL '9223372036854775.807 milliseconds';
+SELECT INTERVAL '-9223372036854775.808 milliseconds';
+SELECT INTERVAL '9223372036854775807 microseconds';
+SELECT INTERVAL '-9223372036854775808 microseconds';
+
+SELECT INTERVAL 'PT2562047788H54.775807S';
+SELECT INTERVAL 'PT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788:00:54.775807';
+
+SELECT INTERVAL 'PT2562047788.0152155019444';
+SELECT INTERVAL 'PT-2562047788.0152155022222';
+
+-- overflow each date/time field
+SELECT INTERVAL '2147483648 years';
+SELECT INTERVAL '-2147483649 years';
+SELECT INTERVAL '2147483648 months';
+SELECT INTERVAL '-2147483649 months';
+SELECT INTERVAL '2147483648 days';
+SELECT INTERVAL '-2147483649 days';
+SELECT INTERVAL '2562047789 hours';
+SELECT INTERVAL '-2562047789 hours';
+SELECT INTERVAL '153722867281 minutes';
+SELECT INTERVAL '-153722867281 minutes';
+SELECT INTERVAL '9223372036855 seconds';
+SELECT INTERVAL '-9223372036855 seconds';
+SELECT INTERVAL '9223372036854777 millisecond';
+SELECT INTERVAL '-9223372036854777 millisecond';
+SELECT INTERVAL '9223372036854775808 microsecond';
+SELECT INTERVAL '-9223372036854775809 microsecond';
+
+SELECT INTERVAL 'P2147483648';
+SELECT INTERVAL 'P-2147483649';
+SELECT INTERVAL 'P1-2147483647-2147483647';
+SELECT INTERVAL 'PT2562047789';
+SELECT INTERVAL 'PT-2562047789';
+
+-- overflow with date/time unit aliases
+SELECT INTERVAL '2147483647 weeks';
+SELECT INTERVAL '-2147483648 weeks';
+SELECT INTERVAL '2147483647 decades';
+SELECT INTERVAL '-2147483648 decades';
+SELECT INTERVAL '2147483647 centuries';
+SELECT INTERVAL '-2147483648 centuries';
+SELECT INTERVAL '2147483647 millennium';
+SELECT INTERVAL '-2147483648 millennium';
+
+SELECT INTERVAL '1 week 2147483647 days';
+SELECT INTERVAL '-1 week -2147483648 days';
+SELECT INTERVAL '2147483647 days 1 week';
+SELECT INTERVAL '-2147483648 days -1 week';
+
+SELECT INTERVAL 'P1W2147483647D';
+SELECT INTERVAL 'P-1W-2147483648D';
+SELECT INTERVAL 'P2147483647D1W';
+SELECT INTERVAL 'P-2147483648D-1W';
+
+SELECT INTERVAL '1 decade 2147483647 years';
+SELECT INTERVAL '1 century 2147483647 years';
+SELECT INTERVAL '1 millennium 2147483647 years';
+SELECT INTERVAL '-1 decade -2147483648 years';
+SELECT INTERVAL '-1 century -2147483648 years';
+SELECT INTERVAL '-1 millennium -2147483648 years';
+
+SELECT INTERVAL '2147483647 years 1 decade';
+SELECT INTERVAL '2147483647 years 1 century';
+SELECT INTERVAL '2147483647 years 1 millennium';
+SELECT INTERVAL '-2147483648 years -1 decade';
+SELECT INTERVAL '-2147483648 years -1 century';
+SELECT INTERVAL '-2147483648 years -1 millennium';
+
+-- overflowing with fractional fields - postgres format
+SELECT INTERVAL '0.1 millennium 2147483647 months';
+SELECT INTERVAL '0.1 centuries 2147483647 months';
+SELECT INTERVAL '0.1 decades 2147483647 months';
+SELECT INTERVAL '0.1 yrs 2147483647 months';
+SELECT INTERVAL '-0.1 millennium -2147483648 months';
+SELECT INTERVAL '-0.1 centuries -2147483648 months';
+SELECT INTERVAL '-0.1 decades -2147483648 months';
+SELECT INTERVAL '-0.1 yrs -2147483648 months';
+
+SELECT INTERVAL '2147483647 months 0.1 millennium';
+SELECT INTERVAL '2147483647 months 0.1 centuries';
+SELECT INTERVAL '2147483647 months 0.1 decades';
+SELECT INTERVAL '2147483647 months 0.1 yrs';
+SELECT INTERVAL '-2147483648 months -0.1 millennium';
+SELECT INTERVAL '-2147483648 months -0.1 centuries';
+SELECT INTERVAL '-2147483648 months -0.1 decades';
+SELECT INTERVAL '-2147483648 months -0.1 yrs';
+
+SELECT INTERVAL '0.1 months 2147483647 days';
+SELECT INTERVAL '-0.1 months -2147483648 days';
+SELECT INTERVAL '2147483647 days 0.1 months';
+SELECT INTERVAL '-2147483648 days -0.1 months';
+
+SELECT INTERVAL '0.5 weeks 2147483647 days';
+SELECT INTERVAL '-0.5 weeks -2147483648 days';
+SELECT INTERVAL '2147483647 days 0.5 weeks';
+SELECT INTERVAL '-2147483648 days -0.5 weeks';
+
+SELECT INTERVAL '0.01 months 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.01 months -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.01 months';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.01 months';
+
+SELECT INTERVAL '0.1 weeks 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.1 weeks -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 weeks';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 weeks';
+
+SELECT INTERVAL '0.1 days 9223372036854775807 microseconds';
+SELECT INTERVAL '-0.1 days -9223372036854775808 microseconds';
+SELECT INTERVAL '9223372036854775807 microseconds 0.1 days';
+SELECT INTERVAL '-9223372036854775808 microseconds -0.1 days';
+
+-- overflowing with fractional fields - ISO8601 format
+SELECT INTERVAL 'P0.1Y2147483647M';
+SELECT INTERVAL 'P-0.1Y-2147483648M';
+SELECT INTERVAL 'P2147483647M0.1Y';
+SELECT INTERVAL 'P-2147483648M-0.1Y';
+
+SELECT INTERVAL 'P0.1M2147483647D';
+SELECT INTERVAL 'P-0.1M-2147483648D';
+SELECT INTERVAL 'P2147483647D0.1M';
+SELECT INTERVAL 'P-2147483648D-0.1M';
+
+SELECT INTERVAL 'P0.5W2147483647D';
+SELECT INTERVAL 'P-0.5W-2147483648D';
+SELECT INTERVAL 'P2147483647D0.5W';
+SELECT INTERVAL 'P-2147483648D-0.5W';
+
+SELECT INTERVAL 'P0.01MT2562047788H54.775807S';
+SELECT INTERVAL 'P-0.01MT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'P0.1DT2562047788H54.775807S';
+SELECT INTERVAL 'P-0.1DT-2562047788H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788.1H54.775807S';
+SELECT INTERVAL 'PT-2562047788.1H-54.775808S';
+
+SELECT INTERVAL 'PT2562047788H0.1M54.775807S';
+SELECT INTERVAL 'PT-2562047788H-0.1M-54.775808S';
+
+-- overflowing with fractional fields - ISO8601 alternative format
+SELECT INTERVAL 'P0.1-2147483647-00';
+SELECT INTERVAL 'P00-0.1-2147483647';
+SELECT INTERVAL 'P00-0.01-00T2562047788:00:54.775807';
+SELECT INTERVAL 'P00-00-0.1T2562047788:00:54.775807';
+SELECT INTERVAL 'PT2562047788.1:00:54.775807';
+SELECT INTERVAL 'PT2562047788:01.:54.775807';
+
+-- overflowing with fractional fields - SQL standard format
+SELECT INTERVAL '0.1 2562047788:0:54.775807';
+SELECT INTERVAL '0.1 2562047788:0:54.775808 ago';
+
+SELECT INTERVAL '2562047788.1:0:54.775807';
+SELECT INTERVAL '2562047788.1:0:54.775808 ago';
+
+SELECT INTERVAL '2562047788:0.1:54.775807';
+SELECT INTERVAL '2562047788:0.1:54.775808 ago';
+
+-- overflowing using AGO with INT_MIN
+SELECT INTERVAL '-2147483648 months ago';
+SELECT INTERVAL '-2147483648 days ago';
+SELECT INTERVAL '-9223372036854775808 microseconds ago';
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -9223372036854775808 us';

pgsql-hackers by date:

Previous
From: Andres Freund
Date:
Subject: Re: pg_walinspect - a new extension to get raw WAL data and WAL stats
Next
From: Noah Misch
Date:
Subject: Re: Skipping logical replication transactions on subscriber side