Thread: Fix overflow in DecodeInterval

Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
The attached patch fixes an overflow bug in DecodeInterval when applying the units week, decade, century, and millennium. The overflow check logic was modelled after the overflow check at the beginning of `int tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);` in timestamp.c.

This is my first patch, so apologies if I screwed up the process somewhere.

- Joe Koshakow
Attachment

Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> The attached patch fixes an overflow bug in DecodeInterval when applying
> the units week, decade, century, and millennium. The overflow check logic
> was modelled after the overflow check at the beginning of `int
> tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);` in timestamp.c.

Good catch, but I don't think that tm2interval code is best practice
anymore.  Rather than bringing "double" arithmetic into the mix,
you should use the overflow-detecting arithmetic functions in
src/include/common/int.h.  The existing code here is also pretty
faulty in that it doesn't notice addition overflow when combining
multiple units.  So for example, instead of

    tm->tm_mday += val * 7;

I think we should write something like

    if (pg_mul_s32_overflow(val, 7, &tmp))
        return DTERR_FIELD_OVERFLOW;
    if (pg_add_s32_overflow(tm->tm_mday, tmp, &tm->tm_mday))
        return DTERR_FIELD_OVERFLOW;

Perhaps some macros could be used to make this more legible?

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> writes:
> Joseph Koshakow <koshy44(at)gmail(dot)com> writes:
> > The attached patch fixes an overflow bug in DecodeInterval when applying
> > the units week, decade, century, and millennium. The overflow check logic
> > was modelled after the overflow check at the beginning of `int
> > tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);` in timestamp.c.
>
>
> Good catch, but I don't think that tm2interval code is best practice
> anymore.  Rather than bringing "double" arithmetic into the mix,
> you should use the overflow-detecting arithmetic functions in
> src/include/common/int.h.  The existing code here is also pretty
> faulty in that it doesn't notice addition overflow when combining
> multiple units.  So for example, instead of
>
>
>     tm->tm_mday += val * 7;
>
>
> I think we should write something like
>
>
>     if (pg_mul_s32_overflow(val, 7, &tmp))
>         return DTERR_FIELD_OVERFLOW;
>     if (pg_add_s32_overflow(tm->tm_mday, tmp, &tm->tm_mday))
>         return DTERR_FIELD_OVERFLOW;
>
>
> Perhaps some macros could be used to make this more legible?
>
>
>             regards, tom lane
>
>
>     @postgresql

Thanks for the feedback Tom, I've attached an updated patch with
your suggestions. Feel free to rename the horribly named macro.

Also while fixing this I noticed that fractional intervals can also
cause an overflow issue.
postgres=# SELECT INTERVAL '0.1 months 2147483647 days';
     interval
------------------
 -2147483646 days
(1 row)
I haven't looked into it, but it's probably a similar cause.

Attachment

Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Fri, Feb 11, 2022 at 4:58 PM Joseph Koshakow <koshy44@gmail.com> wrote:
>
> Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> writes:
> > Joseph Koshakow <koshy44(at)gmail(dot)com> writes:
> > > The attached patch fixes an overflow bug in DecodeInterval when applying
> > > the units week, decade, century, and millennium. The overflow check logic
> > > was modelled after the overflow check at the beginning of `int
> > > tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);` in timestamp.c.
> >
> >
> > Good catch, but I don't think that tm2interval code is best practice
> > anymore.  Rather than bringing "double" arithmetic into the mix,
> > you should use the overflow-detecting arithmetic functions in
> > src/include/common/int.h.  The existing code here is also pretty
> > faulty in that it doesn't notice addition overflow when combining
> > multiple units.  So for example, instead of
> >
> >
> >     tm->tm_mday += val * 7;
> >
> >
> > I think we should write something like
> >
> >
> >     if (pg_mul_s32_overflow(val, 7, &tmp))
> >         return DTERR_FIELD_OVERFLOW;
> >     if (pg_add_s32_overflow(tm->tm_mday, tmp, &tm->tm_mday))
> >         return DTERR_FIELD_OVERFLOW;
> >
> >
> > Perhaps some macros could be used to make this more legible?
> >
> >
> >             regards, tom lane
> >
> >
> >     @postgresql
>
> Thanks for the feedback Tom, I've attached an updated patch with
> your suggestions. Feel free to rename the horribly named macro.
>
> Also while fixing this I noticed that fractional intervals can also
> cause an overflow issue.
> postgres=# SELECT INTERVAL '0.1 months 2147483647 days';
>      interval
> ------------------
>  -2147483646 days
> (1 row)
> I haven't looked into it, but it's probably a similar cause.

Hey Tom,

I was originally going to fix the fractional issue in a follow-up
patch, but I took a look at it and decided to make a new patch
with both fixes. I tried to make the handling of fractional and
non-fractional units consistent. I've attached the patch below.

While investigating this, I've noticed a couple more overflow
issues with Intervals, but I think they're best handled in separate
patches. I've included the ones I've found in this email.

  postgres=# CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
  CREATE TABLE
  postgres=# INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 weeks
2147483647 hrs');
  INSERT 0 1
  postgres=# SELECT * FROM INTERVAL_TBL_OF;
  ERROR:  interval out of range

  postgres=# SELECT justify_days(INTERVAL '2147483647 months 2147483647 days');
             justify_days
  -----------------------------------
   -172991738 years -4 mons -23 days
  (1 row)

Cheers,
Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Andres Freund
Date:
Hi,

On 2022-02-12 21:16:05 -0500, Joseph Koshakow wrote:
> I've attached the patch below.

Any reason for using int return types?


> +/* As above, but initial val produces years */
> +static int
> +AdjustYears(int val, struct pg_tm *tm, int multiplier)
> +{
> +    int            years;
> +    if (pg_mul_s32_overflow(val, multiplier, &years))
> +        return 1;
> +    if (pg_add_s32_overflow(tm->tm_year, years, &tm->tm_year))
> +        return 1;
> +    return 0;
>  }

particularly since the pg_*_overflow stuff uses bool?


Greetings,

Andres Freund



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sat, Feb 12, 2022 at 10:51 PM Andres Freund <andres@anarazel.de> wrote:
> Any reason for using int return types?
>
> particularly since the pg_*_overflow stuff uses bool?

I chose int return types to keep all these methods
consistent with DecodeInterval, which returns a
non-zero int to indicate an error. Though I wasn't sure
if an int or bool would be best, so I'm happy to change
to bool if people think that's better.

Also I'm realizing now that I've incorrectly been using the
number of the patch to indicate the version, instead of just
sticking a v3 to the front. So sorry about that, all the patches
I sent in this thread are the same patch, just different versions.

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Actually found an additional overflow issue in
DecodeInterval. I've updated the patch with the
fix. Specifically it prevents trying to negate a field
if it equals INT_MIN. Let me know if this is best
put into another patch.

- Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Andres Freund
Date:
Hi,

On 2022-02-13 09:35:47 -0500, Joseph Koshakow wrote:
> I chose int return types to keep all these methods
> consistent with DecodeInterval, which returns a
> non-zero int to indicate an error.

That's different, because it actually returns different types of errors. IMO
that difference is actually reason to use a bool for the new cases, because
then it's a tad clearer that they don't return DTERR_*.

> Though I wasn't sure
> if an int or bool would be best, so I'm happy to change
> to bool if people think that's better.

+1 or bool.


> Also I'm realizing now that I've incorrectly been using the
> number of the patch to indicate the version, instead of just
> sticking a v3 to the front. So sorry about that, all the patches
> I sent in this thread are the same patch, just different versions.

No worries ;)


Do we want to consider backpatching these fixes? If so, I'd argue for skipping
10, because it doesn't have int.h stuff yet. There's also the issue with
potentially breaking indexes / constraints? Not that goes entirely away across
major versions...

Greetings,

Andres Freund



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> Do we want to consider backpatching these fixes?

I wasn't thinking that we should.  It's a behavioral change and
people might not be pleased to have it appear in minor releases.

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Attached is a new version switching from ints to bools, as requested.

- Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Feb 13, 2022 at 5:12 PM Joseph Koshakow <koshy44@gmail.com> wrote:
>
> Attached is a new version switching from ints to bools, as requested.
>
> - Joe Koshakow

Tom, Andres,

Thanks for your feedback! I'm not super familiar with the process,
should I submit this thread to the commitfest or just leave it as is?

Thanks,
Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Andres Freund
Date:
Hi,

On 2022-02-15 06:44:40 -0500, Joseph Koshakow wrote:
> Thanks for your feedback! I'm not super familiar with the process,
> should I submit this thread to the commitfest or just leave it as is?

Submit it to the CF - then we get an automatic test on a few platforms. I
think we can apply it quickly after that...

Greetings,

Andres Freund



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Ok, so I've attached a patch with my final unprompted changes. It
contains the following two changes:

1. I found some more overflows with the ISO8601 formats and have
included some fixes.
2. I reverted the overflow checks for the seconds field. It's actually a
bit more complicated than I thought. For example consider the following
query:
    postgres=# SELECT INTERVAL '0.99999999 min 2147483647 sec';
            interval
    ----------------------
    -596523:13:09.000001
    (1 row)
This query will overflow the tm_sec field of the struct pg_tm, however
it's not actually out of range for the Interval. I'm not sure the best
way to handle this right now, but I think it would be best to leave it
for a future patch. Perhaps the time related fields in struct pg_tm
need to be changed to 64 bit ints.

- Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Copying over a related thread here to have info all in one place.
It's a little fragmented so sorry about that.


On Fri, Feb 18, 2022 at 11:44 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Joseph Koshakow <koshy44@gmail.com> writes:
> > When formatting the output of an Interval, we call abs() on the hours
> > field of the Interval. Calling abs(INT_MIN) returns back INT_MIN
> > causing the output to contain two '-' characters. The attached patch
> > fixes that issue by special casing INT_MIN hours.
>
> Good catch, but it looks to me like three out of the four formats in
> EncodeInterval have variants of this problem --- there are assumptions
> throughout that code that we can compute "-x" or "abs(x)" without
> fear.  Not much point in fixing only one symptom.
>
> Also, I notice that there's an overflow hazard upstream of here,
> in interval2tm:
>
> regression=# select interval '214748364 hours' * 11;
> ERROR:  interval out of range
> regression=# \errverbose
> ERROR:  22008: interval out of range
> LOCATION:  interval2tm, timestamp.c:1982
>
> There's no good excuse for not being able to print a value that
> we computed successfully.
>
> I wonder if the most reasonable fix would be to start using int64
> instead of int arithmetic for the values that are potentially large.
> I doubt that we'd be taking much of a performance hit on modern
> hardware.
>
>                         regards, tom lane

Joseph Koshakow <koshy44@gmail.com> writes:
> On Fri, Feb 18, 2022 at 11:44 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I wonder if the most reasonable fix would be to start using int64
>> instead of int arithmetic for the values that are potentially large.
>> I doubt that we'd be taking much of a performance hit on modern
>> hardware.

> That's an interesting idea. I've always assumed that the range of the
> time fields of Intervals was 2147483647 hours 59 minutes
> 59.999999 seconds to -2147483648 hours -59 minutes
> -59.999999 seconds. However the only reason that we can't support
> the full range of int64 microseconds is because the struct pg_tm fields
> are only ints. If we increase those fields to int64 then we'd be able to
> support the full int64 range for microseconds as well as implicitly fix
> some of the overflow issues in DecodeInterval and EncodeInterval.

On Sat, Feb 19, 2022 at 1:52 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Joseph Koshakow <koshy44@gmail.com> writes:
> > On Sat, Feb 19, 2022 at 12:00 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> I think that messing with struct pg_tm might have too many side-effects.
> >> However, pg_tm isn't all that well adapted to intervals in the first
> >> place, so it'd make sense to make a new struct specifically for interval
> >> decoding.
>
> > Yeah that's a good idea, pg_tm is used all over the place. Is there a
> > reason we need an intermediate structure to convert to and from?
> > We could just convert directly to an Interval in DecodeInterval and
> > print directly from an Interval in EncodeInterval.
>
> Yeah, I thought about that too, but if you look at the other callers of
> interval2tm, you'll see this same set of issues.  I'm inclined to keep
> the current code structure and just fix the data structure.  Although
> interval2tm isn't *that* big, I don't think four copies of its logic
> would be an improvement.
>
> > Also I originally created a separate thread and patch because I
> > thought this wouldn't be directly related to my other patch,
> > https://commitfest.postgresql.org/37/3541/. However I think with these
> > discussed changes it's directly related. So I think it's best to close
> > this thread and patch and copy this conversion to the other thread.
>
> Agreed.
>
>                         regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Attached is a patch of my first pass. The to_char method isn't finished
and I need to add a bunch of tests, but everything else is in place. It
ended up being a fairly large change in case anyone wants to take a look
the changes so far.

One thing I noticed is that interval.c has a ton of code copied and pasted
from other files. The code seemed out of date from those other files, so
I tried to bring it up to date and add my changes.

- Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> Attached is a patch of my first pass. The to_char method isn't finished
> and I need to add a bunch of tests, but everything else is in place. It
> ended up being a fairly large change in case anyone wants to take a look
> the changes so far.

Couple of quick comments:

* You've assumed in a number of places that long == int64.  This is wrong
on many platforms.  Current project style for use of int64 in printf-type
calls is to cast the argument to (long long) explicitly and use "%lld" (or
"%llu", etc).  As for strtoint_64, surely that functionality exists
somewhere already (hopefully with fewer bugs than this has).

* I think that tools like Coverity will complain about how you've
got both calls that check the result of AdjustFractSeconds (etc)
and calls that don't.  You might consider coding the latter like

+            /* this can't overflow: */
+            (void) AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);

in hopes of (a) silencing those warnings and (b) making the implicit
assumption clear to readers.

* I'm not entirely buying use of pg_time_t, rather than just int64,
in struct itm.  I think the point of this struct is to get away
from datetime-specific datatypes.  Also, if pg_time_t ever changed
width, it'd create issues for users of this struct.  I also note
a number of places in the patch that are assuming that these fields
are int64 not something else.

* I'm a little inclined to rename interval2tm/tm2interval to
interval2itm/itm2interval, as I think it'd be confusing for those
function names to refer to a typedef they no longer use.

* The uses of tm2itm make me a bit itchy.  Is that sweeping
upstream-of-there overflow problems under the rug?

* // comments are not project style, either.  While pgindent will
convert them to /* ... */ style, the results might not be pleasing.

> One thing I noticed is that interval.c has a ton of code copied and pasted
> from other files. The code seemed out of date from those other files, so
> I tried to bring it up to date and add my changes.

We haven't actually maintained ecpg's copies of backend datatype-specific
code in many years.  While bringing that stuff up to speed might be
worthwhile (or perhaps not, given the lack of complaints), I'd see it
as a separate project.

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Feb 20, 2022 at 6:37 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Couple of quick comments:

Thanks for the comments Tom, I'll work on fixing these and submit a
new patch.

> * The uses of tm2itm make me a bit itchy.  Is that sweeping
> upstream-of-there overflow problems under the rug?

I agree, I'm not super happy with that approach. In fact
I'm pretty sure it will cause queries like
    SELECT INTERVAL '2147483648:00:00';
to overflow upstream, even though queries like
    SELECT INTERVAL '2147483648 hours';
would not. The places tm2itm is being used are
 * After DecodeTime
 * In interval_to_char.
The more general issue is how to share code with
functions that are doing almost identical things but use
pg_tm instead of the new pg_itm? I'm not really sure what
the best solution is right now but I will think about it. If
anyone has suggestions though, feel free to chime in.

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
Hi All,

Sorry for the delay in the new patch, I've attached my most recent
patch to this email. I ended up reworking a good portion of my previous
patch so below I've included some reasons why, notes on my current
approach, and some pro/cons to the approach.

* The main reason for the rework had to do with double conversions and
shared code.

* 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 ended up creating two intermediate data structures for Intervals.
One for decoding and one for everything else. I'll go into more detail
below.
* One common benefit was that they both contain a usec field which
means that the Interval methods no longer need to carry around a
separate fsec argument.
* The obvious con here is that Intervals require two unique
intermediate data structures, while all other date/time types
can share a single intermediate data structure. I find this to
be a bit clunky.

* pg_itm_in is the struct used for Interval decoding. It's very similar
to pg_tm, except all of the time related fields are collapsed into a
single `int64 usec` field.
* The biggest benefit of this was that all int64-double conversions
are limited to a single function, AdjustFractMicroseconds. Instead
of fractional units flowing down over every single time field, they
only need to flow down into the single `int64 usec` field.
* Overflows are caught much earlier in the decoding process which
helps avoid wasted work.
* I found that the decoding code became simpler for time fields,
though this is a bit subjective.

* pg_itm is the struct used for all other Interval functionality. It's
very similar to pg_tm, except the tm_hour field is converted from int
to int64 and an `int tm_usec` field was added.
* When encoding and working with Intervals, we almost always want
to break the time field out into hours, min, sec, usec. So it's
helpful to have a common place to do this, instead of every
function duplicating this code.
* When breaking the time fields out, a single field will never
contain a value greater than could have fit in the next unit
higher. Meaning that minutes will never be greater than 60, seconds
will be never greater than 60, and usec will never be greater than
1,000. So hours is actually the only field that needs to be int64
and the rest can be an int.
* This also helps limit the impact to shared code (see below).

* There's some shared code between Intervals and other date/time types.
Specifically the DecodeTime function and the datetime_to_char_body
function. These functions take in a `struct pg_tm` and a `fsec_t fsec`
(fsec_t is just an alias for int32) which allows them to be re-used by
all date/time types. The only difference now between pg_tm and pg_itm
is the tm_hour field size (the tm_usec field in pg_itm can be used as
the fsec). So to get around this I changed the function signatures to
take a `struct pg_tm`, `fsec_t fsec`, and an `int64 hour` argument.
It's up to the caller to provide to correct hour field. Intervals can
easily convert pg_itm to a pg_tm, fsec, and hour. It's honestly a bit
error-prone since those functions have to explicitly ignore the
pg_tm->tm_hour field and use the provided hour argument instead, but I
couldn't think of a better less intrusive solution. If anyone has a
better idea, please don't hesitate to bring it up.

* This partly existed in the previous patch, but I just wanted to
restate it. All modifications to pg_itm_in during decoding is done via
helper functions that check for overflow. All invocations of these
functions either return an error on overflow or explicitly state why an
overflow is impossible.

* I completely rewrote the ParseISO8601Number function to try and avoid
double to int64 conversions. I tried to model it after the parsing done
in DecodeInterval, though I would appreciate extra scrutiny here.

- Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
I just realized another issue today. It may have been obvious from one
of Tom's earlier messages, but I'm just now putting the pieces
together.
On Fri, Feb 18, 2022 at 11:44 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Also, I notice that there's an overflow hazard upstream of here,
> in interval2tm:
>
> regression=# select interval '214748364 hours' * 11;
> ERROR: interval out of range
> regression=# \errverbose
> ERROR: 22008: interval out of range
> LOCATION: interval2tm, timestamp.c:1982
>
> There's no good excuse for not being able to print a value that
> we computed successfully.

Scenarios like this can properly decode the interval, but actually
error out when encoding the interval. As a consequence you can insert
the value successfully into a table, but any attempt to query the table
that includes the "bad interval" value in the result will cause an
error. Below I've demonstrated an example:

postgres=# CREATE TABLE tbl (i INTERVAL);
CREATE TABLE
postgres=# INSERT INTO tbl VALUES ('1 day'), ('3 months'), ('2 years');
INSERT 0 3
postgres=# SELECT * FROM tbl;
i
---------
1 day
3 mons
2 years
(3 rows)

postgres=# INSERT INTO tbl VALUES ('2147483647 hours 60 minutes');
INSERT 0 1
postgres=# SELECT * FROM tbl;
ERROR: interval out of range

This would seriously reduce the usable of any table that contains one
of these "bad interval" values.

My patch actually fixes this issue, but I just wanted to call it out
because it might be relevant when reviewing.



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> [ v8-0001-Check-for-overflow-when-decoding-an-interval.patch ]

This isn't applying per the cfbot; looks like it got sideswiped
by 9e9858389.  Here's a quick rebase.  I've not reviewed it, but
I did notice (because git was in my face about this) that it's
got whitespace issues.  Please try to avoid unnecessary whitespace
changes ... pgindent will clean those up, but it makes reviewing
harder.

            regards, tom lane

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ba0ec35ac5..014ec88e0d 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"
@@ -38,16 +39,20 @@ static int    DecodeNumberField(int len, char *str,
                               int fmask, int *tmask,
                               struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
 static int    DecodeTime(char *str, int fmask, int range,
-                       int *tmask, struct pg_tm *tm, fsec_t *fsec);
+                       int *tmask, struct pg_tm *tm, int64 *hour, 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,
+static char *AppendSeconds(char *cp, int sec, int64 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 AdjustFractMicroseconds(long double frac, struct pg_itm_in *itm_in, int64 scale);
+static bool AdjustFractDays(double frac, struct pg_itm_in *pg_itm_in, int scale);
+static bool AdjustFractMonths(double frac, struct pg_itm_in *itm_in, int scale);
+static bool AdjustMicroseconds(int64 val, struct pg_itm_in *itm_in, int64 multiplier, double fval);
+static bool AdjustDays(int val, struct pg_itm_in *itm_in, int multiplier);
+static bool AdjustYears(int val, struct pg_itm_in *itm_in, int multiplier);
 static int    DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
                                             pg_time_t *tp);
 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
@@ -428,7 +433,7 @@ GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
  * Note that any sign is stripped from the input seconds values.
  */
 static char *
-AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
+AppendSeconds(char *cp, int sec, int64 fsec, int precision, bool fillzeros)
 {
     Assert(precision >= 0);

@@ -437,10 +442,9 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
     else
         cp = pg_ultostr(cp, Abs(sec));

-    /* fsec_t is just an int32 */
     if (fsec != 0)
     {
-        int32        value = Abs(fsec);
+        int64        value = Abs(fsec);
         char       *end = &cp[precision + 1];
         bool        gotnonzero = false;

@@ -453,8 +457,8 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
          */
         while (precision--)
         {
-            int32        oldval = value;
-            int32        remainder;
+            int64        oldval = value;
+            int64        remainder;

             value /= 10;
             remainder = oldval - value * 10;
@@ -475,7 +479,7 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
          * which will generate a correct answer in the minimum valid width.
          */
         if (value)
-            return pg_ultostr(cp, Abs(fsec));
+            return pg_ulltostr(cp, Abs(fsec));

         return end;
     }
@@ -497,36 +501,96 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
 }

 /*
- * 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.
+ * We assume the input frac is less than 1 so overflow of frac is not an issue.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractMicroseconds(long double frac, struct pg_itm_in *itm_in, int64 scale)
 {
-    int            sec;
+    int64        usec;
+    int64        round = 0;

     if (frac == 0)
-        return;
+        return true;
     frac *= scale;
-    sec = (int) frac;
-    tm->tm_sec += sec;
-    frac -= sec;
-    *fsec += rint(frac * 1000000);
+    usec = (int64) frac;
+    if (pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec))
+        return false;
+
+    frac = frac - usec;
+    if (frac > 0.5)
+        round = 1;
+    else if (frac < -0.5)
+        round = -1;
+
+    return !pg_add_s64_overflow(itm_in->tm_usec, round, &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)
+static bool
+AdjustFractDays(double frac, struct pg_itm_in *itm_in, int scale)
 {
     int            extra_days;

     if (frac == 0)
-        return;
+        return true;
     frac *= scale;
     extra_days = (int) frac;
-    tm->tm_mday += extra_days;
+    if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday))
+        return false;
     frac -= extra_days;
-    AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+    return AdjustFractMicroseconds(frac, itm_in, USECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_itm_in *itm_in, int scale)
+{
+    int extra_months = rint(frac * MONTHS_PER_YEAR * scale);
+    return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon);
+}
+
+/*
+ * Multiply val by multiplier (to produce microseconds) and add to *itm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustMicroseconds(int64 val, struct pg_itm_in *itm_in, int64 multiplier, double fval)
+{
+    int64        usecs;
+    if (pg_mul_s64_overflow(val, multiplier, &usecs) ||
+        pg_add_s64_overflow(itm_in->tm_usec, usecs, &itm_in->tm_usec))
+        return false;
+
+    return AdjustFractMicroseconds(fval, itm_in, multiplier);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *itm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_itm_in *itm_in, int multiplier)
+{
+    int            extra_days;
+    return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+        !pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday);
+}
+
+/* As above, but initial val produces months */
+static bool
+AdjustMonths(int val, struct pg_itm_in *itm_in)
+{
+    return !pg_add_s32_overflow(itm_in->tm_mon, val, &itm_in->tm_mon);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_itm_in *itm_in, int multiplier)
+{
+    int            years;
+    return !pg_mul_s32_overflow(val, multiplier, &years) &&
+        !pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year);
 }

 /* Fetch a fractional-second value with suitable error checking */
@@ -965,7 +1029,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
                 break;

             case DTK_TIME:
-
+            {
+                int64 hour;
                 /*
                  * This might be an ISO time following a "t" field.
                  */
@@ -977,16 +1042,19 @@ DecodeDateTime(char **field, int *ftype, int nf,
                     ptype = 0;
                 }
                 dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
-                                   &tmask, tm, fsec);
+                                   &tmask, tm, &hour, fsec);
                 if (dterr)
                     return dterr;
+                if (hour > INT_MAX || hour < INT_MIN)
+                    return DTERR_FIELD_OVERFLOW;
+                tm->tm_hour = (int) hour;

                 /* check for time overflow */
                 if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec,
                                    *fsec))
                     return DTERR_FIELD_OVERFLOW;
                 break;
-
+            }
             case DTK_TZ:
                 {
                     int            tz;
@@ -1866,13 +1934,18 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                 break;

             case DTK_TIME:
+            {
+                int64 hour;
                 dterr = DecodeTime(field[i], (fmask | DTK_DATE_M),
                                    INTERVAL_FULL_RANGE,
-                                   &tmask, tm, fsec);
+                                   &tmask, tm, &hour, fsec);
                 if (dterr)
                     return dterr;
+                if (hour > INT_MAX || hour < INT_MIN)
+                    return DTERR_FIELD_OVERFLOW;
+                tm->tm_hour = (int) hour;
                 break;
-
+            }
             case DTK_TZ:
                 {
                     int            tz;
@@ -2554,10 +2627,13 @@ ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
  *
  * Only check the lower limit on hours, since this same code can be
  * used to represent time spans.
+ *
+ * Different consumers of this function have different requirements on the size
+ * of hours. So we take in an *int64 hour and let the consumer check the result.
  */
 static int
 DecodeTime(char *str, int fmask, int range,
-           int *tmask, struct pg_tm *tm, fsec_t *fsec)
+           int *tmask, struct pg_tm *tm, int64 *hour, fsec_t *fsec)
 {
     char       *cp;
     int            dterr;
@@ -2565,7 +2641,7 @@ DecodeTime(char *str, int fmask, int range,
     *tmask = DTK_TIME_M;

     errno = 0;
-    tm->tm_hour = strtoint(str, &cp, 10);
+    *hour = strtoi64(str, &cp, 10);
     if (errno == ERANGE)
         return DTERR_FIELD_OVERFLOW;
     if (*cp != ':')
@@ -2581,9 +2657,11 @@ DecodeTime(char *str, int fmask, int range,
         /* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
         if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
         {
+            if (*hour > INT_MAX || *hour < INT_MIN)
+                return DTERR_FIELD_OVERFLOW;
             tm->tm_sec = tm->tm_min;
-            tm->tm_min = tm->tm_hour;
-            tm->tm_hour = 0;
+            tm->tm_min = (int) *hour;
+            *hour = 0;
         }
     }
     else if (*cp == '.')
@@ -2592,9 +2670,11 @@ DecodeTime(char *str, int fmask, int range,
         dterr = ParseFractionalSecond(cp, fsec);
         if (dterr)
             return dterr;
+        if (*hour > INT_MAX || *hour < INT_MIN)
+            return DTERR_FIELD_OVERFLOW;
         tm->tm_sec = tm->tm_min;
-        tm->tm_min = tm->tm_hour;
-        tm->tm_hour = 0;
+        tm->tm_min = (int) *hour;
+        *hour = 0;
     }
     else if (*cp == ':')
     {
@@ -2617,7 +2697,7 @@ DecodeTime(char *str, int fmask, int range,
         return DTERR_BAD_FORMAT;

     /* do a sanity check */
-    if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
+    if (*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)
@@ -2626,6 +2706,30 @@ DecodeTime(char *str, int fmask, int range,
     return 0;
 }

+/* DecodeTimeForInterval()
+ * Decode time string which includes delimiters for Interval decoding.
+ * Return 0 if okay, a DTERR code if not.
+ */
+static int
+DecodeTimeForInterval(char *str, int fmask, int range,
+                                 int *tmask, struct pg_itm_in *itm_in)
+{
+    int dterr;
+    int64 hour;
+    struct pg_tm tt,
+            *tm = &tt;
+    dterr = DecodeTime(str, fmask, range,
+                       tmask, tm, &hour, (fsec_t *)&itm_in->tm_usec);
+    if (dterr)
+        return dterr;
+
+    if (!AdjustMicroseconds(hour, itm_in, USECS_PER_HOUR, 0) ||
+        !AdjustMicroseconds(tm->tm_min, itm_in, USECS_PER_MINUTE, 0) ||
+        !AdjustMicroseconds(tm->tm_sec, itm_in, USECS_PER_SEC, 0))
+        return DTERR_FIELD_OVERFLOW;
+
+    return 0;
+}

 /* DecodeNumber()
  * Interpret plain numeric field as a date value in context.
@@ -3063,28 +3167,24 @@ DecodeSpecial(int field, char *lowtoken, int *val)
     return type;
 }

-
-/* 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_year = 0;
+    itm_in->tm_mon = 0;
+    itm_in->tm_mday = 0;
+    itm_in->tm_usec = 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 +3194,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,
+                tval;
     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 +3217,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 +3238,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 +3304,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,8 +3321,8 @@ 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 ((val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+                        (val * MONTHS_PER_YEAR + val2) < INT_MIN)
                         return DTERR_FIELD_OVERFLOW;
                     val = val * MONTHS_PER_YEAR + val2;
                     fval = 0;
@@ -3247,21 +3347,20 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                 switch (type)
                 {
                     case DTK_MICROSEC:
-                        *fsec += rint(val + fval);
+                        if (!AdjustMicroseconds(val, itm_in, 1, fval))
+                            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, itm_in, USECS_PER_MSEC, fval))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MILLISECOND);
                         break;

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

                         /*
                          * If any subseconds were specified, consider this
@@ -3274,57 +3373,71 @@ 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, itm_in, USECS_PER_MINUTE, fval))
+                            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, itm_in, USECS_PER_HOUR, fval))
+                            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 (val < INT_MIN || val > INT_MAX ||
+                            !AdjustDays((int) val, itm_in, 1) ||
+                            !AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(DAY);
                         break;

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

                     case DTK_MONTH:
-                        tm->tm_mon += val;
-                        AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                        if (val < INT_MIN || val > INT_MAX ||
+                            !AdjustMonths((int) val, itm_in) ||
+                            !AdjustFractDays(fval, itm_in, DAYS_PER_MONTH))
+                            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 (val < INT_MIN || val > INT_MAX ||
+                            !AdjustYears((int) val, itm_in, 1) ||
+                            !AdjustFractMonths(fval, itm_in, 1))
+                            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 (val < INT_MIN || val > INT_MAX ||
+                            !AdjustYears((int) val, itm_in, YEARS_PER_DECADE) ||
+                            !AdjustFractMonths(fval, itm_in, YEARS_PER_DECADE))
+                            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 (val < INT_MIN || val > INT_MAX ||
+                            !AdjustYears((int) val, itm_in, YEARS_PER_CENTURY) ||
+                            !AdjustFractMonths(fval, itm_in, YEARS_PER_CENTURY))
+                            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 (val < INT_MIN || val > INT_MAX ||
+                            !AdjustYears((int) val, itm_in, YEARS_PER_MILLENNIUM) ||
+                            !AdjustFractMonths(fval, itm_in, YEARS_PER_MILLENNIUM))
+                            return DTERR_FIELD_OVERFLOW;
                         tmask = DTK_M(MILLENNIUM);
                         break;

@@ -3335,7 +3448,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], &tval);
                 if (type == IGNORE_DTF)
                     continue;

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

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

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

                     default:
@@ -3374,16 +3487,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 +3523,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 +3560,37 @@ 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;
+    *ipart = 0;
+    *fpart = 0.0;

     if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
         return DTERR_BAD_FORMAT;
     errno = 0;
-    val = strtod(str, endptr);
+
+    /* Parse sign if there is any */
+    if (*str == '-')
+    {
+        sign = -1;
+        str++;
+        *endptr = str;
+    }
+
+    /* Parse int64 part if there is any */
+    if (isdigit((unsigned char) **endptr))
+        *ipart = strtoi64(*endptr, endptr, 10) * sign;
+
+    /* Parse decimal part if there is any */
+    if (**endptr == '.') {
+        *fpart = strtod(*endptr, endptr) * sign;
+    }
+
     /* did we not see anything that looks like a double? */
     if (*endptr == str || errno != 0)
         return DTERR_BAD_FORMAT;
-    /* watch out for overflow */
-    if (val < INT_MIN || val > INT_MAX)
-        return DTERR_FIELD_OVERFLOW;
-    /* be very sure we truncate towards zero (cf dtrunc()) */
-    if (val >= 0)
-        *ipart = (int) floor(val);
-    else
-        *ipart = (int) -floor(-val);
-    *fpart = val - *ipart;
+
     return 0;
 }

@@ -3508,21 +3619,20 @@ 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
  *     - allows decimals in fields other than the least significant unit.
  */
 int
-DecodeISO8601Interval(char *str,
-                      int *dtype, struct pg_tm *tm, fsec_t *fsec)
+DecodeISO8601Interval(char *str, 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 +3641,7 @@ DecodeISO8601Interval(char *str,
     while (*str)
     {
         char       *fieldstart;
-        int            val;
+        int64        val;
         double        fval;
         char        unit;
         int            dterr;
@@ -3557,32 +3667,50 @@ DecodeISO8601Interval(char *str,

         if (datepart)
         {
+            /* Date parts cannot be bigger than int */
+            if (val < INT_MIN || val > INT_MAX)
+                return DTERR_FIELD_OVERFLOW;
             switch (unit)        /* before T: Y M W D */
             {
                 case 'Y':
-                    tm->tm_year += val;
-                    tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+                    /*
+                     * Not possible to overflow years in this format since
+                     * there's no year aliases and can't have fractional
+                     * years
+                     */
+                    (void) AdjustYears((int) val, itm_in, 1);
+                    if (!AdjustFractMonths(fval, itm_in, 1))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'M':
-                    tm->tm_mon += val;
-                    AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+                    if (!AdjustMonths((int) val, itm_in) ||
+                        !AdjustFractDays(fval, itm_in, DAYS_PER_MONTH))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'W':
-                    tm->tm_mday += val * 7;
-                    AdjustFractDays(fval, tm, fsec, 7);
+                    if (!AdjustDays((int) val, itm_in, 7) ||
+                        !AdjustFractDays(fval, itm_in, 7))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'D':
-                    tm->tm_mday += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+                    if (!AdjustDays((int) val, itm_in, 1) ||
+                        !AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY))
+                        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);
+                        /* None of the date fields can overflow because val is
+                         * within the bounds of an int from the check above
+                         */
+                        (void) AdjustYears((int) val / 10000, itm_in, 1);
+                        (void) AdjustMonths((int) (val / 100) % 100, itm_in);
+                        (void) AdjustDays((int) val % 100, itm_in, 1);
+                        /* Can't overflow because date fields must come before
+                         * time fields
+                         */
+                        (void) AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY);
                         if (unit == '\0')
                             return 0;
                         datepart = false;
@@ -3596,8 +3724,14 @@ DecodeISO8601Interval(char *str,
                     if (havefield)
                         return DTERR_BAD_FORMAT;

-                    tm->tm_year += val;
-                    tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+                    /*
+                     * Not possible to overflow years in this format since
+                     * there's no year aliases and can't have fractional
+                     * years
+                     */
+                    (void) AdjustYears((int) val, itm_in, 1);
+                    /* Can't overflow because years must come before months */
+                    (void) AdjustFractMonths(fval, itm_in, 1);
                     if (unit == '\0')
                         return 0;
                     if (unit == 'T')
@@ -3610,8 +3744,10 @@ 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((int) val, itm_in))
+                        return DTERR_FIELD_OVERFLOW;
+                    /* Can't overflow because months must come before days */
+                    (void) AdjustFractDays(fval, itm_in, DAYS_PER_MONTH);
                     if (*str == '\0')
                         return 0;
                     if (*str == 'T')
@@ -3627,8 +3763,10 @@ 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((int) val, itm_in, 1))
+                        return DTERR_FIELD_OVERFLOW;
+                    /* Can't overflow because days must come before time fields */
+                    (void) AdjustFractMicroseconds(fval, itm_in, USECS_PER_DAY);
                     if (*str == '\0')
                         return 0;
                     if (*str == 'T')
@@ -3648,24 +3786,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, itm_in, USECS_PER_HOUR, fval))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'M':
-                    tm->tm_min += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+                    if (!AdjustMicroseconds(val, itm_in, USECS_PER_MINUTE, fval))
+                        return DTERR_FIELD_OVERFLOW;
                     break;
                 case 'S':
-                    tm->tm_sec += val;
-                    AdjustFractSeconds(fval, tm, fsec, 1);
+                    if (!AdjustMicroseconds(val, itm_in, USECS_PER_SEC, fval))
+                        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, itm_in, USECS_PER_HOUR, 0) ||
+                            !AdjustMicroseconds((val / 100) % 100, itm_in, USECS_PER_MINUTE, 0) ||
+                            !AdjustMicroseconds(val % 100, itm_in, USECS_PER_SEC, 0) ||
+                            !AdjustFractMicroseconds(fval, itm_in, 1))
+                            return DTERR_FIELD_OVERFLOW;
                         return 0;
                     }
                     /* Else fall through to extended alternative format */
@@ -3675,16 +3814,16 @@ DecodeISO8601Interval(char *str,
                     if (havefield)
                         return DTERR_BAD_FORMAT;

-                    tm->tm_hour += val;
-                    AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+                    if (!AdjustMicroseconds(val, itm_in, USECS_PER_HOUR, fval))
+                        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, itm_in, USECS_PER_MINUTE, fval))
+                        return DTERR_FIELD_OVERFLOW;
                     if (*str == '\0')
                         return 0;
                     if (*str != ':')
@@ -3694,8 +3833,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, itm_in, USECS_PER_SEC, fval))
+                        return DTERR_FIELD_OVERFLOW;
                     if (*str == '\0')
                         return 0;
                     return DTERR_BAD_FORMAT;
@@ -4166,25 +4305,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 +4338,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 +4347,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 +4377,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;
+    int64        year = (int64) itm->tm_year;
+    int64        mon = (int64) itm->tm_mon;
+    int64        mday = (int64) itm->tm_mday;
+    int64        hour = itm->tm_hour;
+    int            min = itm->tm_min;
+    int            sec = itm->tm_sec;
+    int         usec = itm->tm_usec;
     bool        is_before = false;
     bool        is_zero = true;

@@ -4263,13 +4403,13 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
             {
                 bool        has_negative = year < 0 || mon < 0 ||
                 mday < 0 || hour < 0 ||
-                min < 0 || sec < 0 || fsec < 0;
+                min < 0 || sec < 0 || usec < 0;
                 bool        has_positive = year > 0 || mon > 0 ||
                 mday > 0 || hour > 0 ||
-                min > 0 || sec > 0 || fsec > 0;
+                min > 0 || sec > 0 || usec > 0;
                 bool        has_year_month = year != 0 || mon != 0;
                 bool        has_day_time = mday != 0 || hour != 0 ||
-                min != 0 || sec != 0 || fsec != 0;
+                min != 0 || sec != 0 || usec != 0;
                 bool        has_day = mday != 0;
                 bool        sql_standard_value = !(has_negative && has_positive) &&
                 !(has_year_month && has_day_time);
@@ -4287,7 +4427,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
                     hour = -hour;
                     min = -min;
                     sec = -sec;
-                    fsec = -fsec;
+                    usec = -usec;
                 }

                 if (!has_negative && !has_positive)
@@ -4304,32 +4444,34 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
                     char        year_sign = (year < 0 || mon < 0) ? '-' : '+';
                     char        day_sign = (mday < 0) ? '-' : '+';
                     char        sec_sign = (hour < 0 || min < 0 ||
-                                            sec < 0 || fsec < 0) ? '-' : '+';
+                                            sec < 0 || usec < 0) ? '-' : '+';

-                    sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
-                            year_sign, abs(year), abs(mon),
-                            day_sign, abs(mday),
-                            sec_sign, abs(hour), abs(min));
+                    sprintf(cp, "%c%lld-%lld %c%lld %c%lld:%02d:",
+                            year_sign, (long long) Abs(year), (long long) Abs(mon),
+                            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 = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
                     *cp = '\0';
                 }
                 else if (has_year_month)
                 {
-                    sprintf(cp, "%d-%d", year, mon);
+                    sprintf(cp, "%lld-%lld",
+                            (long long) year, (long long) mon);
                 }
                 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 = AppendSeconds(cp, sec, usec, 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 = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
                     *cp = '\0';
                 }
             }
@@ -4339,7 +4481,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
         case INTSTYLE_ISO_8601:
             /* special-case zero to avoid printing nothing */
             if (year == 0 && mon == 0 && mday == 0 &&
-                hour == 0 && min == 0 && sec == 0 && fsec == 0)
+                hour == 0 && min == 0 && sec == 0 && usec == 0)
             {
                 sprintf(cp, "PT0S");
                 break;
@@ -4348,15 +4490,15 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
             cp = AddISO8601IntPart(cp, year, 'Y');
             cp = AddISO8601IntPart(cp, mon, 'M');
             cp = AddISO8601IntPart(cp, mday, 'D');
-            if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
+            if (hour != 0 || min != 0 || sec != 0 || usec != 0)
                 *cp++ = 'T';
             cp = AddISO8601IntPart(cp, hour, 'H');
             cp = AddISO8601IntPart(cp, min, 'M');
-            if (sec != 0 || fsec != 0)
+            if (sec != 0 || usec != 0)
             {
-                if (sec < 0 || fsec < 0)
+                if (sec < 0 || usec < 0)
                     *cp++ = '-';
-                cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+                cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, false);
                 *cp++ = 'S';
                 *cp++ = '\0';
             }
@@ -4373,16 +4515,16 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
              */
             cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
             cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
-            if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
+            if (is_zero || hour != 0 || min != 0 || sec != 0 || usec != 0)
             {
-                bool        minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
+                bool        minus = (hour < 0 || min < 0 || sec < 0 || usec < 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 = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, true);
                 *cp = '\0';
             }
             break;
@@ -4397,10 +4539,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
             cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
             cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
             cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
-            if (sec != 0 || fsec != 0)
+            if (sec != 0 || usec != 0)
             {
                 *cp++ = ' ';
-                if (sec < 0 || (sec == 0 && fsec < 0))
+                if (sec < 0 || (sec == 0 && usec < 0))
                 {
                     if (is_zero)
                         is_before = true;
@@ -4409,10 +4551,10 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
                 }
                 else if (is_before)
                     *cp++ = '-';
-                cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
+                cp = AppendSeconds(cp, sec, usec, MAX_INTERVAL_PRECISION, false);
                 /* We output "ago", not negatives, so use abs(). */
                 sprintf(cp, " sec%s",
-                        (abs(sec) != 1 || fsec != 0) ? "s" : "");
+                        (abs(sec) != 1 || usec != 0) ? "s" : "");
                 is_zero = false;
             }
             /* identically zero? then put in a unitless zero... */
@@ -4668,7 +4810,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 */
@@ -4762,10 +4904,10 @@ 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;
+    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);
+    itmin2interval(&itm_in, resInterval);
     values[1] = IntervalPGetDatum(resInterval);

     values[2] = BoolGetDatum(is_dst);
@@ -4795,7 +4937,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 +4973,10 @@ 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;
+        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);
+        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 ed698f788d..4db0860ad3 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -496,6 +496,10 @@ typedef struct
 typedef struct TmToChar
 {
     struct pg_tm tm;            /* classic 'tm' struct */
+    /* Different date/time types have different requirements on the size of the
+     * hour field. So we take in a separate int64 hour field.
+     */
+    int64        tm_hour;        /* hours */
     fsec_t        fsec;            /* fractional seconds */
     const char *tzn;            /* timezone */
 } TmToChar;
@@ -2655,6 +2659,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;
+    int64 tm_hour = in->tm_hour;
     int            i;

     /* cache localized days and months */
@@ -2674,25 +2679,25 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
         {
             case DCH_A_M:
             case DCH_P_M:
-                strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+                strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                        ? P_M_STR : A_M_STR);
                 s += strlen(s);
                 break;
             case DCH_AM:
             case DCH_PM:
-                strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+                strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                        ? PM_STR : AM_STR);
                 s += strlen(s);
                 break;
             case DCH_a_m:
             case DCH_p_m:
-                strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+                strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                        ? p_m_STR : a_m_STR);
                 s += strlen(s);
                 break;
             case DCH_am:
             case DCH_pm:
-                strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
+                strcpy(s, (tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
                        ? pm_STR : am_STR);
                 s += strlen(s);
                 break;
@@ -2703,16 +2708,16 @@ 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_hour >= 0) ? 2 : 3,
+                        tm_hour % (HOURS_PER_DAY / 2) == 0 ? (long long) HOURS_PER_DAY / 2 :
+                        (long long) 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_hour >= 0) ? 2 : 3,
+                        (long long) tm_hour);
                 if (S_THth(n->suffix))
                     str_numth(s, s, S_TH_TYPE(n->suffix));
                 s += strlen(s);
@@ -2760,7 +2765,7 @@ 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 +
+                sprintf(s, "%lld", (long long) tm_hour * SECS_PER_HOUR +
                         tm->tm_min * SECS_PER_MINUTE +
                         tm->tm_sec);
                 if (S_THth(n->suffix))
@@ -4106,6 +4111,7 @@ timestamp_to_char(PG_FUNCTION_ARGS)
         ereport(ERROR,
                 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                  errmsg("timestamp out of range")));
+    tmtc.tm_hour = (int64) tm->tm_hour;

     thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
     tm->tm_wday = (thisdate + 1) % 7;
@@ -4138,6 +4144,7 @@ timestamptz_to_char(PG_FUNCTION_ARGS)
         ereport(ERROR,
                 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                  errmsg("timestamp out of range")));
+    tmtc.tm_hour = (int64) tm->tm_hour;

     thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
     tm->tm_wday = (thisdate + 1) % 7;
@@ -4162,6 +4169,8 @@ interval_to_char(PG_FUNCTION_ARGS)
                *res;
     TmToChar    tmtc;
     struct pg_tm *tm;
+    struct pg_itm tt,
+            *itm = &tt;

     if (VARSIZE_ANY_EXHDR(fmt) <= 0)
         PG_RETURN_NULL();
@@ -4169,8 +4178,16 @@ interval_to_char(PG_FUNCTION_ARGS)
     ZERO_tmtc(&tmtc);
     tm = tmtcTm(&tmtc);

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

     /* 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/numutils.c b/src/backend/utils/adt/numutils.c
index cc3f95d399..8bdcdee328 100644
--- a/src/backend/utils/adt/numutils.c
+++ b/src/backend/utils/adt/numutils.c
@@ -602,3 +602,15 @@ pg_ultostr(char *str, uint32 value)

     return str + len;
 }
+
+/*
+ * pg_ulltostr
+ *        See above
+ */
+char *
+pg_ulltostr(char *str, uint64 value)
+{
+    int            len = pg_ulltoa_n(value, str);
+
+    return str + len;
+}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index ae36ff3328..77cc730b9d 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,14 @@ 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)
+    if (interval2itm(*span, itm) != 0)
         elog(ERROR, "could not convert interval to tm");

-    EncodeInterval(tm, fsec, IntervalStyle, buf);
+    EncodeInterval(itm, IntervalStyle, buf);

     result = pstrdup(buf);
     PG_RETURN_CSTRING(result);
@@ -1963,45 +1958,59 @@ tm2timestamp(struct pg_tm *tm, fsec_t fsec, int *tzp, Timestamp *result)
  * Convert an interval data type to a tm structure.
  */
 int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+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))
+    itm->tm_hour = tfrac;
+    if (!SAMESIGN(itm->tm_hour, tfrac))
         ereport(ERROR,
                 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                  errmsg("interval out of range")));
     tfrac = time / USECS_PER_MINUTE;
     time -= tfrac * USECS_PER_MINUTE;
-    tm->tm_min = tfrac;
+    itm->tm_min = tfrac;
     tfrac = time / USECS_PER_SEC;
-    *fsec = time - (tfrac * USECS_PER_SEC);
-    tm->tm_sec = tfrac;
+    itm->tm_usec = time - (tfrac * USECS_PER_SEC);
+    itm->tm_sec = tfrac;
+
+    return 0;
+}
+
+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 = (int) total_months;
+    span->day = itm->tm_mday;
+    span->time = (((((itm->tm_hour * INT64CONST(60)) +
+                     itm->tm_min) * INT64CONST(60)) +
+                   itm->tm_sec) * USECS_PER_SEC) + itm->tm_usec;

     return 0;
 }

 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;
+    double        total_months = (double) 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->day = itm_in->tm_mday;
+    span->time = itm_in->tm_usec;

     return 0;
 }
@@ -3601,11 +3610,10 @@ 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,
-               *tm = &tt;
+    struct pg_itm tt,
+               *itm = &tt;
     struct pg_tm tt1,
                *tm1 = &tt1;
     struct pg_tm tt2,
@@ -3617,84 +3625,84 @@ timestamp_age(PG_FUNCTION_ARGS)
         timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
     {
         /* form the symbolic difference */
-        fsec = 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;
-        tm->tm_mday = tm1->tm_mday - tm2->tm_mday;
-        tm->tm_mon = tm1->tm_mon - tm2->tm_mon;
-        tm->tm_year = tm1->tm_year - tm2->tm_year;
+        itm->tm_usec = fsec1 - fsec2;
+        itm->tm_sec = tm1->tm_sec - tm2->tm_sec;
+        itm->tm_min = tm1->tm_min - tm2->tm_min;
+        itm->tm_hour = tm1->tm_hour - tm2->tm_hour;
+        itm->tm_mday = tm1->tm_mday - tm2->tm_mday;
+        itm->tm_mon = tm1->tm_mon - tm2->tm_mon;
+        itm->tm_year = tm1->tm_year - tm2->tm_year;

         /* flip sign if necessary... */
         if (dt1 < dt2)
         {
-            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;
+            itm->tm_usec = -itm->tm_usec;
+            itm->tm_sec = -itm->tm_sec;
+            itm->tm_min = -itm->tm_min;
+            itm->tm_hour = -itm->tm_hour;
+            itm->tm_mday = -itm->tm_mday;
+            itm->tm_mon = -itm->tm_mon;
+            itm->tm_year = -itm->tm_year;
         }

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

-        while (tm->tm_sec < 0)
+        while (itm->tm_sec < 0)
         {
-            tm->tm_sec += SECS_PER_MINUTE;
-            tm->tm_min--;
+            itm->tm_sec += SECS_PER_MINUTE;
+            itm->tm_min--;
         }

-        while (tm->tm_min < 0)
+        while (itm->tm_min < 0)
         {
-            tm->tm_min += MINS_PER_HOUR;
-            tm->tm_hour--;
+            itm->tm_min += MINS_PER_HOUR;
+            itm->tm_hour--;
         }

-        while (tm->tm_hour < 0)
+        while (itm->tm_hour < 0)
         {
-            tm->tm_hour += HOURS_PER_DAY;
-            tm->tm_mday--;
+            itm->tm_hour += HOURS_PER_DAY;
+            itm->tm_mday--;
         }

-        while (tm->tm_mday < 0)
+        while (itm->tm_mday < 0)
         {
             if (dt1 < dt2)
             {
-                tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
-                tm->tm_mon--;
+                itm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
+                itm->tm_mon--;
             }
             else
             {
-                tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
-                tm->tm_mon--;
+                itm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
+                itm->tm_mon--;
             }
         }

-        while (tm->tm_mon < 0)
+        while (itm->tm_mon < 0)
         {
-            tm->tm_mon += MONTHS_PER_YEAR;
-            tm->tm_year--;
+            itm->tm_mon += MONTHS_PER_YEAR;
+            itm->tm_year--;
         }

         /* recover sign if necessary... */
         if (dt1 < dt2)
         {
-            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;
+            itm->tm_usec = -itm->tm_usec;
+            itm->tm_sec = -itm->tm_sec;
+            itm->tm_min = -itm->tm_min;
+            itm->tm_hour = -itm->tm_hour;
+            itm->tm_mday = -itm->tm_mday;
+            itm->tm_mon = -itm->tm_mon;
+            itm->tm_year = -itm->tm_year;
         }

-        if (tm2interval(tm, fsec, result) != 0)
+        if (itm2interval(itm, result) != 0)
             ereport(ERROR,
                     (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                      errmsg("interval out of range")));
@@ -3720,11 +3728,10 @@ 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,
-               *tm = &tt;
+    struct pg_itm tt,
+               *itm = &tt;
     struct pg_tm tt1,
                *tm1 = &tt1;
     struct pg_tm tt2,
@@ -3738,69 +3745,69 @@ timestamptz_age(PG_FUNCTION_ARGS)
         timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
     {
         /* form the symbolic difference */
-        fsec = 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;
-        tm->tm_mday = tm1->tm_mday - tm2->tm_mday;
-        tm->tm_mon = tm1->tm_mon - tm2->tm_mon;
-        tm->tm_year = tm1->tm_year - tm2->tm_year;
+        itm->tm_usec = fsec1 - fsec2;
+        itm->tm_sec = tm1->tm_sec - tm2->tm_sec;
+        itm->tm_min = tm1->tm_min - tm2->tm_min;
+        itm->tm_hour = tm1->tm_hour - tm2->tm_hour;
+        itm->tm_mday = tm1->tm_mday - tm2->tm_mday;
+        itm->tm_mon = tm1->tm_mon - tm2->tm_mon;
+        itm->tm_year = tm1->tm_year - tm2->tm_year;

         /* flip sign if necessary... */
         if (dt1 < dt2)
         {
-            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;
+            itm->tm_usec = -itm->tm_usec;
+            itm->tm_sec = -itm->tm_sec;
+            itm->tm_min = -itm->tm_min;
+            itm->tm_hour = -itm->tm_hour;
+            itm->tm_mday = -itm->tm_mday;
+            itm->tm_mon = -itm->tm_mon;
+            itm->tm_year = -itm->tm_year;
         }

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

-        while (tm->tm_sec < 0)
+        while (itm->tm_sec < 0)
         {
-            tm->tm_sec += SECS_PER_MINUTE;
-            tm->tm_min--;
+            itm->tm_sec += SECS_PER_MINUTE;
+            itm->tm_min--;
         }

-        while (tm->tm_min < 0)
+        while (itm->tm_min < 0)
         {
-            tm->tm_min += MINS_PER_HOUR;
-            tm->tm_hour--;
+            itm->tm_min += MINS_PER_HOUR;
+            itm->tm_hour--;
         }

-        while (tm->tm_hour < 0)
+        while (itm->tm_hour < 0)
         {
-            tm->tm_hour += HOURS_PER_DAY;
-            tm->tm_mday--;
+            itm->tm_hour += HOURS_PER_DAY;
+            itm->tm_mday--;
         }

-        while (tm->tm_mday < 0)
+        while (itm->tm_mday < 0)
         {
             if (dt1 < dt2)
             {
-                tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
-                tm->tm_mon--;
+                itm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
+                itm->tm_mon--;
             }
             else
             {
-                tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
-                tm->tm_mon--;
+                itm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
+                itm->tm_mon--;
             }
         }

-        while (tm->tm_mon < 0)
+        while (itm->tm_mon < 0)
         {
-            tm->tm_mon += MONTHS_PER_YEAR;
-            tm->tm_year--;
+            itm->tm_mon += MONTHS_PER_YEAR;
+            itm->tm_year--;
         }

         /*
@@ -3810,16 +3817,16 @@ timestamptz_age(PG_FUNCTION_ARGS)
         /* recover sign if necessary... */
         if (dt1 < dt2)
         {
-            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;
+            itm->tm_usec = -itm->tm_usec;
+            itm->tm_sec = -itm->tm_sec;
+            itm->tm_min = -itm->tm_min;
+            itm->tm_hour = -itm->tm_hour;
+            itm->tm_mday = -itm->tm_mday;
+            itm->tm_mon = -itm->tm_mon;
+            itm->tm_year = -itm->tm_year;
         }

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

     result = (Interval *) palloc(sizeof(Interval));

@@ -4320,45 +4326,45 @@ interval_trunc(PG_FUNCTION_ARGS)

     if (type == UNITS)
     {
-        if (interval2tm(*interval, tm, &fsec) == 0)
+        if (interval2itm(*interval, itm) == 0)
         {
             switch (val)
             {
                 case DTK_MILLENNIUM:
                     /* caution: C division may have negative remainder */
-                    tm->tm_year = (tm->tm_year / 1000) * 1000;
+                    itm->tm_year = (itm->tm_year / 1000) * 1000;
                     /* FALL THRU */
                 case DTK_CENTURY:
                     /* caution: C division may have negative remainder */
-                    tm->tm_year = (tm->tm_year / 100) * 100;
+                    itm->tm_year = (itm->tm_year / 100) * 100;
                     /* FALL THRU */
                 case DTK_DECADE:
                     /* caution: C division may have negative remainder */
-                    tm->tm_year = (tm->tm_year / 10) * 10;
+                    itm->tm_year = (itm->tm_year / 10) * 10;
                     /* FALL THRU */
                 case DTK_YEAR:
-                    tm->tm_mon = 0;
+                    itm->tm_mon = 0;
                     /* FALL THRU */
                 case DTK_QUARTER:
-                    tm->tm_mon = 3 * (tm->tm_mon / 3);
+                    itm->tm_mon = 3 * (itm->tm_mon / 3);
                     /* FALL THRU */
                 case DTK_MONTH:
-                    tm->tm_mday = 0;
+                    itm->tm_mday = 0;
                     /* FALL THRU */
                 case DTK_DAY:
-                    tm->tm_hour = 0;
+                    itm->tm_hour = 0;
                     /* FALL THRU */
                 case DTK_HOUR:
-                    tm->tm_min = 0;
+                    itm->tm_min = 0;
                     /* FALL THRU */
                 case DTK_MINUTE:
-                    tm->tm_sec = 0;
+                    itm->tm_sec = 0;
                     /* FALL THRU */
                 case DTK_SECOND:
-                    fsec = 0;
+                    itm->tm_usec = 0;
                     break;
                 case DTK_MILLISEC:
-                    fsec = (fsec / 1000) * 1000;
+                    itm->tm_usec = (itm->tm_usec / 1000) * 1000;
                     break;
                 case DTK_MICROSEC:
                     break;
@@ -4371,7 +4377,7 @@ interval_trunc(PG_FUNCTION_ARGS)
                              (val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
             }

-            if (tm2interval(tm, fsec, result) != 0)
+            if (itm2interval(itm, result) != 0)
                 ereport(ERROR,
                         (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                          errmsg("interval out of range")));
@@ -5189,9 +5195,8 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
     int            type,
                 val;
     char       *lowunits;
-    fsec_t        fsec;
-    struct pg_tm tt,
-               *tm = &tt;
+    struct pg_itm tt,
+               *itm = &tt;

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

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

                 case DTK_MILLISEC:
@@ -5217,9 +5222,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(itm->tm_sec * INT64CONST(1000000) + +
itm->tm_usec,3)); 
                     else
-                        PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
+                        PG_RETURN_FLOAT8(itm->tm_sec * 1000.0 + itm->tm_usec / 1000.0);
                     break;

                 case DTK_SECOND:
@@ -5228,48 +5233,48 @@ 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(itm->tm_sec * INT64CONST(1000000) + +
itm->tm_usec,6)); 
                     else
-                        PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
+                        PG_RETURN_FLOAT8(itm->tm_sec + itm->tm_usec / 1000000.0);
                     break;

                 case DTK_MINUTE:
-                    intresult = tm->tm_min;
+                    intresult = itm->tm_min;
                     break;

                 case DTK_HOUR:
-                    intresult = tm->tm_hour;
+                    intresult = itm->tm_hour;
                     break;

                 case DTK_DAY:
-                    intresult = tm->tm_mday;
+                    intresult = itm->tm_mday;
                     break;

                 case DTK_MONTH:
-                    intresult = tm->tm_mon;
+                    intresult = itm->tm_mon;
                     break;

                 case DTK_QUARTER:
-                    intresult = (tm->tm_mon / 3) + 1;
+                    intresult = (itm->tm_mon / 3) + 1;
                     break;

                 case DTK_YEAR:
-                    intresult = tm->tm_year;
+                    intresult = itm->tm_year;
                     break;

                 case DTK_DECADE:
                     /* caution: C division may have negative remainder */
-                    intresult = tm->tm_year / 10;
+                    intresult = itm->tm_year / 10;
                     break;

                 case DTK_CENTURY:
                     /* caution: C division may have negative remainder */
-                    intresult = tm->tm_year / 100;
+                    intresult = itm->tm_year / 100;
                     break;

                 case DTK_MILLENNIUM:
                     /* caution: C division may have negative remainder */
-                    intresult = tm->tm_year / 1000;
+                    intresult = itm->tm_year / 1000;
                     break;

                 default:
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 5fa38d20d8..ba918a2b22 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -64,9 +64,11 @@ typedef struct
 /*
  * Assorted constants for datetime-related calculations
  */
-
-#define DAYS_PER_YEAR    365.25    /* assumes leap year every four years */
-#define MONTHS_PER_YEAR 12
+#define YEARS_PER_MILLENNIUM    1000
+#define YEARS_PER_CENTURY        100
+#define YEARS_PER_DECADE        10
+#define DAYS_PER_YEAR            365.25    /* assumes leap year every four years */
+#define MONTHS_PER_YEAR            12
 /*
  *    DAYS_PER_MONTH is very imprecise.  The more accurate value is
  *    365.2425/12 = 30.436875, or '30 days 10:29:06'.  Right now we only
@@ -92,6 +94,7 @@ typedef struct
 #define USECS_PER_HOUR    INT64CONST(3600000000)
 #define USECS_PER_MINUTE INT64CONST(60000000)
 #define USECS_PER_SEC    INT64CONST(1000000)
+#define USECS_PER_MSEC    INT64CONST(1000)

 /*
  * We allow numeric timezone offsets up to 15:59:59 either way from Greenwich.
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..708d1e0cc9 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -44,6 +44,31 @@ struct pg_tm
     const char *tm_zone;
 };

+/* data structure to help decode intervals */
+struct pg_itm_in
+{
+    int64        tm_usec;
+    int            tm_mday;
+    int            tm_mon;            /* see above */
+    int            tm_year;        /* see above */
+};
+
+/* data structure to help encode and manipulate intervals */
+struct pg_itm
+{
+    /* time units smaller than hours only have values less than 1 hour and can
+     * fit into an int
+     */
+    int            tm_usec;
+    int            tm_sec;
+    int            tm_min;
+    int64        tm_hour;
+    int            tm_mday;
+    int            tm_mon;            /* see above */
+    int            tm_year;        /* see above */
+    int            tm_yday;
+};
+
 typedef struct pg_tz pg_tz;
 typedef struct pg_tzenum pg_tzenum;

diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 666e545496..bf3e2a9336 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -53,6 +53,7 @@ extern int    pg_ltoa(int32 l, char *a);
 extern int    pg_lltoa(int64 ll, char *a);
 extern char *pg_ultostr_zeropad(char *str, uint32 value, int32 minwidth);
 extern char *pg_ultostr(char *str, uint32 value);
+extern char *pg_ulltostr(char *str, uint64 value);

 /* oid.c */
 extern oidvector *buildoidvector(const Oid *oids, int n);
diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..d4d699c8f7 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);
-extern int    DecodeISO8601Interval(char *str,
-                                  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+                           int *dtype, struct pg_itm_in *tm);
+extern int    DecodeISO8601Interval(char *str, 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..eea429dc5f 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 int    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';

Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Mon, Mar 21, 2022 at 8:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> This isn't applying per the cfbot; looks like it got sideswiped
> by 9e9858389.  Here's a quick rebase.  I've not reviewed it, but
> I did notice (because git was in my face about this) that it's
> got whitespace issues.  Please try to avoid unnecessary whitespace
> changes ... pgindent will clean those up, but it makes reviewing
> harder.

Sorry about that, I didn't have my IDE set up quite right and
noticed a little too late that I had some auto-formatting turned
on. Thanks for doing the rebase, did it end up fixing
the whitespace issues? If not I'll go through the patch and try
and fix them all.

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> Sorry about that, I didn't have my IDE set up quite right and
> noticed a little too late that I had some auto-formatting turned
> on. Thanks for doing the rebase, did it end up fixing
> the whitespace issues? If not I'll go through the patch and try
> and fix them all.

No, I just fixed the merge failure.

Our standard way to clean up whitespace issues and make sure code
meets our layout conventions is to run pgindent over it [1].
For this particular patch, that might be too much, because it will
reindent the sections that you added braces around, making the patch
harder to review.  So maybe the best bet is to leave well enough
alone and expect the committer to re-pgindent before pushing it.
However, if you spot any diff hunks where there's just a whitespace
change, getting rid of those would be appreciated.

            regards, tom lane

[1] https://wiki.postgresql.org/wiki/Running_pgindent_on_non-core_code_or_development_code



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
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';

Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
I wrote:
> ... 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.

As best I can tell, the case isn't reachable with spec-compliant input,
but it's easy to demonstrate an issue if you set intervalstyle to
sql_standard and then put in Postgres-format input.  Historically,
you got

regression=# show intervalstyle;
 IntervalStyle 
---------------
 postgres
(1 row)

regression=# select '-23 hours 45 min 12.34 sec'::interval;
   interval   
--------------
 -22:14:47.66
(1 row)

(because by default the field signs are taken as independent)

regression=# set intervalstyle = sql_standard ;
SET
regression=# select '-23 hours 45 min 12.34 sec'::interval;
   interval   
--------------
 -23:45:12.34
(1 row)

However, with this patch both cases produce "-22:14:47.66",
because we already merged the differently-signed fields and
DecodeInterval can't tease them apart again.  Perhaps we could
get away with changing this nonstandard corner case, but I'm
pretty uncomfortable with that thought --- I don't think
random semantics changes are within the charter of this patch.

I think the patch can be salvaged, though.  I like the concept
of converting all the sub-day fields to microseconds immediately,
because it avoids a host of issues, so I don't want to give that up.
What I'm going to look into is detecting the sign-adjustment-needed
case up front (which is easy enough, since it's looking at the
input data not the conversion results) and then forcing the
individual field values negative before we accumulate them into
the pg_itm_in struct.

Meanwhile, the fact that we didn't detect this issue immediately
shows that there's a gap in our regression tests.  So the *first*
thing I'm gonna do is push a patch to add test cases like what
I showed above.

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Fri, Apr 1, 2022 at 8:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> 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?

Yeah, you're correct, I don't think there is any problem with just
using double. I don't exactly remember why I thought long double
was necessary in the revised code. I probably just confused
myself because it would have been necessary with the old
rounding code, but not the revised code.

> Anyway, I've spent today reviewing the code and cleaning up things
> I didn't like, and attached is a v10.

Thanks so much for the review and updates!

> I think the patch can be salvaged, though.  I like the concept
> of converting all the sub-day fields to microseconds immediately,
> because it avoids a host of issues, so I don't want to give that up.
> What I'm going to look into is detecting the sign-adjustment-needed
> case up front (which is easy enough, since it's looking at the
> input data not the conversion results) and then forcing the
> individual field values negative before we accumulate them into
> the pg_itm_in struct.

This sounds like a very reasonable and achievable approach
to me.

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sat, Apr 2, 2022 at 1:29 PM Joseph Koshakow <koshy44@gmail.com> wrote:
>
> On Fri, Apr 1, 2022 at 8:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >
> > 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?
>
> Yeah, you're correct, I don't think there is any problem with just
> using double. I don't exactly remember why I thought long double
> was necessary in the revised code. I probably just confused
> myself because it would have been necessary with the old
> rounding code, but not the revised code.

Ok I actually remember now, the issue is with the rounding
code in AdjustFractMicroseconds.

>    frac *= scale;
>    usec = (int64) frac;
>
>    /* Round off any fractional microsecond */
>    frac -= usec;
>    if (frac > 0.5)
>       usec++;
>    else if (frac < -0.5)
>       usec--;

I believe it's possible for `frac -= usec;` to result in a value greater
than 1 or less than -1 due to the lossiness of int64 to double
conversions. Then we'd incorrectly round in one direction. I don't
have a concrete counter example, but at worst we'd end up with a
result that's a couple of microseconds off, so it's probably not a huge
deal.

If I'm right about the above, and we care enough to fix it, then I think
it can be fixed with the following:

>    frac *= scale;
>    usec = (int64) frac;
>
>    /* Remove non fractional part from frac */
>    frac -= (double) usec;
>    /* Adjust for lossy conversion from int64 to double */
>    while (frac < 0 && frac < -1)
>       frac++;
>    while (frac > 0 && frac > 1)
>       frac--;
>
>    /* Round off any fractional microsecond */
>    if (frac > 0.5)
>       usec++;
>    else if (frac < -0.5)
>       usec--;

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sat, Apr 2, 2022 at 2:22 PM Joseph Koshakow <koshy44@gmail.com> wrote:
>
> On Sat, Apr 2, 2022 at 1:29 PM Joseph Koshakow <koshy44@gmail.com> wrote:
> >
> > On Fri, Apr 1, 2022 at 8:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > >
> > > 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?
> >
> > Yeah, you're correct, I don't think there is any problem with just
> > using double. I don't exactly remember why I thought long double
> > was necessary in the revised code. I probably just confused
> > myself because it would have been necessary with the old
> > rounding code, but not the revised code.
>
> Ok I actually remember now, the issue is with the rounding
> code in AdjustFractMicroseconds.
>
> >    frac *= scale;
> >    usec = (int64) frac;
> >
> >    /* Round off any fractional microsecond */
> >    frac -= usec;
> >    if (frac > 0.5)
> >       usec++;
> >    else if (frac < -0.5)
> >       usec--;
>
> I believe it's possible for `frac -= usec;` to result in a value greater
> than 1 or less than -1 due to the lossiness of int64 to double
> conversions. Then we'd incorrectly round in one direction. I don't
> have a concrete counter example, but at worst we'd end up with a
> result that's a couple of microseconds off, so it's probably not a huge
> deal.
>
> If I'm right about the above, and we care enough to fix it, then I think
> it can be fixed with the following:
>
> >    frac *= scale;
> >    usec = (int64) frac;
> >
> >    /* Remove non fractional part from frac */
> >    frac -= (double) usec;
> >    /* Adjust for lossy conversion from int64 to double */
> >    while (frac < 0 && frac < -1)
> >       frac++;
> >    while (frac > 0 && frac > 1)
> >       frac--;
> >
> >    /* Round off any fractional microsecond */
> >    if (frac > 0.5)
> >       usec++;
> >    else if (frac < -0.5)
> >       usec--;


Sorry, those should be inclusive comparisons
>    frac *= scale;
>    usec = (int64) frac;
>
>    /* Remove non fractional part from frac */
>    frac -= (double) usec;
>    /* Adjust for lossy conversion from int64 to double */
>    while (frac < 0 && frac <= -1)
>       frac++;
>    while (frac > 0 && frac >= 1)
>       frac--;
>
>    /* Round off any fractional microsecond */
>    if (frac > 0.5)
>       usec++;
>    else if (frac < -0.5)
>       usec--;



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Fri, Apr 1, 2022 at 8:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I think the patch can be salvaged, though.  I like the concept
> of converting all the sub-day fields to microseconds immediately,
> because it avoids a host of issues, so I don't want to give that up.
> What I'm going to look into is detecting the sign-adjustment-needed
> case up front (which is easy enough, since it's looking at the
> input data not the conversion results) and then forcing the
> individual field values negative before we accumulate them into
> the pg_itm_in struct.

I took a stab at this issue and the attached patch (which would be
applied on top of your v10 patch) seems to fix the issue. Feel
free to ignore it if you're already working on a fix.

- Joe

Attachment

Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> Ok I actually remember now, the issue is with the rounding
> code in AdjustFractMicroseconds.
> ...
> I believe it's possible for `frac -= usec;` to result in a value greater
> than 1 or less than -1 due to the lossiness of int64 to double
> conversions.

I think it's not, at least not for the interesting range of possible
values in this code.  Given that abs(frac) < 1 to start with, the
abs value of usec can't exceed the value of scale, which is at most 
USECS_PER_DAY so it's at most 37 or so bits, which is well within
the exact range for any sane implementation of double.  It would
take a very poor floating-point implementation to not get the right
answer here.  (And we're largely assuming IEEE-compliant floats these
days.)

Anyway, the other issue indeed turns out to be easy to fix.
Attached is a v11 that deals with that.  If the cfbot doesn't
complain about it, I'll push this later today.

            regards, tom lane

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ba0ec35ac5..462f2ed7a8 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 has abs value 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 has abs value 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,53 @@ 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        force_negative = false;
     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);
+
+    /*----------
+     * The SQL standard defines the interval literal
+     *     '-1 1:00:00'
+     * to mean "negative 1 days and negative 1 hours", while Postgres
+     * traditionally treats this as meaning "negative 1 days and positive
+     * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
+     * to all fields if there are no other explicit signs.
+     *
+     * We leave the signs alone if there are additional explicit signs.
+     * This protects us against misinterpreting postgres-style dump output,
+     * since the postgres-style output code has always put an explicit sign on
+     * all fields following a negative field.  But note that SQL-spec output
+     * is ambiguous and can be misinterpreted on load!    (So it's best practice
+     * to dump in postgres style, not SQL style.)
+     *----------
+     */
+    if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
+    {
+        force_negative = true;
+        /* Check for additional explicit signs */
+        for (i = 1; i < nf; i++)
+        {
+            if (*field[i] == '-' || *field[i] == '+')
+            {
+                force_negative = false;
+                break;
+            }
+        }
+    }

     /* read through list backwards to pick up units before values */
     for (i = nf - 1; i >= 0; i--)
@@ -3116,10 +3348,13 @@ 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;
+                if (force_negative &&
+                    itm_in->tm_usec > 0)
+                    itm_in->tm_usec = -itm_in->tm_usec;
                 type = DTK_DAY;
                 break;

@@ -3137,18 +3372,21 @@ 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;
                     }

+                    if (force_negative &&
+                        itm_in->tm_usec > 0)
+                        itm_in->tm_usec = -itm_in->tm_usec;
+
                     /*
                      * Set the next type to be a day, if units are not
                      * specified. This handles the case of '1 +02:03' since we
@@ -3204,7 +3442,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 +3459,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 == '.')
@@ -3244,24 +3482,32 @@ DecodeInterval(char **field, int *ftype, int nf, int range,

                 tmask = 0;        /* DTK_M(type); */

+                if (force_negative)
+                {
+                    /* val and fval should be of same sign, but test anyway */
+                    if (val > 0)
+                        val = -val;
+                    if (fval > 0)
+                        fval = -fval;
+                }
+
                 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 +3520,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 +3588,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 +3596,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,79 +3627,19 @@ 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'
-     * to mean "negative 1 days and negative 1 hours", while Postgres
-     * traditionally treats this as meaning "negative 1 days and positive
-     * 1 hours".  In SQL_STANDARD intervalstyle, we apply the leading sign
-     * to all fields if there are no other explicit signs.
-     *
-     * We leave the signs alone if there are additional explicit signs.
-     * This protects us against misinterpreting postgres-style dump output,
-     * since the postgres-style output code has always put an explicit sign on
-     * all fields following a negative field.  But note that SQL-spec output
-     * is ambiguous and can be misinterpreted on load!    (So it's best practice
-     * to dump in postgres style, not SQL style.)
-     *----------
-     */
-    if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
-    {
-        /* Check for additional explicit signs */
-        bool        more_signs = false;
-
-        for (i = 1; i < nf; i++)
-        {
-            if (*field[i] == '-' || *field[i] == '+')
-            {
-                more_signs = true;
-                break;
-            }
-        }
-
-        if (!more_signs)
-        {
-            /*
-             * 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;
-        }
-    }
-
     /* 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 2ba8d41284..f9489144c7 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -889,9 +889,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;
@@ -900,13 +899,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);
@@ -917,12 +913,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)
     {
@@ -936,7 +932,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")));
@@ -960,15 +956,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);
@@ -1960,50 +1953,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;
 }

@@ -3612,10 +3632,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;
@@ -3628,7 +3647,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;
@@ -3639,7 +3658,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;
@@ -3649,9 +3668,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--;
         }

@@ -3696,7 +3715,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;
@@ -3705,7 +3724,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")));
@@ -3731,10 +3750,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;
@@ -3749,7 +3767,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;
@@ -3760,7 +3778,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;
@@ -3770,9 +3788,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--;
         }

@@ -3821,7 +3839,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;
@@ -3830,7 +3848,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")));
@@ -4317,8 +4335,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));
@@ -4331,7 +4348,7 @@ interval_trunc(PG_FUNCTION_ARGS)

     if (type == UNITS)
     {
-        if (interval2tm(*interval, tm, &fsec) == 0)
+        interval2itm(*interval, tm);
         {
             switch (val)
             {
@@ -4366,10 +4383,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;
@@ -4382,13 +4399,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
     {
@@ -5200,8 +5215,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),
@@ -5214,12 +5228,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:
@@ -5228,9 +5242,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:
@@ -5239,9 +5253,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:
@@ -5291,11 +5305,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 9a7e2852f2..86c8d4bc99 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -928,6 +928,617 @@ select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
  @ 0.7 secs | @ 0.7 secs | @ 0.7 secs
 (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 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)
+
+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)
+
 -- check that '30 days' equals '1 month' according to the hash function
 select '30 days'::interval = '1 month'::interval as t;
  t
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 811b581e09..f05055e03a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -318,6 +318,190 @@ select interval '-10 mons -3 days +03:55:06.70';
 select interval '1 year 2 mons 3 days 04:05:06.699999';
 select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';

+-- 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 sql_standard;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to iso_8601;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to postgres_verbose;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+
 -- check that '30 days' equals '1 month' according to the hash function
 select '30 days'::interval = '1 month'::interval as t;
 select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;

Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> I took a stab at this issue and the attached patch (which would be
> applied on top of your v10 patch) seems to fix the issue. Feel
> free to ignore it if you're already working on a fix.

You really only need to flip val/fval in one place.  More to the
point, there's also the hh:mm:ss paths to deal with; see my v11.

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sat, Apr 2, 2022 at 3:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Joseph Koshakow <koshy44@gmail.com> writes:
> > Ok I actually remember now, the issue is with the rounding
> > code in AdjustFractMicroseconds.
> > ...
> > I believe it's possible for `frac -= usec;` to result in a value greater
> > than 1 or less than -1 due to the lossiness of int64 to double
> > conversions.
>
> I think it's not, at least not for the interesting range of possible
> values in this code.  Given that abs(frac) < 1 to start with, the
> abs value of usec can't exceed the value of scale, which is at most
> USECS_PER_DAY so it's at most 37 or so bits, which is well within
> the exact range for any sane implementation of double.  It would
> take a very poor floating-point implementation to not get the right
> answer here.  (And we're largely assuming IEEE-compliant floats these
> days.)

Ah, I see. That makes sense to me.

On Sat, Apr 2, 2022 at 3:10 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Joseph Koshakow <koshy44@gmail.com> writes:
> > I took a stab at this issue and the attached patch (which would be
> > applied on top of your v10 patch) seems to fix the issue. Feel
> > free to ignore it if you're already working on a fix.
>
> You really only need to flip val/fval in one place.  More to the
> point, there's also the hh:mm:ss paths to deal with; see my v11.

Good point. Thanks again for all the help!

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> On Sat, Apr 2, 2022 at 3:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I think it's not, at least not for the interesting range of possible
>> values in this code.  Given that abs(frac) < 1 to start with, the
>> abs value of usec can't exceed the value of scale, which is at most
>> USECS_PER_DAY so it's at most 37 or so bits, which is well within
>> the exact range for any sane implementation of double.  It would
>> take a very poor floating-point implementation to not get the right
>> answer here.  (And we're largely assuming IEEE-compliant floats these
>> days.)

> Ah, I see. That makes sense to me.

Cool.  I've pushed the patch.

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
I wrote:
> Cool.  I've pushed the patch.

Hmm ... buildfarm's not entirely happy [1][2][3]:

diff -U3 /home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/expected/interval.out
/home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/results/interval.out
--- /home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/expected/interval.out    2022-04-03 04:56:32.000000000 +0000
+++ /home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/results/interval.out    2022-04-03 05:23:00.000000000 +0000
@@ -1465,7 +1465,7 @@
  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"
+ ERROR:  invalid input syntax for type interval: "PT2562047788:01.:54.775807"
  LINE 1: select interval 'PT2562047788:01.:54.775807';
                          ^
  -- overflowing with fractional fields - SQL standard format

What do you make of that?  I'm betting that strtod() works a
bit differently on those old platforms, but too tired to
look closer tonight.

            regards, tom lane

[1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=sungazer&dt=2022-04-03%2004%3A56%3A34
[2] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=hoverfly&dt=2022-04-03%2000%3A51%3A50
[3] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=anole&dt=2022-04-03%2000%3A32%3A10



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Apr 3, 2022 at 3:09 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> I wrote:
> > Cool.  I've pushed the patch.
>
> Hmm ... buildfarm's not entirely happy [1][2][3]:
>
> diff -U3 /home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/expected/interval.out
/home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/results/interval.out
> --- /home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/expected/interval.out 2022-04-03 04:56:32.000000000 +0000
> +++ /home/nm/farm/gcc64/HEAD/pgsql.build/src/test/regress/results/interval.out  2022-04-03 05:23:00.000000000 +0000
> @@ -1465,7 +1465,7 @@
>   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"
> + ERROR:  invalid input syntax for type interval: "PT2562047788:01.:54.775807"
>   LINE 1: select interval 'PT2562047788:01.:54.775807';
>                           ^
>   -- overflowing with fractional fields - SQL standard format
>
> What do you make of that?  I'm betting that strtod() works a
> bit differently on those old platforms, but too tired to
> look closer tonight.
>
>                         regards, tom lane
>
> [1] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=sungazer&dt=2022-04-03%2004%3A56%3A34
> [2] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=hoverfly&dt=2022-04-03%2000%3A51%3A50
> [3] https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=anole&dt=2022-04-03%2000%3A32%3A10

I think I know that the issue is. It's with `ParseISO8601Number` and
the minutes field "1.".
Previously that function parsed the entire field into a single double,
so "1." would
be parsed into 1.0. Now we try to parse the integer and decimal parts
separately. So
we first parse "1" into 1 and then fail to "." into anything because
it's not a valid decimal.

What's interesting is that I believe this syntax, "1.", always would
have failed for
non-ISO8601 Interval. It was only previously valid with ISO8601 intervals.

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> On Sun, Apr 3, 2022 at 3:09 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Hmm ... buildfarm's not entirely happy [1][2][3]:

> I think I know that the issue is. It's with `ParseISO8601Number` and
> the minutes field "1.".
> Previously that function parsed the entire field into a single double,
> so "1." would
> be parsed into 1.0. Now we try to parse the integer and decimal parts
> separately. So
> we first parse "1" into 1 and then fail to "." into anything because
> it's not a valid decimal.

Interesting point, but then why doesn't it fail everywhere?

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
I wrote:
> Joseph Koshakow <koshy44@gmail.com> writes:
>> I think I know that the issue is. It's with `ParseISO8601Number` and
>> the minutes field "1.".
>> Previously that function parsed the entire field into a single double,
>> so "1." would
>> be parsed into 1.0. Now we try to parse the integer and decimal parts
>> separately. So
>> we first parse "1" into 1 and then fail to "." into anything because
>> it's not a valid decimal.

> Interesting point, but then why doesn't it fail everywhere?

Oh ... a bit of testing says that strtod() on an empty string
succeeds (returning zero) on Linux, but fails with EINVAL on
AIX.  The latter is a lot less surprising than the former,
so we'd better cope.

(Reading POSIX with an eagle eye, it looks like both behaviors
are allowed per spec: this is why you have to check that endptr
was advanced to be sure everything is kosher.)

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Apr 3, 2022 at 12:03 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> I wrote:
> > Joseph Koshakow <koshy44@gmail.com> writes:
> >> I think I know that the issue is. It's with `ParseISO8601Number` and
> >> the minutes field "1.".
> >> Previously that function parsed the entire field into a single double,
> >> so "1." would
> >> be parsed into 1.0. Now we try to parse the integer and decimal parts
> >> separately. So
> >> we first parse "1" into 1 and then fail to "." into anything because
> >> it's not a valid decimal.
>
> > Interesting point, but then why doesn't it fail everywhere?
>
> Oh ... a bit of testing says that strtod() on an empty string
> succeeds (returning zero) on Linux, but fails with EINVAL on
> AIX.  The latter is a lot less surprising than the former,
> so we'd better cope.
>
> (Reading POSIX with an eagle eye, it looks like both behaviors
> are allowed per spec: this is why you have to check that endptr
> was advanced to be sure everything is kosher.)
>
>                         regards, tom lane

I'm not sure I follow exactly. Where would we pass an empty
string to strtod()? Wouldn't we be passing a string with a
single character of '.'?

Either way, from reading the man pages though it seems
that strtod() has the same behavior on any invalid input in
Linux, return 0 and don't advance endptr.

So I think we need to check that endptr has moved both after
the call to strtoi64() and strtod().

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> On Sun, Apr 3, 2022 at 12:03 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Oh ... a bit of testing says that strtod() on an empty string
>> succeeds (returning zero) on Linux, but fails with EINVAL on
>> AIX.  The latter is a lot less surprising than the former,
>> so we'd better cope.

> I'm not sure I follow exactly. Where would we pass an empty
> string to strtod()? Wouldn't we be passing a string with a
> single character of '.'?

Oh, I was thinking that we passed "cp + 1" to strtod, but that
was just caffeine deprivation.  You're right, what we are asking
it to parse is "." not "".  The result is the same though:
per testing, AIX sets EINVAL and Linux doesn't.

> So I think we need to check that endptr has moved both after
> the call to strtoi64() and strtod().

I'm not sure we need to do that explicitly, given that there's
a check later as to whether endptr is pointing at \0; that will
fail if endptr wasn't advanced.

The fix I was loosely envisioning was to check for cp[1] == '\0'
and not bother calling strtod() in that case.

            regards, tom lane



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Apr 3, 2022 at 12:30 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Joseph Koshakow <koshy44@gmail.com> writes:
> > So I think we need to check that endptr has moved both after
> > the call to strtoi64() and strtod().
>
> I'm not sure we need to do that explicitly, given that there's
> a check later as to whether endptr is pointing at \0; that will
> fail if endptr wasn't advanced.
>
> The fix I was loosely envisioning was to check for cp[1] == '\0'
> and not bother calling strtod() in that case.

Ah, ok I see what you mean. I agree an approach like that should
work, but I don't actually think cp is null terminated in this case. The
entire Interval is passed to DecodeISO8601Interval() as one big
string, so the specific number we're parsing may be somewhere
in the middle.

If we just do the opposite and check isdigit(cp[1]) and only call
strtod() in that case I think it should work.

- Joe Koshakow



Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Apr 3, 2022 at 12:44 PM Joseph Koshakow <koshy44@gmail.com> wrote:
>
> On Sun, Apr 3, 2022 at 12:30 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >
> > Joseph Koshakow <koshy44@gmail.com> writes:
> > > So I think we need to check that endptr has moved both after
> > > the call to strtoi64() and strtod().
> >
> > I'm not sure we need to do that explicitly, given that there's
> > a check later as to whether endptr is pointing at \0; that will
> > fail if endptr wasn't advanced.
> >
> > The fix I was loosely envisioning was to check for cp[1] == '\0'
> > and not bother calling strtod() in that case.
>
> Ah, ok I see what you mean. I agree an approach like that should
> work, but I don't actually think cp is null terminated in this case. The
> entire Interval is passed to DecodeISO8601Interval() as one big
> string, so the specific number we're parsing may be somewhere
> in the middle.
>
> If we just do the opposite and check isdigit(cp[1]) and only call
> strtod() in that case I think it should work.
>
> - Joe Koshakow

How does this patch look? I don't really have any way to test it on
AIX.

- Joe Koshakow

Attachment

Re: Fix overflow in DecodeInterval

From
Tom Lane
Date:
Joseph Koshakow <koshy44@gmail.com> writes:
> How does this patch look? I don't really have any way to test it on
> AIX.

That buildfarm machine is pretty slow, so I'm not in a hurry to test
it manually either.  However, now that we realize the issue is about
whether strtod(".") produces EINVAL or not, I think we need to fix
all the places in datetime.c that are risking that.  After a bit of
hacking I have the attached.  (I think that the call sites for
strtoint and its variants are not at risk of passing empty strings,
so there's not need for concern there.)

BTW, the way you had it coded would allow 'P.Y0M3DT4H5M6S', which
I don't think we want to allow --- at least, that's rejected by v14
on my machine.

            regards, tom lane

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 462f2ed7a8..4c12c4d663 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -668,19 +668,50 @@ AdjustYears(int64 val, int scale,
 }


-/* Fetch a fractional-second value with suitable error checking */
+/*
+ * Parse the fractional part of a number (decimal point and optional digits,
+ * followed by end of string).  Returns the fractional value into *frac.
+ *
+ * Returns 0 if successful, DTERR code if bogus input detected.
+ */
+static int
+ParseFraction(char *cp, double *frac)
+{
+    /* Caller should always pass the start of the fraction part */
+    Assert(*cp == '.');
+
+    /*
+     * We want to allow just "." with no digits, but some versions of strtod
+     * will report EINVAL for that, so special-case it.
+     */
+    if (cp[1] == '\0')
+    {
+        *frac = 0;
+    }
+    else
+    {
+        errno = 0;
+        *frac = strtod(cp, &cp);
+        /* check for parse failure */
+        if (*cp != '\0' || errno != 0)
+            return DTERR_BAD_FORMAT;
+    }
+    return 0;
+}
+
+/*
+ * Fetch a fractional-second value with suitable error checking.
+ * Same as ParseFraction except we convert the result to integer microseconds.
+ */
 static int
 ParseFractionalSecond(char *cp, fsec_t *fsec)
 {
     double        frac;
+    int            dterr;

-    /* Caller should always pass the start of the fraction part */
-    Assert(*cp == '.');
-    errno = 0;
-    frac = strtod(cp, &cp);
-    /* check for parse failure */
-    if (*cp != '\0' || errno != 0)
-        return DTERR_BAD_FORMAT;
+    dterr = ParseFraction(cp, &frac);
+    if (dterr)
+        return dterr;
     *fsec = rint(frac * 1000000);
     return 0;
 }
@@ -1248,10 +1279,9 @@ DecodeDateTime(char **field, int *ftype, int nf,
                             {
                                 double        time;

-                                errno = 0;
-                                time = strtod(cp, &cp);
-                                if (*cp != '\0' || errno != 0)
-                                    return DTERR_BAD_FORMAT;
+                                dterr = ParseFraction(cp, &time);
+                                if (dterr)
+                                    return dterr;
                                 time *= USECS_PER_DAY;
                                 dt2time(time,
                                         &tm->tm_hour, &tm->tm_min,
@@ -2146,10 +2176,9 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
                             {
                                 double        time;

-                                errno = 0;
-                                time = strtod(cp, &cp);
-                                if (*cp != '\0' || errno != 0)
-                                    return DTERR_BAD_FORMAT;
+                                dterr = ParseFraction(cp, &time);
+                                if (dterr)
+                                    return dterr;
                                 time *= USECS_PER_DAY;
                                 dt2time(time,
                                         &tm->tm_hour, &tm->tm_min,
@@ -3035,13 +3064,21 @@ DecodeNumberField(int len, char *str, int fmask,
          * Can we use ParseFractionalSecond here?  Not clear whether trailing
          * junk should be rejected ...
          */
-        double        frac;
+        if (cp[1] == '\0')
+        {
+            /* avoid assuming that strtod will accept "." */
+            *fsec = 0;
+        }
+        else
+        {
+            double        frac;

-        errno = 0;
-        frac = strtod(cp, NULL);
-        if (errno != 0)
-            return DTERR_BAD_FORMAT;
-        *fsec = rint(frac * 1000000);
+            errno = 0;
+            frac = strtod(cp, NULL);
+            if (errno != 0)
+                return DTERR_BAD_FORMAT;
+            *fsec = rint(frac * 1000000);
+        }
         /* Now truncate off the fraction for further processing */
         *cp = '\0';
         len = strlen(str);
@@ -3467,11 +3504,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                 }
                 else if (*cp == '.')
                 {
-                    errno = 0;
-                    fval = strtod(cp, &cp);
-                    if (*cp != '\0' || errno != 0)
-                        return DTERR_BAD_FORMAT;
-
+                    dterr = ParseFraction(cp, &fval);
+                    if (dterr)
+                        return dterr;
                     if (*field[i] == '-')
                         fval = -fval;
                 }
@@ -3650,6 +3685,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
  * Helper functions to avoid duplicated code in DecodeISO8601Interval.
  *
  * Parse a decimal value and break it into integer and fractional parts.
+ * Set *endptr to end+1 of the parsed substring.
  * Returns 0 or DTERR code.
  */
 static int
@@ -3676,7 +3712,20 @@ ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)

     /* Parse fractional part if there is any */
     if (**endptr == '.')
-        *fpart = strtod(*endptr, endptr) * sign;
+    {
+        /*
+         * As in ParseFraction, some versions of strtod insist on seeing some
+         * digits after '.', but some don't.  We want to allow zero digits
+         * after '.' as long as there were some before it.
+         */
+        if (isdigit((unsigned char) *(*endptr + 1)))
+            *fpart = strtod(*endptr, endptr) * sign;
+        else
+        {
+            (*endptr)++;        /* advance over '.' */
+            str++;                /* so next test will fail if no digits */
+        }
+    }

     /* did we not see anything that looks like a number? */
     if (*endptr == str || errno != 0)
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 86c8d4bc99..03f77c01dc 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -908,6 +908,41 @@ select  interval 'P0002'                  AS "year only",
  2 years   | 2 years 10 mons | 2 years 10 mons 15 days | 2 years 00:00:01    | 2 years 10 mons 00:00:01 | 2 years 10
mons15 days 00:00:01 | 10:00:00  | 10:30:00 
 (1 row)

+-- Check handling of fractional fields in ISO8601 format.
+select interval 'P1Y0M3DT4H5M6S';
+        interval
+------------------------
+ 1 year 3 days 04:05:06
+(1 row)
+
+select interval 'P1.0Y0M3DT4H5M6S';
+        interval
+------------------------
+ 1 year 3 days 04:05:06
+(1 row)
+
+select interval 'P1.1Y0M3DT4H5M6S';
+           interval
+------------------------------
+ 1 year 1 mon 3 days 04:05:06
+(1 row)
+
+select interval 'P1.Y0M3DT4H5M6S';
+        interval
+------------------------
+ 1 year 3 days 04:05:06
+(1 row)
+
+select interval 'P.1Y0M3DT4H5M6S';
+       interval
+-----------------------
+ 1 mon 3 days 04:05:06
+(1 row)
+
+select interval 'P.Y0M3DT4H5M6S';  -- error
+ERROR:  invalid input syntax for type interval: "P.Y0M3DT4H5M6S"
+LINE 1: select interval 'P.Y0M3DT4H5M6S';
+                        ^
 -- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
 SET IntervalStyle to postgres_verbose;
 select interval '-10 mons -3 days +03:55:06.70';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f05055e03a..97d33a1323 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -312,6 +312,14 @@ select  interval 'P0002'                  AS "year only",
         interval 'PT10'                   AS "hour only",
         interval 'PT10:30'                AS "hour minute";

+-- Check handling of fractional fields in ISO8601 format.
+select interval 'P1Y0M3DT4H5M6S';
+select interval 'P1.0Y0M3DT4H5M6S';
+select interval 'P1.1Y0M3DT4H5M6S';
+select interval 'P1.Y0M3DT4H5M6S';
+select interval 'P.1Y0M3DT4H5M6S';
+select interval 'P.Y0M3DT4H5M6S';  -- error
+
 -- test a couple rounding cases that changed since 8.3 w/ HAVE_INT64_TIMESTAMP.
 SET IntervalStyle to postgres_verbose;
 select interval '-10 mons -3 days +03:55:06.70';

Re: Fix overflow in DecodeInterval

From
Joseph Koshakow
Date:
On Sun, Apr 3, 2022 at 3:06 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> That buildfarm machine is pretty slow, so I'm not in a hurry to test
> it manually either.  However, now that we realize the issue is about
> whether strtod(".") produces EINVAL or not, I think we need to fix
> all the places in datetime.c that are risking that.  After a bit of
> hacking I have the attached.  (I think that the call sites for
> strtoint and its variants are not at risk of passing empty strings,
> so there's not need for concern there.)
>
> BTW, the way you had it coded would allow 'P.Y0M3DT4H5M6S', which
> I don't think we want to allow --- at least, that's rejected by v14
> on my machine.


Oh yeah, good catch. Your patch seems like it should
fix all the issues. Thanks again for the help!

- Joe Koshakow