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
From f678ee1bdcd6e075f1b1c771e6d5f6b3f9089086 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <kosh...@gmail.com>
Date: Fri, 11 Feb 2022 14:18:32 -0500
Subject: [PATCH] Check for overflow when decoding an interval

When decoding an interval there are various date units which are
aliases for some multiple of another unit. For example a week is 7 days
and a decade is 10 years. When decoding these specific units, there is
no check for overflow, allowing the interval to overflow. This commit
adds an overflow check for all of these units.

Additionally fractional date/time units are rolled down into the next
smallest unit. For example 0.1 months is 3 days. When adding these
fraction units, there is no check for overflow, allowing the interval
to overflow. This commit adds an overflow check for all of the
fractional units.

Additionally adding the word "ago" to the interval negates every
field. However the negative of INT_MIN is still INT_MIN. This
commit adds a check to make sure that we don't try and negate
a field if it's INT_MIN.

Additionally when encoding an interval there are numerous points
where fields are negating or passed to abs(). When any of the
fields are INT_MIN this results in undefined behavior, but
usually just leaves the value unchanged as INT_MIN. This
commit fixes this issue.

Additionally this commit changes the Interval code to use it's
own intermediate pg_tm data structure that uses int64s instead
of ints. This helps avoid numerous overflow/underflow issues.
Additionally it allows us to use the full range of int64
microseconds instead of being artificially constrained by the
int pg_tm fields.

Signed-off-by: Joseph Koshakow <kosh...@gmail.com>
---
 src/backend/utils/adt/datetime.c          | 413 +++++++++------
 src/backend/utils/adt/formatting.c        |  24 +-
 src/backend/utils/adt/timestamp.c         |  54 +-
 src/common/string.c                       |  15 +
 src/include/common/string.h               |   2 +
 src/include/pgtime.h                      |  12 +
 src/include/utils/datetime.h              |   6 +-
 src/include/utils/timestamp.h             |   5 +-
 src/interfaces/ecpg/pgtypeslib/dt.h       |  20 +-
 src/interfaces/ecpg/pgtypeslib/interval.c | 584 ++++++++++++++--------
 src/test/regress/expected/interval.out    | 147 ++++++
 src/test/regress/sql/interval.sql         |  46 ++
 12 files changed, 946 insertions(+), 382 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 7926258c06..908a67648d 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"
@@ -42,12 +43,15 @@ static int	DecodeTime(char *str, int fmask, int range,
 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, int64 sec, fsec_t fsec,
 						   int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractSeconds(double frac, struct pg_itm *itm, fsec_t *fsec,
 							   int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
+static bool AdjustFractDays(double frac, struct pg_itm *itm, fsec_t *fsec,
 							int scale);
+static bool AdjustFractMonths(double frac, struct pg_itm *itm, int scale);
+static bool AdjustDays(int val, struct pg_itm *itm, int multiplier);
+static bool AdjustYears(int val, struct pg_itm *itm, 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 +432,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, int64 sec, fsec_t fsec, int precision, bool fillzeros)
 {
 	Assert(precision >= 0);
 
@@ -500,33 +504,82 @@ 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.
  */
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractSeconds(double frac, struct pg_itm *itm, fsec_t *fsec, int scale)
 {
 	int			sec;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
+	sec = (int64) frac;
+	if (pg_add_s64_overflow(itm->tm_sec, sec, &itm->tm_sec))
+		return false;
 	frac -= sec;
 	*fsec += rint(frac * 1000000);
+	return true;
 }
 
 /* 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 *itm, fsec_t *fsec, 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->tm_mday, extra_days, &itm->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractSeconds(frac, itm, fsec, SECS_PER_DAY);
+}
+
+/* As above, but initial scale produces months */
+static bool
+AdjustFractMonths(double frac, struct pg_itm *itm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(itm->tm_mon, months, &itm->tm_mon);
+}
+
+/*
+ * Add val to *tm seconds.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustSeconds(int64 val, struct pg_itm *itm)
+{
+	return !pg_add_s64_overflow(itm->tm_sec, val, &itm->tm_sec);
+}
+
+/*
+ * Multiply val by multiplier (to produce days) and add to *tm.
+ * Returns true if successful, false if tm overflows.
+ */
+static bool
+AdjustDays(int val, struct pg_itm *itm, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		!pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday);
+}
+
+/* As above, but initial val produces months */
+static bool
+AdjustMonths(int val, struct pg_itm *itm)
+{
+	return !pg_add_s32_overflow(itm->tm_mon, val, &itm->tm_mon);
+}
+
+/* As above, but initial val produces years */
+static bool
+AdjustYears(int val, struct pg_itm *itm, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		!pg_add_s32_overflow(itm->tm_year, years, &itm->tm_year);
 }
 
 /* Fetch a fractional-second value with suitable error checking */
@@ -3064,12 +3117,28 @@ DecodeSpecial(int field, char *lowtoken, int *val)
 }
 
 
+/* ClearPgItm
+ *
+ * Zero out a pg_itm and associated fsec_t
+ */
+static inline void
+ClearPgItm(struct pg_itm *itm, fsec_t *fsec)
+{
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
+	*fsec = 0;
+}
+
 /* ClearPgTm
  *
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_tm
  */
 static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgTm(struct pg_tm *tm)
 {
 	tm->tm_year = 0;
 	tm->tm_mon = 0;
@@ -3077,7 +3146,6 @@ ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
 	tm->tm_hour = 0;
 	tm->tm_min = 0;
 	tm->tm_sec = 0;
-	*fsec = 0;
 }
 
 
@@ -3094,21 +3162,23 @@ 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 *itm, fsec_t *fsec)
 {
-	bool		is_before = false;
-	char	   *cp;
-	int			fmask = 0,
-				tmask,
-				type;
-	int			i;
-	int			dterr;
-	int			val;
-	double		fval;
+	bool			is_before = false;
+	char	   		*cp;
+	int				fmask = 0,
+					tmask,
+					type;
+	int				i;
+	int				dterr;
+	int64			val;
+	double			fval;
+	struct pg_tm 	tm;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
+	ClearPgTm(&tm);
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -3117,7 +3187,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 		{
 			case DTK_TIME:
 				dterr = DecodeTime(field[i], fmask, range,
-								   &tmask, tm, fsec);
+								   &tmask, &tm, fsec);
+				tm2itm(&tm, itm);
 				if (dterr)
 					return dterr;
 				type = DTK_DAY;
@@ -3138,14 +3209,19 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
 					DecodeTime(field[i] + 1, fmask, range,
-							   &tmask, tm, fsec) == 0)
+							   &tmask, &tm, fsec) == 0)
 				{
+					tm2itm(&tm, itm);
 					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;
+						if (itm->tm_hour == PG_INT64_MIN ||
+							itm->tm_min == PG_INT64_MIN ||
+							itm->tm_mon == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_hour = -itm->tm_hour;
+						itm->tm_min = -itm->tm_min;
+						itm->tm_sec = -itm->tm_sec;
 						*fsec = -(*fsec);
 					}
 
@@ -3164,7 +3240,6 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				 */
 
 				/* FALLTHROUGH */
-
 			case DTK_DATE:
 			case DTK_NUMBER:
 				if (type == IGNORE_DTF)
@@ -3204,7 +3279,7 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoint_64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -3253,14 +3328,16 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 					case DTK_MILLISEC:
 						/* avoid overflowing the fsec field */
-						tm->tm_sec += val / 1000;
+						if (!AdjustSeconds(val / 1000, itm))
+							return DTERR_FIELD_OVERFLOW;
 						val -= (val / 1000) * 1000;
 						*fsec += rint((val + fval) * 1000);
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
+						if (!AdjustSeconds(val, itm))
+							return DTERR_FIELD_OVERFLOW;
 						*fsec += rint(fval * 1000000);
 
 						/*
@@ -3274,57 +3351,86 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						itm->tm_min += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						itm->tm_hour += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR))
+							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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 1))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, 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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustMonths((int)val, itm))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, 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)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_year += val;
+						if (!AdjustFractMonths(fval, itm, 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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 10))
+							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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 100))
+							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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MILLENNIUM);
 						break;
 
@@ -3335,7 +3441,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], (int *)&val);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -3381,7 +3487,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 
 		sec = *fsec / USECS_PER_SEC;
 		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
+		if (!AdjustSeconds(sec, itm))
+			return DTERR_FIELD_OVERFLOW;
 	}
 
 	/*----------
@@ -3422,31 +3529,39 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 			 */
 			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->tm_sec > 0)
+				itm->tm_sec = -itm->tm_sec;
+			if (itm->tm_min > 0)
+				itm->tm_min = -itm->tm_min;
+			if (itm->tm_hour > 0)
+				itm->tm_hour = -itm->tm_hour;
+			if (itm->tm_mday > 0)
+				itm->tm_mday = -itm->tm_mday;
+			if (itm->tm_mon > 0)
+				itm->tm_mon = -itm->tm_mon;
+			if (itm->tm_year > 0)
+				itm->tm_year = -itm->tm_year;
 		}
 	}
 
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (itm->tm_sec == PG_INT64_MIN ||
+			itm->tm_min == PG_INT64_MIN ||
+			itm->tm_hour == PG_INT64_MIN ||
+			itm->tm_mday == INT_MIN ||
+			itm->tm_mon == INT_MIN ||
+			itm->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*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_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;
 	}
 
 	return 0;
@@ -3460,7 +3575,7 @@ 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;
 
@@ -3476,9 +3591,9 @@ ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
 		return DTERR_FIELD_OVERFLOW;
 	/* be very sure we truncate towards zero (cf dtrunc()) */
 	if (val >= 0)
-		*ipart = (int) floor(val);
+		*ipart = (int64) floor(val);
 	else
-		*ipart = (int) -floor(-val);
+		*ipart = (int64) -floor(-val);
 	*fpart = val - *ipart;
 	return 0;
 }
@@ -3516,13 +3631,13 @@ ISO8601IntegerWidth(char *fieldstart)
  */
 int
 DecodeISO8601Interval(char *str,
-					  int *dtype, struct pg_tm *tm, fsec_t *fsec)
+					  int *dtype, struct pg_itm *itm, fsec_t *fsec)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -3531,7 +3646,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -3560,29 +3675,35 @@ 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);
+					itm->tm_year += val;
+					if (!AdjustFractMonths(fval, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays(val, itm, 7))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						itm->tm_year += val / 10000;
+						itm->tm_mon += (val / 100) % 100;
+						itm->tm_mday += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -3596,8 +3717,8 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					itm->tm_year += val;
+					itm->tm_mon += rint(fval * MONTHS_PER_YEAR);
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -3610,8 +3731,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))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3627,8 +3749,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, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -3648,24 +3771,24 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						itm->tm_hour += val / 10000;
+						itm->tm_min += (val / 100) % 100;
+						itm->tm_sec += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, 1);
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -3675,16 +3798,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -3694,8 +3817,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -4166,22 +4289,22 @@ 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, "%ld%c", value, units);
 	return cp + strlen(cp);
 }
 
 /* Append a postgres-style interval field, but only if value isn't zero */
 static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+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%ld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
 			value,
@@ -4199,7 +4322,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 +4331,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, " %ld %s%s", value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
@@ -4238,15 +4361,15 @@ 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, fsec_t fsec, 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;
+	int64		min = itm->tm_min;
+	int64		sec = itm->tm_sec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -4306,28 +4429,28 @@ 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:",
-							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+					sprintf(cp, "%c%ld-%ld %c%ld %c%ld:%02ld:",
+							year_sign, Abs(year), Abs(mon),
+							day_sign, Abs(mday),
+							sec_sign, Abs(hour), Abs(min));
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
 				}
 				else if (has_year_month)
 				{
-					sprintf(cp, "%d-%d", year, mon);
+					sprintf(cp, "%ld-%ld", year, mon);
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%ld %ld:%02ld:", mday, 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, "%ld:%02ld:", hour, min);
 					cp += strlen(cp);
 					cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 					*cp = '\0';
@@ -4377,10 +4500,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%02ld:%02ld:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						Abs(hour), Abs(min));
 				cp += strlen(cp);
 				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 				*cp = '\0';
@@ -4412,7 +4535,7 @@ EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
 				cp = AppendSeconds(cp, sec, fsec, 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 || fsec != 0) ? "s" : "");
 				is_zero = false;
 			}
 			/* identically zero? then put in a unitless zero... */
@@ -4668,7 +4791,7 @@ pg_timezone_abbrevs(PG_FUNCTION_ARGS)
 	int			gmtoffset;
 	bool		is_dst;
 	unsigned char *p;
-	struct pg_tm tm;
+	struct pg_itm itm;
 	Interval   *resInterval;
 
 	/* stuff done only on the first call of the function */
@@ -4762,10 +4885,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, 0, sizeof(struct pg_itm));
+	itm.tm_sec = gmtoffset;
 	resInterval = (Interval *) palloc(sizeof(Interval));
-	tm2interval(&tm, 0, resInterval);
+	tm2interval(&itm, 0, resInterval);
 	values[1] = IntervalPGetDatum(resInterval);
 
 	values[2] = BoolGetDatum(is_dst);
@@ -4798,7 +4921,7 @@ pg_timezone_names(PG_FUNCTION_ARGS)
 	fsec_t		fsec;
 	const char *tzn;
 	Interval   *resInterval;
-	struct pg_tm itm;
+	struct pg_itm itm;
 	MemoryContext oldcontext;
 
 	/* check to see if caller supports us returning a tuplestore */
@@ -4857,7 +4980,7 @@ 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));
+		MemSet(&itm, 0, sizeof(struct pg_itm));
 		itm.tm_sec = -tzoff;
 		resInterval = (Interval *) palloc(sizeof(Interval));
 		tm2interval(&itm, 0, resInterval);
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index d4c2e7b069..d26aa57ec2 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -519,6 +519,13 @@ do { \
 	tmtcTzn(_X) = NULL; \
 } while(0)
 
+#define ZERO_itm(_X) \
+do {	\
+	(_X)->tm_sec  = (_X)->tm_year = (_X)->tm_min = \
+	(_X)->tm_hour = (_X)->tm_yday = 0; \
+	(_X)->tm_mday = (_X)->tm_mon  = 1; \
+} while(0)
+
 /*
  *	to_char(time) appears to to_char() as an interval, so this check
  *	is really for interval and time data types.
@@ -4156,18 +4163,31 @@ interval_to_char(PG_FUNCTION_ARGS)
 			   *res;
 	TmToChar	tmtc;
 	struct pg_tm *tm;
+	struct pg_itm itm;
 
 	if (VARSIZE_ANY_EXHDR(fmt) <= 0)
 		PG_RETURN_NULL();
 
 	ZERO_tmtc(&tmtc);
 	tm = tmtcTm(&tmtc);
+	ZERO_itm(&itm);
+
+	tm2itm(tm, &itm);
 
-	if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
+	if (interval2tm(*it, &itm, &tmtcFsec(&tmtc)) != 0)
 		PG_RETURN_NULL();
 
 	/* 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;
+	itm.tm_yday = (itm.tm_year * MONTHS_PER_YEAR + itm.tm_mon) * DAYS_PER_MONTH + itm.tm_mday;
+
+	// TODO this will not convert fields greater than ints correctly
+	tm->tm_sec = (int)itm.tm_sec;
+	tm->tm_min = (int)itm.tm_min;
+	tm->tm_hour = (int)itm.tm_hour;
+	tm->tm_mday = itm.tm_mday;
+	tm->tm_mon = itm.tm_mon;
+	tm->tm_year = itm.tm_year;
+	tm->tm_yday = itm.tm_yday;
 
 	if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION())))
 		PG_RETURN_NULL();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 36f8a84bcc..7e930dbd6f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -889,8 +889,8 @@ interval_in(PG_FUNCTION_ARGS)
 	int32		typmod = PG_GETARG_INT32(2);
 	Interval   *result;
 	fsec_t		fsec;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	int			dtype;
 	int			nf;
 	int			range;
@@ -899,12 +899,12 @@ 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;
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
 	fsec = 0;
 
 	if (typmod >= 0)
@@ -916,12 +916,12 @@ interval_in(PG_FUNCTION_ARGS)
 						  ftype, MAXDATEFIELDS, &nf);
 	if (dterr == 0)
 		dterr = DecodeInterval(field, ftype, nf, range,
-							   &dtype, tm, &fsec);
+							   &dtype, itm, &fsec);
 
 	/* 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, &fsec);
 
 	if (dterr != 0)
 	{
@@ -935,7 +935,7 @@ interval_in(PG_FUNCTION_ARGS)
 	switch (dtype)
 	{
 		case DTK_DELTA:
-			if (tm2interval(tm, fsec, result) != 0)
+			if (tm2interval(itm, fsec, result) != 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 						 errmsg("interval out of range")));
@@ -959,15 +959,15 @@ interval_out(PG_FUNCTION_ARGS)
 {
 	Interval   *span = PG_GETARG_INTERVAL_P(0);
 	char	   *result;
-	struct pg_tm tt,
-			   *tm = &tt;
+	struct pg_itm tt,
+			   *itm = &tt;
 	fsec_t		fsec;
 	char		buf[MAXDATELEN + 1];
 
-	if (interval2tm(*span, tm, &fsec) != 0)
+	if (interval2tm(*span, itm, &fsec) != 0)
 		elog(ERROR, "could not convert interval to tm");
 
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	EncodeInterval(itm, fsec, IntervalStyle, buf);
 
 	result = pstrdup(buf);
 	PG_RETURN_CSTRING(result);
@@ -1963,7 +1963,7 @@ 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)
+interval2tm(Interval span, struct pg_itm *tm, fsec_t *fsec)
 {
 	TimeOffset	time;
 	TimeOffset	tfrac;
@@ -1991,7 +1991,7 @@ interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
 }
 
 int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+tm2interval(struct pg_itm *tm, fsec_t fsec, Interval *span)
 {
 	double		total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
 
@@ -2006,6 +2006,18 @@ tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
 	return 0;
 }
 
+void
+tm2itm(struct pg_tm *tm, struct pg_itm *itm)
+{
+	itm->tm_sec = (int64)tm->tm_sec;
+	itm->tm_min = (int64)tm->tm_min;
+	itm->tm_hour = (int64)tm->tm_hour;
+	itm->tm_mday = tm->tm_mday;
+	itm->tm_mon = tm->tm_mon;
+	itm->tm_year = tm->tm_year;
+	itm->tm_yday = tm->tm_yday;
+}
+
 static TimeOffset
 time2t(const int hour, const int min, const int sec, const fsec_t fsec)
 {
@@ -3577,7 +3589,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 	fsec_t		fsec,
 				fsec1,
 				fsec2;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
@@ -3696,7 +3708,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 	fsec_t		fsec,
 				fsec1,
 				fsec2;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 	struct pg_tm tt1,
 			   *tm1 = &tt1;
@@ -4280,7 +4292,7 @@ interval_trunc(PG_FUNCTION_ARGS)
 				val;
 	char	   *lowunits;
 	fsec_t		fsec;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 
 	result = (Interval *) palloc(sizeof(Interval));
@@ -5163,7 +5175,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 				val;
 	char	   *lowunits;
 	fsec_t		fsec;
-	struct pg_tm tt,
+	struct pg_itm tt,
 			   *tm = &tt;
 
 	lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
diff --git a/src/common/string.c b/src/common/string.c
index 16940d1fa7..14d0267919 100644
--- a/src/common/string.c
+++ b/src/common/string.c
@@ -43,6 +43,21 @@ pg_str_endswith(const char *str, const char *end)
 }
 
 
+/*
+ * strtoint_64 --- just like strtol, but returns int64 not long
+ */
+int64
+strtoint_64(const char *pg_restrict str, char **pg_restrict endptr, int base)
+{
+	long		val;
+
+	val = strtol(str, endptr, base);
+	if (val != (int64) val)
+		errno = ERANGE;
+	return (int64) val;
+}
+
+
 /*
  * strtoint --- just like strtol, but returns int not long
  */
diff --git a/src/include/common/string.h b/src/include/common/string.h
index cf00fb53cd..ea4c99befc 100644
--- a/src/include/common/string.h
+++ b/src/include/common/string.h
@@ -24,6 +24,8 @@ typedef struct PromptInterruptContext
 extern bool pg_str_endswith(const char *str, const char *end);
 extern int	strtoint(const char *pg_restrict str, char **pg_restrict endptr,
 					 int base);
+extern int64	strtoint_64(const char *pg_restrict str, char **pg_restrict endptr,
+						   int base);
 extern void pg_clean_ascii(char *str);
 extern int	pg_strip_crlf(char *str);
 extern bool pg_is_ascii(const char *str);
diff --git a/src/include/pgtime.h b/src/include/pgtime.h
index 2977b13aab..e3f42359bd 100644
--- a/src/include/pgtime.h
+++ b/src/include/pgtime.h
@@ -44,6 +44,18 @@ struct pg_tm
 	const char *tm_zone;
 };
 
+// data structure to help encode and decode intervals
+struct pg_itm
+{
+	pg_time_t	tm_sec;
+	pg_time_t	tm_min;
+	pg_time_t	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/datetime.h b/src/include/utils/datetime.h
index 0d158f3e4b..0968e66106 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 *itm, fsec_t *fsec);
 extern int	DecodeISO8601Interval(char *str,
-								  int *dtype, struct pg_tm *tm, fsec_t *fsec);
+								  int *dtype, struct pg_itm *itm, fsec_t *fsec);
 
 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, fsec_t fsec, 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..17f76d40a8 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	interval2tm(Interval span, struct pg_itm *tm, fsec_t *fsec);
+extern int	tm2interval(struct pg_itm *tm, fsec_t fsec, Interval *span);
+extern void	tm2itm(struct pg_tm *tm, struct pg_itm *interval_tm);
 
 extern Timestamp SetEpochTimestamp(void);
 extern void GetEpochTime(struct pg_tm *tm);
diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h
index 893c9b6194..8b11d454f5 100644
--- a/src/interfaces/ecpg/pgtypeslib/dt.h
+++ b/src/interfaces/ecpg/pgtypeslib/dt.h
@@ -115,6 +115,11 @@ typedef int32 fsec_t;
 /* generic fields to help with parsing */
 #define ISODATE 22
 #define ISOTIME 23
+/* these are only for parsing intervals */
+#define WEEK		24
+#define DECADE		25
+#define CENTURY		26
+#define MILLENNIUM	27
 /* hack for parsing two-word timezone specs "MET DST" etc */
 #define DTZMOD	28				/* "DST" as a separate word */
 /* reserved for unrecognized string values */
@@ -311,10 +316,21 @@ do { \
 #define TIMESTAMP_IS_NOEND(j)	((j) == DT_NOEND)
 #define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
 
-int			DecodeInterval(char **, int *, int, int *, struct tm *, fsec_t *);
+struct itm
+{
+	int64		tm_sec;
+	int64		tm_min;
+	int64		tm_hour;
+	int			tm_mday;
+	int			tm_mon;
+	int			tm_year;
+	int			tm_yday;
+};
+
+int			DecodeInterval(char **, int *, int, int *, struct itm *, fsec_t *);
 int			DecodeTime(char *, int *, struct tm *, fsec_t *);
 void		EncodeDateTime(struct tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str, bool EuroDates);
-void		EncodeInterval(struct tm *tm, fsec_t fsec, int style, char *str);
+void		EncodeInterval(struct itm *tm, fsec_t fsec, int style, char *str);
 int			tm2timestamp(struct tm *, fsec_t, int *, timestamp *);
 int			DecodeUnits(int field, char *lowtoken, int *val);
 bool		CheckDateTokenTables(void);
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
index a7e530cb5d..a35f02c299 100644
--- a/src/interfaces/ecpg/pgtypeslib/interval.c
+++ b/src/interfaces/ecpg/pgtypeslib/interval.c
@@ -10,6 +10,7 @@
 #error -ffast-math is known to break this code
 #endif
 
+#include "common/int.h"
 #include "common/string.h"
 #include "dt.h"
 #include "pgtypes_error.h"
@@ -17,43 +18,94 @@
 #include "pgtypeslib_extern.h"
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
- * and changed struct pg_tm to struct tm
+ * and changed struct pg_itm to struct itm
  */
+
 static void
-AdjustFractSeconds(double frac, struct /* pg_ */ tm *tm, fsec_t *fsec, int scale)
+tm2itm(struct tm *tm, struct itm *itm)
+{
+	itm->tm_sec = (int64)tm->tm_sec;
+	itm->tm_min = (int64)tm->tm_min;
+	itm->tm_hour = (int64)tm->tm_hour;
+	itm->tm_mday = tm->tm_mday;
+	itm->tm_mon = tm->tm_mon;
+	itm->tm_year = tm->tm_year;
+	itm->tm_yday = tm->tm_yday;
+}
+
+static bool
+AdjustFractSeconds(double frac, struct /* pg_ */ itm *itm, fsec_t *fsec, int scale)
 {
 	int			sec;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	sec = (int) frac;
-	tm->tm_sec += sec;
+	sec = (int64) frac;
+	if (pg_add_s64_overflow(itm->tm_sec, sec, &itm->tm_sec))
+		return false;
 	frac -= sec;
 	*fsec += rint(frac * 1000000);
+	return true;
 }
 
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
  * and changed struct pg_tm to struct tm
  */
-static void
-AdjustFractDays(double frac, struct /* pg_ */ tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractDays(double frac, struct /* pg_ */ itm *itm, fsec_t *fsec, int scale)
 {
 	int			extra_days;
 
 	if (frac == 0)
-		return;
+		return true;
 	frac *= scale;
-	extra_days = (int) frac;
-	tm->tm_mday += extra_days;
+	extra_days = (int64) frac;
+	if (pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday))
+		return false;
 	frac -= extra_days;
-	AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+	return AdjustFractSeconds(frac, itm, fsec, SECS_PER_DAY);
+}
+
+static bool
+AdjustFractMonths(double frac, struct /* pg_ */ itm *itm, int scale)
+{
+	int months = rint(frac * MONTHS_PER_YEAR * scale);
+	return !pg_add_s32_overflow(itm->tm_mon, months, &itm->tm_mon);
+}
+
+static bool
+AdjustSeconds(int64 val, struct /* pg_ */ itm *itm)
+{
+	return !pg_add_s64_overflow(itm->tm_sec, val, &itm->tm_sec);
+}
+
+static bool
+AdjustDays(int val, struct /* pg_ */ itm *itm, int multiplier)
+{
+	int			extra_days;
+	return !pg_mul_s32_overflow(val, multiplier, &extra_days) &&
+		   !pg_add_s32_overflow(itm->tm_mday, extra_days, &itm->tm_mday);
+}
+
+static bool
+AdjustMonths(int val, struct /* pg_ */ itm *itm)
+{
+	return !pg_add_s32_overflow(itm->tm_mon, val, &itm->tm_mon);
+}
+
+static bool
+AdjustYears(int val, struct /* pg_ */ itm *itm, int multiplier)
+{
+	int			years;
+	return !pg_mul_s32_overflow(val, multiplier, &years) &&
+		   !pg_add_s32_overflow(itm->tm_year, years, &itm->tm_year);
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 static int
-ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
 {
 	double		val;
 
@@ -69,9 +121,9 @@ ParseISO8601Number(const char *str, char **endptr, int *ipart, double *fpart)
 		return DTERR_FIELD_OVERFLOW;
 	/* be very sure we truncate towards zero (cf dtrunc()) */
 	if (val >= 0)
-		*ipart = (int) floor(val);
+		*ipart = (int64) floor(val);
 	else
-		*ipart = (int) -floor(-val);
+		*ipart = (int64) -floor(-val);
 	*fpart = val - *ipart;
 	return 0;
 }
@@ -88,10 +140,22 @@ ISO8601IntegerWidth(const char *fieldstart)
 
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
- * and changed struct pg_tm to struct tm
+ * and changed struct pg_itm to struct itm
  */
 static inline void
-ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
+ClearPgItm(struct /* pg_ */ itm *itm, fsec_t *fsec)
+{
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
+	*fsec = 0;
+}
+
+static inline void
+ClearPgTm(struct /* pg_ */ tm *tm)
 {
 	tm->tm_year = 0;
 	tm->tm_mon = 0;
@@ -99,7 +163,6 @@ ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
 	tm->tm_hour = 0;
 	tm->tm_min = 0;
 	tm->tm_sec = 0;
-	*fsec = 0;
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c
@@ -110,13 +173,13 @@ ClearPgTm(struct /* pg_ */ tm *tm, fsec_t *fsec)
  */
 static int
 DecodeISO8601Interval(char *str,
-					  int *dtype, struct /* pg_ */ tm *tm, fsec_t *fsec)
+					  int *dtype, struct /* pg_ */ itm *itm, fsec_t *fsec)
 {
 	bool		datepart = true;
 	bool		havefield = false;
 
 	*dtype = DTK_DELTA;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
 
 	if (strlen(str) < 2 || str[0] != 'P')
 		return DTERR_BAD_FORMAT;
@@ -125,7 +188,7 @@ DecodeISO8601Interval(char *str,
 	while (*str)
 	{
 		char	   *fieldstart;
-		int			val;
+		int64		val;
 		double		fval;
 		char		unit;
 		int			dterr;
@@ -154,29 +217,35 @@ 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);
+					itm->tm_year += val;
+					if (!AdjustFractMonths(fval, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'M':
-					tm->tm_mon += val;
-					AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+					if (!AdjustMonths(val, itm))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'W':
-					tm->tm_mday += val * 7;
-					AdjustFractDays(fval, tm, fsec, 7);
+					if (!AdjustDays(val, itm, 7))
+						return DTERR_FIELD_OVERFLOW;
+					if (!AdjustFractDays(fval, itm, fsec, 7))
+						return DTERR_FIELD_OVERFLOW;
 					break;
 				case 'D':
-					tm->tm_mday += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+					if (!AdjustDays(val, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					break;
 				case 'T':		/* ISO 8601 4.4.3.3 Alternative Format / Basic */
 				case '\0':
 					if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
 					{
-						tm->tm_year += val / 10000;
-						tm->tm_mon += (val / 100) % 100;
-						tm->tm_mday += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+						itm->tm_year += val / 10000;
+						itm->tm_mon += (val / 100) % 100;
+						itm->tm_mday += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 						if (unit == '\0')
 							return 0;
 						datepart = false;
@@ -190,8 +259,8 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_year += val;
-					tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+					itm->tm_year += val;
+					itm->tm_mon += rint(fval * MONTHS_PER_YEAR);
 					if (unit == '\0')
 						return 0;
 					if (unit == 'T')
@@ -204,8 +273,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))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractDays(fval, itm, fsec, DAYS_PER_MONTH);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -221,8 +291,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, itm, 1))
+						return DTERR_FIELD_OVERFLOW;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY);
 					if (*str == '\0')
 						return 0;
 					if (*str == 'T')
@@ -242,24 +313,24 @@ DecodeISO8601Interval(char *str,
 			switch (unit)		/* after T: H M S */
 			{
 				case 'H':
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					break;
 				case 'M':
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					break;
 				case 'S':
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					break;
 				case '\0':		/* ISO 8601 4.4.3.3 Alternative Format */
 					if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
 					{
-						tm->tm_hour += val / 10000;
-						tm->tm_min += (val / 100) % 100;
-						tm->tm_sec += val % 100;
-						AdjustFractSeconds(fval, tm, fsec, 1);
+						itm->tm_hour += val / 10000;
+						itm->tm_min += (val / 100) % 100;
+						itm->tm_sec += val % 100;
+						AdjustFractSeconds(fval, itm, fsec, 1);
 						return 0;
 					}
 					/* Else fall through to extended alternative format */
@@ -269,16 +340,16 @@ DecodeISO8601Interval(char *str,
 					if (havefield)
 						return DTERR_BAD_FORMAT;
 
-					tm->tm_hour += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+					itm->tm_hour += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR);
 					if (unit == '\0')
 						return 0;
 
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_min += val;
-					AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+					itm->tm_min += val;
+					AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE);
 					if (*str == '\0')
 						return 0;
 					if (*str != ':')
@@ -288,8 +359,8 @@ DecodeISO8601Interval(char *str,
 					dterr = ParseISO8601Number(str, &str, &val, &fval);
 					if (dterr)
 						return dterr;
-					tm->tm_sec += val;
-					AdjustFractSeconds(fval, tm, fsec, 1);
+					itm->tm_sec += val;
+					AdjustFractSeconds(fval, itm, fsec, 1);
 					if (*str == '\0')
 						return 0;
 					return DTERR_BAD_FORMAT;
@@ -324,23 +395,25 @@ DecodeISO8601Interval(char *str,
  */
 int
 DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
-			   int *dtype, struct /* pg_ */ tm *tm, fsec_t *fsec)
+			   int *dtype, struct /* pg_ */ itm *itm, fsec_t *fsec)
 {
 	int			IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
 	int			range = INTERVAL_FULL_RANGE;
-	bool		is_before = false;
-	char	   *cp;
-	int			fmask = 0,
-				tmask,
-				type;
-	int			i;
-	int			dterr;
-	int			val;
-	double		fval;
+	bool			is_before = false;
+	char	   		*cp;
+	int				fmask = 0,
+					tmask,
+					type;
+	int				i;
+	int				dterr;
+	int64			val;
+	double			fval;
+	struct tm 	tm;
 
 	*dtype = DTK_DELTA;
 	type = IGNORE_DTF;
-	ClearPgTm(tm, fsec);
+	ClearPgItm(itm, fsec);
+	ClearPgTm(&tm);
 
 	/* read through list backwards to pick up units before values */
 	for (i = nf - 1; i >= 0; i--)
@@ -348,8 +421,9 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 		switch (ftype[i])
 		{
 			case DTK_TIME:
-				dterr = DecodeTime(field[i],	/* range, */
-								   &tmask, tm, fsec);
+				dterr = DecodeTime(field[i],	/* fmask, range, */
+								   &tmask, &tm, fsec);
+				tm2itm(&tm, itm);
 				if (dterr)
 					return dterr;
 				type = DTK_DAY;
@@ -358,27 +432,29 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 			case DTK_TZ:
 
 				/*
-				 * Timezone is a token with a leading sign character and at
+				 * Timezone means a token with a leading sign character and at
 				 * least one digit; there could be ':', '.', '-' embedded in
 				 * it as well.
 				 */
 				Assert(*field[i] == '-' || *field[i] == '+');
 
 				/*
-				 * Try for hh:mm or hh:mm:ss.  If not, fall through to
-				 * DTK_NUMBER case, which can handle signed float numbers and
-				 * signed year-month values.
+				 * Check for signed hh:mm or hh:mm:ss.  If so, process exactly
+				 * like DTK_TIME case above, plus handling the sign.
 				 */
 				if (strchr(field[i] + 1, ':') != NULL &&
-					DecodeTime(field[i] + 1,	/* INTERVAL_FULL_RANGE, */
-							   &tmask, tm, fsec) == 0)
-				{
-					if (*field[i] == '-')
-					{
+					DecodeTime(field[i] + 1,	/* fmask, range, */
+							   &tmask, &tm, fsec) == 0) {
+					tm2itm(&tm, itm);
+					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;
+						if (itm->tm_hour == PG_INT64_MIN ||
+							itm->tm_min == PG_INT64_MIN ||
+							itm->tm_mon == PG_INT64_MIN)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_hour = -itm->tm_hour;
+						itm->tm_min = -itm->tm_min;
+						itm->tm_sec = -itm->tm_sec;
 						*fsec = -(*fsec);
 					}
 
@@ -388,11 +464,15 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 					 * are reading right to left.
 					 */
 					type = DTK_DAY;
-					tmask = DTK_M(TZ);
 					break;
 				}
-				/* FALL THROUGH */
 
+				/*
+				 * Otherwise, fall through to DTK_NUMBER case, which can
+				 * handle signed float numbers and signed year-month values.
+				 */
+
+				/* FALLTHROUGH */
 			case DTK_DATE:
 			case DTK_NUMBER:
 				if (type == IGNORE_DTF)
@@ -412,17 +492,17 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 							break;
 						case INTERVAL_MASK(HOUR):
 						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
-						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
-						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 							type = DTK_HOUR;
 							break;
 						case INTERVAL_MASK(MINUTE):
 						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
+						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
 							type = DTK_MINUTE;
 							break;
 						case INTERVAL_MASK(SECOND):
-						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 						case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
+						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
+						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
 							type = DTK_SECOND;
 							break;
 						default:
@@ -432,7 +512,7 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 				}
 
 				errno = 0;
-				val = strtoint(field[i], &cp, 10);
+				val = strtoint_64(field[i], &cp, 10);
 				if (errno == ERANGE)
 					return DTERR_FIELD_OVERFLOW;
 
@@ -449,6 +529,9 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 					type = DTK_MONTH;
 					if (*field[i] == '-')
 						val2 = -val2;
+					if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
+						((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+						return DTERR_FIELD_OVERFLOW;
 					val = val * MONTHS_PER_YEAR + val2;
 					fval = 0;
 				}
@@ -477,12 +560,17 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 						break;
 
 					case DTK_MILLISEC:
+						/* avoid overflowing the fsec field */
+						if (!AdjustSeconds(val / 1000, itm))
+							return DTERR_FIELD_OVERFLOW;
+						val -= (val / 1000) * 1000;
 						*fsec += rint((val + fval) * 1000);
 						tmask = DTK_M(MILLISECOND);
 						break;
 
 					case DTK_SECOND:
-						tm->tm_sec += val;
+						if (!AdjustSeconds(val, itm))
+							return DTERR_FIELD_OVERFLOW;
 						*fsec += rint(fval * 1000000);
 
 						/*
@@ -496,58 +584,87 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 						break;
 
 					case DTK_MINUTE:
-						tm->tm_min += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+						itm->tm_min += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_MINUTE))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(MINUTE);
 						break;
 
 					case DTK_HOUR:
-						tm->tm_hour += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+						itm->tm_hour += val;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_HOUR))
+							return DTERR_FIELD_OVERFLOW;
 						tmask = DTK_M(HOUR);
-						type = DTK_DAY;
+						type = DTK_DAY; /* set for next field */
 						break;
 
 					case DTK_DAY:
-						tm->tm_mday += val;
-						AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
-						tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 1))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractSeconds(fval, itm, fsec, SECS_PER_DAY))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(DAY);
 						break;
 
 					case DTK_WEEK:
-						tm->tm_mday += val * 7;
-						AdjustFractDays(fval, tm, fsec, 7);
-						tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustDays((int)val, itm, 7))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, 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)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustMonths((int)val, itm))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractDays(fval, itm, fsec, 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);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						itm->tm_year += val;
+						if (!AdjustFractMonths(fval, itm, 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);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 10))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 10))
+							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);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 100))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 100))
+							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);
-						tmask = (fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR);
+						if (val < INT_MIN || val > INT_MAX)
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustYears((int)val, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						if (!AdjustFractMonths(fval, itm, 1000))
+							return DTERR_FIELD_OVERFLOW;
+						tmask = DTK_M(MILLENNIUM);
 						break;
 
 					default:
@@ -557,7 +674,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], (int *)&val);
 				if (type == IGNORE_DTF)
 					continue;
 
@@ -603,7 +720,8 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 
 		sec = *fsec / USECS_PER_SEC;
 		*fsec -= sec * USECS_PER_SEC;
-		tm->tm_sec += sec;
+		if (!AdjustSeconds(sec, itm))
+			return DTERR_FIELD_OVERFLOW;
 	}
 
 	/*----------
@@ -644,31 +762,39 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 			 */
 			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->tm_sec > 0)
+				itm->tm_sec = -itm->tm_sec;
+			if (itm->tm_min > 0)
+				itm->tm_min = -itm->tm_min;
+			if (itm->tm_hour > 0)
+				itm->tm_hour = -itm->tm_hour;
+			if (itm->tm_mday > 0)
+				itm->tm_mday = -itm->tm_mday;
+			if (itm->tm_mon > 0)
+				itm->tm_mon = -itm->tm_mon;
+			if (itm->tm_year > 0)
+				itm->tm_year = -itm->tm_year;
 		}
 	}
 
 	/* finally, AGO negates everything */
 	if (is_before)
 	{
+		if (itm->tm_sec == PG_INT64_MIN ||
+			itm->tm_min == PG_INT64_MIN ||
+			itm->tm_hour == PG_INT64_MIN ||
+			itm->tm_mday == INT_MIN ||
+			itm->tm_mon == INT_MIN ||
+			itm->tm_year == INT_MIN)
+			return DTERR_FIELD_OVERFLOW;
+
 		*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_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;
 	}
 
 	return 0;
@@ -677,7 +803,7 @@ DecodeInterval(char **field, int *ftype, int nf,	/* int range, */
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 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)
@@ -686,23 +812,23 @@ 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, " %ld %s%s", value, units, (value == 1) ? "" : "s");
 	*is_zero = false;
 	return cp + strlen(cp);
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 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%ld %s%s",
 			(!*is_zero) ? " " : "",
 			(*is_before && value > 0) ? "+" : "",
 			value,
@@ -720,33 +846,69 @@ AddPostgresIntPart(char *cp, int value, const char *units,
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
 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, "%ld%c", value, units);
 	return cp + strlen(cp);
 }
 
 /* copy&pasted from .../src/backend/utils/adt/datetime.c */
-static void
-AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
+static char *
+AppendSeconds(char *cp, int64 sec, fsec_t fsec, int precision, bool fillzeros)
 {
-	if (fsec == 0)
-	{
-		if (fillzeros)
-			sprintf(cp, "%02d", abs(sec));
-		else
-			sprintf(cp, "%d", abs(sec));
-	}
+	Assert(precision >= 0);
+
+	if (fillzeros)
+		cp = pg_ultostr_zeropad(cp, Abs(sec), 2);
 	else
+		cp = pg_ultostr(cp, Abs(sec));
+
+	/* fsec_t is just an int32 */
+	if (fsec != 0)
 	{
-		if (fillzeros)
-			sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
-		else
-			sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
-		TrimTrailingZeros(cp);
+		int32		value = Abs(fsec);
+		char	   *end = &cp[precision + 1];
+		bool		gotnonzero = false;
+
+		*cp++ = '.';
+
+		/*
+		 * Append the fractional seconds part.  Note that we don't want any
+		 * trailing zeros here, so since we're building the number in reverse
+		 * we'll skip appending zeros until we've output a non-zero digit.
+		 */
+		while (precision--)
+		{
+			int32		oldval = value;
+			int32		remainder;
+
+			value /= 10;
+			remainder = oldval - value * 10;
+
+			/* check if we got a non-zero */
+			if (remainder)
+				gotnonzero = true;
+
+			if (gotnonzero)
+				cp[precision] = '0' + remainder;
+			else
+				end = &cp[precision];
+		}
+
+		/*
+		 * If we still have a non-zero value then precision must have not been
+		 * enough to print the number.  We punt the problem to pg_ltostr(),
+		 * which will generate a correct answer in the minimum valid width.
+		 */
+		if (value)
+			return pg_ultostr(cp, Abs(fsec));
+
+		return end;
 	}
+	else
+		return cp;
 }
 
 
@@ -756,15 +918,15 @@ AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
  */
 
 void
-EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct /* pg_ */ itm *itm, fsec_t fsec, 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;
+	int64		min = itm->tm_min;
+	int64		sec = itm->tm_sec;
 	bool		is_before = false;
 	bool		is_zero = true;
 
@@ -824,28 +986,30 @@ 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:",
-							year_sign, abs(year), abs(mon),
-							day_sign, abs(mday),
-							sec_sign, abs(hour), abs(min));
+					sprintf(cp, "%c%ld-%ld %c%ld %c%ld:%02ld:",
+							year_sign, Abs(year), Abs(mon),
+							day_sign, Abs(mday),
+							sec_sign, Abs(hour), Abs(min));
 					cp += strlen(cp);
 					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
 				}
 				else if (has_year_month)
 				{
-					sprintf(cp, "%d-%d", year, mon);
+					sprintf(cp, "%ld-%ld", year, mon);
 				}
 				else if (has_day)
 				{
-					sprintf(cp, "%d %d:%02d:", mday, hour, min);
+					sprintf(cp, "%ld %ld:%02ld:", mday, hour, min);
 					cp += strlen(cp);
 					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					*cp = '\0';
 				}
 				else
 				{
-					sprintf(cp, "%d:%02d:", hour, min);
+					sprintf(cp, "%ld:%02ld:", hour, min);
 					cp += strlen(cp);
 					AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+					*cp = '\0';
 				}
 			}
 			break;
@@ -881,18 +1045,25 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
 			/* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
 		case INTSTYLE_POSTGRES:
 			cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
+
+			/*
+			 * Ideally we should spell out "month" like we do for "year" and
+			 * "day".  However, for backward compatibility, we can't easily
+			 * fix this.  bjm 2011-05-24
+			 */
 			cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
 			cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
 			if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
 			{
 				bool		minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
 
-				sprintf(cp, "%s%s%02d:%02d:",
+				sprintf(cp, "%s%s%02ld:%02ld:",
 						is_zero ? "" : " ",
 						(minus ? "-" : (is_before ? "+" : "")),
-						abs(hour), abs(min));
+						Abs(hour), Abs(min));
 				cp += strlen(cp);
 				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
+				*cp = '\0';
 			}
 			break;
 
@@ -918,11 +1089,10 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
 				}
 				else if (is_before)
 					*cp++ = '-';
-				AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
-				cp += strlen(cp);
+				cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
 				/* We output "ago", not negatives, so use abs(). */
 				sprintf(cp, " sec%s",
-						(abs(sec) != 1 || fsec != 0) ? "s" : "");
+						(Abs(sec) != 1 || fsec != 0) ? "s" : "");
 				is_zero = false;
 			}
 			/* identically zero? then put in a unitless zero... */
@@ -939,47 +1109,47 @@ EncodeInterval(struct /* pg_ */ tm *tm, fsec_t fsec, int style, char *str)
  * Convert an interval data type to a tm structure.
  */
 static int
-interval2tm(interval span, struct tm *tm, fsec_t *fsec)
+interval2tm(interval span, struct itm *itm, fsec_t *fsec)
 {
 	int64		time;
 
 	if (span.month != 0)
 	{
-		tm->tm_year = span.month / MONTHS_PER_YEAR;
-		tm->tm_mon = span.month % MONTHS_PER_YEAR;
+		itm->tm_year = span.month / MONTHS_PER_YEAR;
+		itm->tm_mon = span.month % MONTHS_PER_YEAR;
 
 	}
 	else
 	{
-		tm->tm_year = 0;
-		tm->tm_mon = 0;
+		itm->tm_year = 0;
+		itm->tm_mon = 0;
 	}
 
 	time = span.time;
 
-	tm->tm_mday = time / USECS_PER_DAY;
-	time -= tm->tm_mday * USECS_PER_DAY;
-	tm->tm_hour = time / USECS_PER_HOUR;
-	time -= tm->tm_hour * USECS_PER_HOUR;
-	tm->tm_min = time / USECS_PER_MINUTE;
-	time -= tm->tm_min * USECS_PER_MINUTE;
-	tm->tm_sec = time / USECS_PER_SEC;
-	*fsec = time - (tm->tm_sec * USECS_PER_SEC);
+	itm->tm_mday = time / USECS_PER_DAY;
+	time -= itm->tm_mday * USECS_PER_DAY;
+	itm->tm_hour = time / USECS_PER_HOUR;
+	time -= itm->tm_hour * USECS_PER_HOUR;
+	itm->tm_min = time / USECS_PER_MINUTE;
+	time -= itm->tm_min * USECS_PER_MINUTE;
+	itm->tm_sec = time / USECS_PER_SEC;
+	*fsec = time - (itm->tm_sec * USECS_PER_SEC);
 
 	return 0;
 }								/* interval2tm() */
 
 static int
-tm2interval(struct tm *tm, fsec_t fsec, interval * span)
+tm2interval(struct itm *itm, fsec_t fsec, interval * span)
 {
-	if ((double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX ||
-		(double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon < INT_MIN)
+	if ((double) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon > INT_MAX ||
+		(double) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon < INT_MIN)
 		return -1;
-	span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
-	span->time = (((((((tm->tm_mday * INT64CONST(24)) +
-					   tm->tm_hour) * INT64CONST(60)) +
-					 tm->tm_min) * INT64CONST(60)) +
-				   tm->tm_sec) * USECS_PER_SEC) + fsec;
+	span->month = itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+	span->time = (((((((itm->tm_mday * INT64CONST(24)) +
+					   itm->tm_hour) * INT64CONST(60)) +
+					 itm->tm_min) * INT64CONST(60)) +
+				   itm->tm_sec) * USECS_PER_SEC) + fsec;
 
 	return 0;
 }								/* tm2interval() */
@@ -1005,8 +1175,8 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 {
 	interval   *result = NULL;
 	fsec_t		fsec;
-	struct tm	tt,
-			   *tm = &tt;
+	struct itm	tt,
+			   *itm = &tt;
 	int			dtype;
 	int			nf;
 	char	   *field[MAXDATEFIELDS];
@@ -1015,12 +1185,12 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 	char	   *realptr;
 	char	  **ptr = (endptr != NULL) ? endptr : &realptr;
 
-	tm->tm_year = 0;
-	tm->tm_mon = 0;
-	tm->tm_mday = 0;
-	tm->tm_hour = 0;
-	tm->tm_min = 0;
-	tm->tm_sec = 0;
+	itm->tm_year = 0;
+	itm->tm_mon = 0;
+	itm->tm_mday = 0;
+	itm->tm_hour = 0;
+	itm->tm_min = 0;
+	itm->tm_sec = 0;
 	fsec = 0;
 
 	if (strlen(str) > MAXDATELEN)
@@ -1030,8 +1200,8 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 	}
 
 	if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
-		(DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0 &&
-		 DecodeISO8601Interval(str, &dtype, tm, &fsec) != 0))
+		(DecodeInterval(field, ftype, nf, &dtype, itm, &fsec) != 0 &&
+		 DecodeISO8601Interval(str, &dtype, itm, &fsec) != 0))
 	{
 		errno = PGTYPES_INTVL_BAD_INTERVAL;
 		return NULL;
@@ -1048,7 +1218,7 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 		return NULL;
 	}
 
-	if (tm2interval(tm, fsec, result) != 0)
+	if (tm2interval(itm, fsec, result) != 0)
 	{
 		errno = PGTYPES_INTVL_BAD_INTERVAL;
 		free(result);
@@ -1062,19 +1232,19 @@ PGTYPESinterval_from_asc(char *str, char **endptr)
 char *
 PGTYPESinterval_to_asc(interval * span)
 {
-	struct tm	tt,
-			   *tm = &tt;
+	struct itm	tt,
+			   *itm = &tt;
 	fsec_t		fsec;
 	char		buf[MAXDATELEN + 1];
 	int			IntervalStyle = INTSTYLE_POSTGRES_VERBOSE;
 
-	if (interval2tm(*span, tm, &fsec) != 0)
+	if (interval2tm(*span, itm, &fsec) != 0)
 	{
 		errno = PGTYPES_INTVL_BAD_INTERVAL;
 		return NULL;
 	}
 
-	EncodeInterval(tm, fsec, IntervalStyle, buf);
+	EncodeInterval(itm, fsec, IntervalStyle, buf);
 
 	return pgtypes_strdup(buf);
 }
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index accd4a7d90..3ff78a0724 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -232,6 +232,115 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
 ERROR:  interval out of range
 LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
                                                  ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+ERROR:  interval field value out of range: "2147483647 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+ERROR:  interval field value out of range: "-2147483648 weeks"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks'...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+ERROR:  interval field value out of range: "2147483647 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+ERROR:  interval field value out of range: "-2147483648 decades"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decade...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+ERROR:  interval field value out of range: "2147483647 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuri...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+ERROR:  interval field value out of range: "-2147483648 centuries"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centur...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+ERROR:  interval field value out of range: "2147483647 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millenn...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+ERROR:  interval field value out of range: "-2147483648 millennium"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millen...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+ERROR:  interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 214...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+ERROR:  interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+ERROR:  interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 214748...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+ERROR:  interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months 0.1 yrs');
+ERROR:  interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+ERROR:  interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 days');
+ERROR:  interval field value out of range: "1 week 2147483647 days"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 week');
+ERROR:  interval field value out of range: "2147483647 days 1 week"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 ...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+ERROR:  interval field value out of range: "-2147483648 months ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+ERROR:  interval field value out of range: "-2147483648 days ago"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days a...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D');
+ERROR:  interval field value out of range: "P0.1M2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M');
+ERROR:  interval field value out of range: "P0.1Y2147483647M"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y');
+ERROR:  interval field value out of range: "P2147483647M0.1Y"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+ERROR:  interval field value out of range: "P1W2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+ERROR:  interval field value out of range: "P2147483647D1W"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D');
+ERROR:  interval field value out of range: "P0.5W2147483647D"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D')...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483647T00:00:00');
+ERROR:  interval field value out of range: "P0000-00.1-2147483647T00:00:00"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483...
+                                                 ^
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647-00T00:00:00');
+ERROR:  interval field value out of range: "P0000.1-2147483647-00T00:00:00"
+LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647...
+                                                 ^
+-- TODO: Tests to add:
+--      overflow time parts
+--      overflow time parts with fractional day/week parts
+--      overflow time parts with ago
+--      printing INT_MIN/INT64_MIN fields
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
 ERROR:  interval out of range
@@ -1043,3 +1152,41 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)
 
+-- int64 time fields
+-- TODO add more tests
+SELECT INTERVAL '214748364 hours' * 11;
+      ?column?      
+--------------------
+ @ 2362232004 hours
+(1 row)
+
+-- test that INT_MIN number of hours is formatted properly
+-- TODO add more tests
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                          interval                           
+-------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2147483648:00:00
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                           interval                            
+---------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2147483648 hours ago
+(1 row)
+
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                  interval                  
+--------------------------------------------
+ -178956970-8 -2147483648 -2147483648:00:00
+(1 row)
+
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+                 interval                 
+------------------------------------------
+ P-178956970Y-8M-2147483648DT-2147483648H
+(1 row)
+
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 6d532398bd..1a637c7e2c 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -72,6 +72,37 @@ INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 years');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 weeks');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 decades');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 centuries');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 millennium');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 millennium 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 centuries 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 decades 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 yrs 2147483647 months');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 months 0.1 yrs');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('0.1 months 2147483647 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('1 week 2147483647 days');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483647 days 1 week');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 months ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 days ago');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1M2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.1Y2147483647M');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647M0.1Y');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P1W2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P2147483647D1W');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0.5W2147483647D');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000-00.1-2147483647T00:00:00');
+INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('P0000.1-2147483647-00T00:00:00');
+-- TODO: Tests to add:
+--      overflow time parts
+--      overflow time parts with fractional day/week parts
+--      overflow time parts with ago
+--      printing INT_MIN/INT64_MIN fields
 
 -- Test edge-case overflow detection in interval multiplication
 select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
@@ -355,3 +386,18 @@ SELECT f1,
 
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
+
+-- int64 time fields
+-- TODO add more tests
+SELECT INTERVAL '214748364 hours' * 11;
+
+-- test that INT_MIN number of hours is formatted properly
+-- TODO add more tests
+SET IntervalStyle to postgres;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+SET IntervalStyle to postgres_verbose;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+SET IntervalStyle TO sql_standard;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
+SET IntervalStyle to iso_8601;
+SELECT INTERVAL '-2147483648 months -2147483648 days -2147483648 hrs';
-- 
2.25.1

Reply via email to