From 784c97837355bedcf84f2aed9d2c608773a81d3a Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 28 Sep 2023 14:54:02 +0100
Subject: [PATCH v27 7/8] Code review of infinite interval support.

---
 doc/src/sgml/func.sgml                    |   5 +-
 src/backend/utils/adt/date.c              |  27 +-
 src/backend/utils/adt/datetime.c          |   4 +-
 src/backend/utils/adt/selfuncs.c          |  16 +-
 src/backend/utils/adt/timestamp.c         | 366 +++++++++----------
 src/include/catalog/pg_aggregate.dat      |  11 +-
 src/include/catalog/pg_proc.dat           |   6 +-
 src/include/datatype/timestamp.h          |  24 +-
 src/include/utils/timestamp.h             |   3 -
 src/test/regress/expected/interval.out    | 375 ++++++++------------
 src/test/regress/expected/timestamp.out   |   8 +-
 src/test/regress/expected/timestamptz.out |   8 +-
 src/test/regress/expected/window.out      | 406 +++++++++++++++++++---
 src/test/regress/sql/interval.sql         | 131 +++----
 src/test/regress/sql/window.sql           |  93 ++++-
 15 files changed, 912 insertions(+), 571 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c4f09df544..682e3713c6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10435,7 +10435,10 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
      +/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
      <literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
      <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
-     for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
+     for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+     <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+     <literal>century</literal>, and <literal>millennium</literal> for
+     <type>interval</type> inputs).
      For other fields, NULL is returned.  <productname>PostgreSQL</productname>
      versions before 9.6 returned zero for all cases of infinite input.
     </para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ad97f07601..cafe3abe21 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
 #include "access/xact.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
+#include "common/int.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "nodes/supportnodes.h"
@@ -2050,8 +2051,6 @@ time_mi_time(PG_FUNCTION_ARGS)
 	result->day = 0;
 	result->time = time1 - time2;
 
-	Assert(!INTERVAL_NOT_FINITE(result));
-
 	PG_RETURN_INTERVAL_P(result);
 }
 
@@ -2116,7 +2115,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
 
 	/*
 	 * Like time_pl_interval/time_mi_interval, we disregard the month and day
-	 * fields of the offset.  So our test for negative should too.
+	 * fields of the offset.  So our test for negative should too.  This also
+	 * catches -infinity, so we only need worry about +infinity below.
 	 */
 	if (offset->time < 0)
 		ereport(ERROR,
@@ -2126,13 +2126,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
 	/*
 	 * We can't use time_pl_interval/time_mi_interval here, because their
 	 * wraparound behavior would give wrong (or at least undesirable) answers.
-	 * Fortunately the equivalent non-wrapping behavior is trivial, especially
-	 * since we don't worry about integer overflow.
+	 * Fortunately the equivalent non-wrapping behavior is trivial, except
+	 * that adding an infinite (or very large) interval might cause integer
+	 * overflow.  Subtraction cannot overflow here.
 	 */
 	if (sub)
 		sum = base - offset->time;
-	else
-		sum = base + offset->time;
+	else if (pg_add_s64_overflow(base, offset->time, &sum))
+		PG_RETURN_BOOL(less);
 
 	if (less)
 		PG_RETURN_BOOL(val <= sum);
@@ -2666,7 +2667,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
 
 	/*
 	 * Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
-	 * day fields of the offset.  So our test for negative should too.
+	 * day fields of the offset.  So our test for negative should too. This
+	 * also catches -infinity, so we only need worry about +infinity below.
 	 */
 	if (offset->time < 0)
 		ereport(ERROR,
@@ -2676,13 +2678,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
 	/*
 	 * We can't use timetz_pl_interval/timetz_mi_interval here, because their
 	 * wraparound behavior would give wrong (or at least undesirable) answers.
-	 * Fortunately the equivalent non-wrapping behavior is trivial, especially
-	 * since we don't worry about integer overflow.
+	 * Fortunately the equivalent non-wrapping behavior is trivial, except
+	 * that adding an infinite (or very large) interval might cause integer
+	 * overflow.  Subtraction cannot overflow here.
 	 */
 	if (sub)
 		sum.time = base->time - offset->time;
-	else
-		sum.time = base->time + offset->time;
+	else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+		PG_RETURN_BOOL(less);
 	sum.zone = base->zone;
 
 	if (less)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 73dadb747c..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3613,9 +3613,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
 							return DTERR_BAD_FORMAT;
 
 						/*
-						 * infinity can not be followed by anything else. We
+						 * Infinity cannot be followed by anything else. We
 						 * could allow "ago" to reverse the sign of infinity
-						 * but that signed infinity is more intuitive.
+						 * but using signed infinity is more intuitive.
 						 */
 						if (i != nf - 1)
 							return DTERR_BAD_FORMAT;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 6e87173846..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,7 +4795,21 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
 		case DATEOID:
 			return date2timestamp_no_overflow(DatumGetDateADT(value));
 		case INTERVALOID:
-			return interval2timestamp_no_overflow(DatumGetIntervalP(value));
+			{
+				Interval   *interval = DatumGetIntervalP(value);
+
+				/*
+				 * Convert the month part of Interval to days using assumed
+				 * average month length of 365.25/12.0 days.  Not too
+				 * accurate, but plenty good enough for our purposes.
+				 *
+				 * This also works for infinite intervals, which just have all
+				 * fields set to INT_MIN/INT_MAX, and so will produce a result
+				 * smaller/larger than any finite interval.
+				 */
+				return interval->time + interval->day * (double) USECS_PER_DAY +
+					interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+			}
 		case TIMEOID:
 			return DatumGetTimeADT(value);
 		case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0b385fd72c..23d43094e8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,7 +18,6 @@
 #include <ctype.h>
 #include <math.h>
 #include <limits.h>
-#include <float.h>
 #include <sys/time.h>
 
 #include "access/xact.h"
@@ -74,16 +73,16 @@ typedef struct
 } generate_series_timestamptz_fctx;
 
 /*
- * The transition datatype for interval aggregates is declared as Internal. It's
- * a pointer to a IntervalAggState allocated in the aggregate context.
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
  */
 typedef struct IntervalAggState
 {
-	int64		N;				/* count of processed intervals */
-	Interval	sumX;			/* sum of processed intervals */
+	int64		N;				/* count of finite intervals processed */
+	Interval	sumX;			/* sum of finite intervals processed */
 	/* These counts are *not* included in N!  Use IA_TOTAL_COUNT() as needed */
-	int64		pInfcount;		/* count of +Inf values */
-	int64		nInfcount;		/* count of -Inf values */
+	int64		pInfcount;		/* count of +infinity intervals */
+	int64		nInfcount;		/* count of -infinity intervals */
 } IntervalAggState;
 
 #define IA_TOTAL_COUNT(ia) \
@@ -96,11 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
 
-static void interval_um_internal(Interval *interval, Interval *result);
-static void finite_interval_pl(Interval *result, Interval *span1,
-							   Interval *span2);
-static void finite_interval_mi(Interval *result, Interval *span1,
-							   Interval *span2);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+							   Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+							   Interval *result);
 
 /* common code for timestamptypmodin and timestamptztypmodin */
 static int32
@@ -1604,8 +1604,8 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
 		elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
 }
 
-void
-EncodeSpecialInterval(Interval *interval, char *str)
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
 {
 	if (INTERVAL_IS_NOBEGIN(interval))
 		strcpy(str, EARLY);
@@ -2101,6 +2101,13 @@ itm2interval(struct pg_itm *itm, Interval *span)
 /* itmin2interval()
  * Convert a pg_itm_in structure to an Interval.
  * Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow.  This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range).  Such intervals will be
+ * silently converted to +/-infinity.  This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
  */
 int
 itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2112,8 +2119,6 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
 	span->month = (int32) total_months;
 	span->day = itm_in->tm_mday;
 	span->time = itm_in->tm_usec;
-	if (INTERVAL_NOT_FINITE(span))
-		return -1;
 	return 0;
 }
 
@@ -2280,29 +2285,6 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
 }
 #endif
 
-double
-interval2timestamp_no_overflow(Interval *interval)
-{
-	double		result;
-
-	if (INTERVAL_IS_NOBEGIN(interval))
-		result = -DBL_MAX;
-	else if (INTERVAL_IS_NOEND(interval))
-		result = DBL_MAX;
-	else
-	{
-		/*
-		 * Convert the month part of Interval to days using assumed average
-		 * month length of 365.25/12.0 days.  Not too accurate, but plenty
-		 * good enough for our purposes.
-		 */
-		return interval->time + interval->day * (double) USECS_PER_DAY +
-			interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
-	}
-
-	return result;
-}
-
 Datum
 timestamp_sortsupport(PG_FUNCTION_ARGS)
 {
@@ -2526,6 +2508,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
 	return int128_compare(span1, span2);
 }
 
+static int
+interval_sign(const Interval *interval)
+{
+	INT128		span = interval_cmp_value(interval);
+	INT128		zero = int64_to_int128(0);
+
+	return int128_compare(span, zero);
+}
+
 Datum
 interval_eq(PG_FUNCTION_ARGS)
 {
@@ -2801,14 +2792,14 @@ timestamp_mi(PG_FUNCTION_ARGS)
 	/*
 	 * Subtracting two infinite timestamps with different signs results in an
 	 * infinite interval with the same sign as the left operand. Subtracting
-	 * two infinte timestamps with the same sign results in an error.
+	 * two infinite timestamps with the same sign results in an error.
 	 */
 	if (TIMESTAMP_IS_NOBEGIN(dt1))
 	{
 		if (TIMESTAMP_IS_NOBEGIN(dt2))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+					 errmsg("interval out of range")));
 		else
 			INTERVAL_NOBEGIN(result);
 	}
@@ -2817,7 +2808,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
 		if (TIMESTAMP_IS_NOEND(dt2))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+					 errmsg("interval out of range")));
 		else
 			INTERVAL_NOEND(result);
 	}
@@ -3068,16 +3059,16 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
 	Timestamp	result;
 
 	/*
-	 * Adding two infinites with the same sign results in an infinite
-	 * timestamp with the same sign. Adding two infintes with different signs
-	 * results in an error.
+	 * Adding two infinities with the same sign results in an infinite
+	 * timestamp with the same sign. Adding two infinities with different
+	 * signs results in an error.
 	 */
 	if (INTERVAL_IS_NOBEGIN(span))
 	{
 		if (TIMESTAMP_IS_NOEND(timestamp))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
+					 errmsg("timestamp out of range")));
 		else
 			TIMESTAMP_NOBEGIN(result);
 	}
@@ -3086,7 +3077,7 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
 		if (TIMESTAMP_IS_NOBEGIN(timestamp))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
+					 errmsg("timestamp out of range")));
 		else
 			TIMESTAMP_NOEND(result);
 	}
@@ -3197,16 +3188,16 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
 	int			tz;
 
 	/*
-	 * Adding two infinites with the same sign results in an infinite
-	 * timestamp with the same sign. Adding two infintes with different signs
-	 * results in an error.
+	 * Adding two infinities with the same sign results in an infinite
+	 * timestamp with the same sign. Adding two infinities with different
+	 * signs results in an error.
 	 */
 	if (INTERVAL_IS_NOBEGIN(span))
 	{
 		if (TIMESTAMP_IS_NOEND(timestamp))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
+					 errmsg("timestamp out of range")));
 		else
 			TIMESTAMP_NOBEGIN(result);
 	}
@@ -3215,7 +3206,7 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
 		if (TIMESTAMP_IS_NOBEGIN(timestamp))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("interval out of range")));
+					 errmsg("timestamp out of range")));
 		else
 			TIMESTAMP_NOEND(result);
 	}
@@ -3362,16 +3353,12 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
 
 /* Negates the given interval */
 static void
-interval_um_internal(Interval *interval, Interval *result)
+interval_um_internal(const Interval *interval, Interval *result)
 {
 	if (INTERVAL_IS_NOBEGIN(interval))
 		INTERVAL_NOEND(result);
 	else if (INTERVAL_IS_NOEND(interval))
 		INTERVAL_NOBEGIN(result);
-	else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("interval out of range")));
 	else
 	{
 		result->time = -interval->time;
@@ -3438,8 +3425,8 @@ interval_pl(PG_FUNCTION_ARGS)
 
 	/*
 	 * Adding two infinite intervals with the same signs results in an
-	 * infinite interval with the same sign. Adding two infinte intervals with
-	 * different signs results in an error.
+	 * infinite interval with the same sign. Adding two infinite intervals
+	 * with different signs results in an error.
 	 */
 	if (INTERVAL_IS_NOBEGIN(span1))
 	{
@@ -3462,7 +3449,7 @@ interval_pl(PG_FUNCTION_ARGS)
 	else if (INTERVAL_NOT_FINITE(span2))
 		memcpy(result, span2, sizeof(Interval));
 	else
-		finite_interval_pl(result, span1, span2);
+		finite_interval_pl(span1, span2, result);
 
 	PG_RETURN_INTERVAL_P(result);
 }
@@ -3479,7 +3466,7 @@ interval_mi(PG_FUNCTION_ARGS)
 	/*
 	 * Subtracting two infinite intervals with different signs results in an
 	 * infinite interval with the same sign as the left operand. Subtracting
-	 * two infinte intervals with the same sign results in an error.
+	 * two infinite intervals with the same sign results in an error.
 	 */
 	if (INTERVAL_IS_NOBEGIN(span1))
 	{
@@ -3504,7 +3491,7 @@ interval_mi(PG_FUNCTION_ARGS)
 	else if (INTERVAL_IS_NOEND(span2))
 		INTERVAL_NOBEGIN(result);
 	else
-		finite_interval_mi(result, span1, span2);
+		finite_interval_mi(span1, span2, result);
 
 	PG_RETURN_INTERVAL_P(result);
 }
@@ -3552,13 +3539,11 @@ interval_mul(PG_FUNCTION_ARGS)
 	}
 	else if (isinf(factor))
 	{
-		Interval	zero;
 		int			factor_sign,
 					result_sign;
 
 		factor_sign = factor >= 0.0 ? 1 : -1;
-		memset(&zero, 0, sizeof(zero));
-		result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+		result_sign = interval_sign(span) * factor_sign;
 
 		if (result_sign == 0)
 			ereport(ERROR,
@@ -3740,11 +3725,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
 	bool		less = PG_GETARG_BOOL(4);
 	TimestampTz sum;
 
-	if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+	if (interval_sign(offset) < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
 				 errmsg("invalid preceding or following size in window function")));
 
+	/*
+	 * Deal with cases where both base and offset are infinite, and computing
+	 * base +/- offset would cause an error.  As for float and numeric types,
+	 * we assume that all values infinitely precede +infinity and infinitely
+	 * follow -infinity.  See in_range_float8_float8() for reasoning.
+	 */
+	if (INTERVAL_IS_NOEND(offset) &&
+		(sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+		PG_RETURN_BOOL(true);
+
 	/* We don't currently bother to avoid overflow hazards here */
 	if (sub)
 		sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3767,11 +3762,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
 	bool		less = PG_GETARG_BOOL(4);
 	Timestamp	sum;
 
-	if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+	if (interval_sign(offset) < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
 				 errmsg("invalid preceding or following size in window function")));
 
+	/*
+	 * Deal with cases where both base and offset are infinite, and computing
+	 * base +/- offset would cause an error.  As for float and numeric types,
+	 * we assume that all values infinitely precede +infinity and infinitely
+	 * follow -infinity.  See in_range_float8_float8() for reasoning.
+	 */
+	if (INTERVAL_IS_NOEND(offset) &&
+		(sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+		PG_RETURN_BOOL(true);
+
 	/* We don't currently bother to avoid overflow hazards here */
 	if (sub)
 		sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3798,11 +3803,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
 	bool		less = PG_GETARG_BOOL(4);
 	Interval   *sum;
 
-	if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+	if (interval_sign(offset) < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
 				 errmsg("invalid preceding or following size in window function")));
 
+	/*
+	 * Deal with cases where both base and offset are infinite, and computing
+	 * base +/- offset would cause an error.  As for float and numeric types,
+	 * we assume that all values infinitely precede +infinity and infinitely
+	 * follow -infinity.  See in_range_float8_float8() for reasoning.
+	 */
+	if (INTERVAL_IS_NOEND(offset) &&
+		(sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+		PG_RETURN_BOOL(true);
+
 	/* We don't currently bother to avoid overflow hazards here */
 	if (sub)
 		sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3848,21 +3863,13 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
 }
 
 /*
- * Functions to add or subtract two finite intervals.
+ * Functions to add or subtract finite intervals.
  *
- * We handle non-finite intervals in different ways when accumulating/discarding
- * intervals and in actual mathematical operations respectively. But the
- * addition or subtraction of finite intervals have same implementation
- * respectively in both these cases.
- *
- * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
- * to pass either of span1 or span2 as a result pointer. In such a case result
- * will overwrite the given value.
- *
- * If the result is not a valid interval, the function throws an error.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only.  Non-finite intervals require special handling.
  */
 static void
-finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result)
 {
 	Assert(!INTERVAL_NOT_FINITE(span1));
 	Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3889,7 +3896,7 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
 }
 
 static void
-finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
 {
 	Assert(!INTERVAL_NOT_FINITE(span1));
 	Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3921,26 +3928,56 @@ finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
 static void
 do_interval_accum(IntervalAggState *state, Interval *newval)
 {
-	/* Count -infinity inputs separately from all else */
+	/* Infinite inputs are counted separately, and do not affect "N" */
 	if (INTERVAL_IS_NOBEGIN(newval))
 	{
 		state->nInfcount++;
 		return;
 	}
 
-	/* Count infinity inputs separately from all else */
 	if (INTERVAL_IS_NOEND(newval))
 	{
 		state->pInfcount++;
 		return;
 	}
 
-	finite_interval_pl(&state->sumX, &state->sumX, newval);
+	finite_interval_pl(&state->sumX, newval, &state->sumX);
 	state->N++;
 }
 
 /*
- * Transition function for interval aggregates.
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+	/* Infinite inputs are counted separately, and do not affect "N" */
+	if (INTERVAL_IS_NOBEGIN(newval))
+	{
+		state->nInfcount--;
+		return;
+	}
+
+	if (INTERVAL_IS_NOEND(newval))
+	{
+		state->pInfcount--;
+		return;
+	}
+
+	/* Handle to be discarded finite value. */
+	state->N--;
+	if (state->N > 0)
+		finite_interval_mi(&state->sumX, newval, &state->sumX);
+	else
+	{
+		/* All values discarded, reset the state */
+		Assert(state->N == 0);
+		memset(&state->sumX, 0, sizeof(state->sumX));
+	}
+}
+
+/*
+ * Transition function for sum() and avg() interval aggregates.
  */
 Datum
 interval_avg_accum(PG_FUNCTION_ARGS)
@@ -3960,10 +3997,10 @@ interval_avg_accum(PG_FUNCTION_ARGS)
 }
 
 /*
- * Combine function for interval aggregates.
+ * Combine function for sum() and avg() interval aggregates.
  *
- * Combine the given internal aggregate states and place the combination in the
- * first argument.
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
  */
 Datum
 interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3999,7 +4036,7 @@ interval_avg_combine(PG_FUNCTION_ARGS)
 
 	/* Accumulate finite interval values, if any. */
 	if (state2->N > 0)
-		finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
+		finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
 
 	PG_RETURN_POINTER(state1);
 }
@@ -4019,20 +4056,26 @@ interval_avg_serialize(PG_FUNCTION_ARGS)
 	if (!AggCheckCallContext(fcinfo, NULL))
 		elog(ERROR, "aggregate function called in non-aggregate context");
 
-	/* TODO: Handle NULL inputs? */
 	state = (IntervalAggState *) PG_GETARG_POINTER(0);
+
 	pq_begintypsend(&buf);
+
 	/* N */
 	pq_sendint64(&buf, state->N);
-	/* Finite interval value */
+
+	/* sumX */
 	pq_sendint64(&buf, state->sumX.time);
 	pq_sendint32(&buf, state->sumX.day);
 	pq_sendint32(&buf, state->sumX.month);
+
 	/* pInfcount */
 	pq_sendint64(&buf, state->pInfcount);
+
 	/* nInfcount */
 	pq_sendint64(&buf, state->nInfcount);
+
 	result = pq_endtypsend(&buf);
+
 	PG_RETURN_BYTEA_P(result);
 }
 
@@ -4064,10 +4107,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
 	/* N */
 	result->N = pq_getmsgint64(&buf);
 
-	/* Interval struct elements, one by one. */
+	/* sumX */
 	result->sumX.time = pq_getmsgint64(&buf);
-	result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
-	result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+	result->sumX.day = pq_getmsgint(&buf, 4);
+	result->sumX.month = pq_getmsgint(&buf, 4);
 
 	/* pInfcount */
 	result->pInfcount = pq_getmsgint64(&buf);
@@ -4081,42 +4124,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
 }
 
 /*
- * Remove the given interval value from the aggregated state.
- */
-static void
-do_interval_discard(IntervalAggState *state, Interval *newval)
-{
-	/* Count -infinity inputs separately from all else */
-	if (INTERVAL_IS_NOBEGIN(newval))
-	{
-		state->nInfcount--;
-		return;
-	}
-
-	/* Count infinity inputs separately from all else */
-	if (INTERVAL_IS_NOEND(newval))
-	{
-		state->pInfcount--;
-		return;
-	}
-
-	/* Handle to be discarded finite value. */
-	state->N--;
-	if (state->N > 0)
-		finite_interval_mi(&state->sumX, &state->sumX, newval);
-	else
-	{
-		/* All values discarded, reset the state */
-		Assert(state->N == 0);
-		memset(&state->sumX, 0, sizeof(state->sumX));
-	}
-}
-
-/*
- * Generic inverse transition function for interval aggregates
+ * Inverse transition function for sum() and avg() interval aggregates.
  */
 Datum
-interval_accum_inv(PG_FUNCTION_ARGS)
+interval_avg_accum_inv(PG_FUNCTION_ARGS)
 {
 	IntervalAggState *state;
 
@@ -4124,7 +4135,7 @@ interval_accum_inv(PG_FUNCTION_ARGS)
 
 	/* Should not get here with no state */
 	if (state == NULL)
-		elog(ERROR, "interval_accum_inv called with NULL state");
+		elog(ERROR, "interval_avg_accum_inv called with NULL state");
 
 	if (!PG_ARGISNULL(1))
 		do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
@@ -4137,38 +4148,39 @@ Datum
 interval_avg(PG_FUNCTION_ARGS)
 {
 	IntervalAggState *state;
-	double		N_datum;
-	Interval   *sumX;
 
-	sumX = (Interval *) palloc(sizeof(Interval));
 	state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
+	/* If there were no non-null inputs, return NULL */
 	if (state == NULL || IA_TOTAL_COUNT(state) == 0)
 		PG_RETURN_NULL();
 
-	/* adding plus and minus infinities gives error */
-	if (state->pInfcount > 0 && state->nInfcount > 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-				 errmsg("interval out of range.")));
-
-	if (state->pInfcount > 0)
+	/*
+	 * Aggregating infinities that all have the same sign produces infinity
+	 * with that sign.  Aggregating infinities with different signs results in
+	 * an error.
+	 */
+	if (state->pInfcount > 0 || state->nInfcount > 0)
 	{
-		INTERVAL_NOEND(sumX);
-		PG_RETURN_INTERVAL_P(sumX);
-	}
+		Interval   *result;
 
-	if (state->nInfcount > 0)
-	{
-		INTERVAL_NOBEGIN(sumX);
-		PG_RETURN_INTERVAL_P(sumX);
-	}
+		if (state->pInfcount > 0 && state->nInfcount > 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+					 errmsg("interval out of range.")));
+
+		result = (Interval *) palloc(sizeof(Interval));
+		if (state->pInfcount > 0)
+			INTERVAL_NOEND(result);
+		else
+			INTERVAL_NOBEGIN(result);
 
-	N_datum = (double) state->N;
-	sumX = &state->sumX;
+		PG_RETURN_INTERVAL_P(result);
+	}
 
-	PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
-										Float8GetDatum(N_datum)));
+	return DirectFunctionCall2(interval_div,
+							   IntervalPGetDatum(&state->sumX),
+							   Float8GetDatum((double) state->N));
 }
 
 /* sum(interval) aggregate final function */
@@ -4178,33 +4190,31 @@ interval_sum(PG_FUNCTION_ARGS)
 	IntervalAggState *state;
 	Interval   *result;
 
-	result = (Interval *) palloc(sizeof(Interval));
-
 	state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
 	/* If there were no non-null inputs, return NULL */
 	if (state == NULL || IA_TOTAL_COUNT(state) == 0)
 		PG_RETURN_NULL();
 
-	/* adding plus and minus interval infinities is not possible */
+	/*
+	 * Aggregating infinities that all have the same sign produces infinity
+	 * with that sign.  Aggregating infinities with different signs results in
+	 * an error.
+	 */
 	if (state->pInfcount > 0 && state->nInfcount > 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
 				 errmsg("interval out of range.")));
 
+	result = (Interval *) palloc(sizeof(Interval));
+
 	if (state->pInfcount > 0)
-	{
 		INTERVAL_NOEND(result);
-		PG_RETURN_INTERVAL_P(result);
-	}
-
-	if (state->nInfcount > 0)
-	{
+	else if (state->nInfcount > 0)
 		INTERVAL_NOBEGIN(result);
-		PG_RETURN_INTERVAL_P(result);
-	}
+	else
+		memcpy(result, &state->sumX, sizeof(Interval));
 
-	memcpy(result, &state->sumX, sizeof(Interval));
 	PG_RETURN_INTERVAL_P(result);
 }
 
@@ -4234,14 +4244,14 @@ timestamp_age(PG_FUNCTION_ARGS)
 	/*
 	 * Subtracting two infinite timestamps with different signs results in an
 	 * infinite interval with the same sign as the left operand. Subtracting
-	 * two infinte timestamps with the same sign results in an error.
+	 * two infinite timestamps with the same sign results in an error.
 	 */
 	if (TIMESTAMP_IS_NOBEGIN(dt1))
 	{
 		if (TIMESTAMP_IS_NOBEGIN(dt2))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+					 errmsg("interval out of range")));
 		else
 			INTERVAL_NOBEGIN(result);
 	}
@@ -4250,7 +4260,7 @@ timestamp_age(PG_FUNCTION_ARGS)
 		if (TIMESTAMP_IS_NOEND(dt2))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+					 errmsg("interval out of range")));
 		else
 			INTERVAL_NOEND(result);
 	}
@@ -4381,14 +4391,14 @@ timestamptz_age(PG_FUNCTION_ARGS)
 	/*
 	 * Subtracting two infinite timestamps with different signs results in an
 	 * infinite interval with the same sign as the left operand. Subtracting
-	 * two infinte timestamps with the same sign results in an error.
+	 * two infinite timestamps with the same sign results in an error.
 	 */
 	if (TIMESTAMP_IS_NOBEGIN(dt1))
 	{
 		if (TIMESTAMP_IS_NOBEGIN(dt2))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+					 errmsg("interval out of range")));
 		else
 			INTERVAL_NOBEGIN(result);
 	}
@@ -4397,7 +4407,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
 		if (TIMESTAMP_IS_NOEND(dt2))
 			ereport(ERROR,
 					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-					 errmsg("timestamp out of range")));
+					 errmsg("interval out of range")));
 		else
 			INTERVAL_NOEND(result);
 	}
@@ -5829,17 +5839,16 @@ extract_timestamptz(PG_FUNCTION_ARGS)
 /*
  * NonFiniteIntervalPart
  *
- *	Used by interval_part when extracting from infinite
- *	interval.  Returns +/-Infinity if that is the appropriate result,
- *	otherwise returns zero (which should be taken as meaning to return NULL).
+ *	Used by interval_part when extracting from infinite interval.  Returns
+ *	+/-Infinity if that is the appropriate result, otherwise returns zero
+ *	(which should be taken as meaning to return NULL).
  *
  *	Errors thrown here for invalid units should exactly match those that
  *	would be thrown in the calling functions, else there will be unexpected
  *	discrepancies between finite- and infinite-input cases.
  */
 static float8
-NonFiniteIntervalPart(int type, int unit, char *lowunits,
-					  bool isNegative, bool isTz)
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
 {
 	if ((type != UNITS) && (type != RESERV))
 		ereport(ERROR,
@@ -5906,8 +5915,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
 	if (INTERVAL_NOT_FINITE(interval))
 	{
 		double		r = NonFiniteIntervalPart(type, val, lowunits,
-											  INTERVAL_IS_NOBEGIN(interval),
-											  false);
+											  INTERVAL_IS_NOBEGIN(interval));
 
 		if (r != 0.0)
 		{
@@ -6453,7 +6461,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
 		Timestamp	finish = PG_GETARG_TIMESTAMP(1);
 		Interval   *step = PG_GETARG_INTERVAL_P(2);
 		MemoryContext oldcontext;
-		const Interval interval_zero = {0};
 
 		/* create a function context for cross-call persistence */
 		funcctx = SRF_FIRSTCALL_INIT();
@@ -6476,7 +6483,7 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
 		fctx->step = *step;
 
 		/* Determine sign of the interval */
-		fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+		fctx->step_sign = interval_sign(&fctx->step);
 
 		if (fctx->step_sign == 0)
 			ereport(ERROR,
@@ -6539,7 +6546,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
 		Interval   *step = PG_GETARG_INTERVAL_P(2);
 		text	   *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
 		MemoryContext oldcontext;
-		const Interval interval_zero = {0};
 
 		/* create a function context for cross-call persistence */
 		funcctx = SRF_FIRSTCALL_INIT();
@@ -6563,7 +6569,7 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
 		fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
 
 		/* Determine sign of the interval */
-		fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+		fctx->step_sign = interval_sign(&fctx->step);
 
 		if (fctx->step_sign == 0)
 			ereport(ERROR,
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 0e62c3f7a6..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -47,11 +47,9 @@
   aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
   aggserialfn => 'interval_avg_serialize',
   aggdeserialfn => 'interval_avg_deserialize',
-  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
   aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
-  aggmtranstype => 'internal', aggtransspace => '128',
-  aggmtransspace => '128'
-},
+  aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' },
 
 # sum
 { aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -79,10 +77,9 @@
   aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
   aggserialfn => 'interval_avg_serialize',
   aggdeserialfn => 'interval_avg_deserialize',
-  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
   aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
-  aggmtranstype => 'internal', aggtransspace => '128',
-  aggmtransspace => '128'},
+  aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'},
 { aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
   aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
   aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7d66de5043..455ed16c79 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4925,9 +4925,9 @@
   prorettype => 'internal', proargtypes => 'internal internal',
   prosrc => 'interval_avg_combine' },
 { oid => '3549', descr => 'aggregate transition function',
-  proname => 'interval_accum_inv', proisstrict => 'f',
+  proname => 'interval_avg_accum_inv', proisstrict => 'f',
   prorettype => 'internal', proargtypes => 'internal interval',
-  prosrc => 'interval_accum_inv' },
+  prosrc => 'interval_avg_accum_inv' },
 { oid => '3813', descr => 'aggregate serial function',
   proname => 'interval_avg_serialize', prorettype => 'bytea',
   proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
@@ -4935,7 +4935,7 @@
   proname => 'interval_avg_deserialize', prorettype => 'internal',
   proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
 { oid => '1844', descr => 'aggregate final function',
-  proname => 'interval_avg', prorettype => 'interval',
+  proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval',
   proargtypes => 'internal', prosrc => 'interval_avg' },
 { oid => '8069', descr => 'aggregate final function',
   proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index f9bd30d225..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
 #define TIMESTAMP_INFINITY	PG_INT64_MAX
 
 /*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
  */
 #define DT_NOBEGIN		TIMESTAMP_MINUS_INFINITY
 #define DT_NOEND		TIMESTAMP_INFINITY
@@ -168,23 +168,29 @@ struct pg_itm_in
 
 #define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
 
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
 #define INTERVAL_NOBEGIN(i)	\
 	do {	\
-		(i->time) = PG_INT64_MIN;	\
-		(i->day) = PG_INT32_MIN;	\
-		(i->month) = PG_INT32_MIN;	\
+		(i)->time = PG_INT64_MIN;	\
+		(i)->day = PG_INT32_MIN;	\
+		(i)->month = PG_INT32_MIN;	\
 	} while (0)
 
-#define INTERVAL_IS_NOBEGIN(i) ((i->month) == PG_INT32_MIN)
+#define INTERVAL_IS_NOBEGIN(i)	\
+	((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
 
 #define INTERVAL_NOEND(i)	\
 	do {	\
-		(i->time) = PG_INT64_MAX;	\
-		(i->day) = PG_INT32_MAX;	\
-		(i->month) = PG_INT32_MAX;	\
+		(i)->time = PG_INT64_MAX;	\
+		(i)->day = PG_INT32_MAX;	\
+		(i)->month = PG_INT32_MAX;	\
 	} while (0)
 
-#define INTERVAL_IS_NOEND(i) ((i->month) == PG_INT32_MAX)
+#define INTERVAL_IS_NOEND(i)	\
+	((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
 
 #define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
 
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index fe713fb6dd..c4dd96c8c9 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,7 +118,6 @@ extern int	timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
 						 fsec_t *fsec, const char **tzn, pg_tz *attimezone);
 extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
 
-extern void EncodeSpecialInterval(Interval *interval, char *str);
 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);
@@ -128,8 +127,6 @@ extern void GetEpochTime(struct pg_tm *tm);
 
 extern int	timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
 
-extern double interval2timestamp_no_overflow(Interval *interval);
-
 /* timestamp comparison works for timestamptz also */
 #define timestamptz_cmp_internal(dt1,dt2)	timestamp_cmp_internal(dt1, dt2)
 
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 8bd72f60b0..00cd3e84f1 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -284,11 +284,11 @@ SELECT r1.*, r2.*
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
-  ('2147483647 days 2147483646 months'),
-  ('2147483647 days -2147483647 months'),
+  ('2147483647 days 2147483647 months'),
+  ('2147483647 days -2147483648 months'),
   ('1 year'),
-  ('-2147483648 days 2147483646 months'),
-  ('-2147483648 days -2147483647 months');
+  ('-2147483648 days 2147483647 months'),
+  ('-2147483648 days -2147483648 months');
 -- these should fail as out-of-range
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 ERROR:  interval field value out of range: "2147483648 days"
@@ -315,16 +315,16 @@ SELECT r1.*, r2.*
    ORDER BY r1.f1, r2.f1;
                     f1                     |                    f1                     
 -------------------------------------------+-------------------------------------------
- -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
- 1 year                                    | -178956970 years -7 mons -2147483648 days
- 1 year                                    | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days   | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons -2147483648 days   | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days   | 1 year
- 178956970 years 6 mons 2147483647 days    | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days    | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons 2147483647 days    | 1 year
- 178956970 years 6 mons 2147483647 days    | 178956970 years 6 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
+ 1 year                                    | -178956970 years -8 mons -2147483648 days
+ 1 year                                    | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days   | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons -2147483648 days   | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days   | 1 year
+ 178956970 years 7 mons 2147483647 days    | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days    | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons 2147483647 days    | 1 year
+ 178956970 years 7 mons 2147483647 days    | 178956970 years 7 mons -2147483648 days
 (10 rows)
 
 CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -339,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
 SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
                     f1                     
 -------------------------------------------
- -178956970 years -7 mons -2147483648 days
- -178956970 years -7 mons +2147483647 days
+ -178956970 years -8 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days
  1 year
- 178956970 years 6 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days
+ 178956970 years 7 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days
 (5 rows)
 
 RESET enable_seqscan;
@@ -485,9 +485,7 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
 
 SELECT justify_hours(interval '2147483647 days 24 hrs');
 ERROR:  interval out of range
-SELECT justify_days(interval '2147483646 months 30 days');
-ERROR:  interval out of range
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
 ERROR:  interval out of range
 -- test justify_interval()
 SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -508,29 +506,25 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
  @ 5965232 years 4 mons 9 days ago
 (1 row)
 
-SELECT justify_interval(interval '2147483646 months 30 days');
-ERROR:  interval out of range
-SELECT justify_interval(interval '2147483646 months 60 days');
-ERROR:  interval out of range
-SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days');
 ERROR:  interval out of range
-SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
 ERROR:  interval out of range
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
          justify_interval         
 ----------------------------------
- @ 178956970 years 6 mons 29 days
+ @ 178956970 years 7 mons 29 days
 (1 row)
 
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
            justify_interval           
 --------------------------------------
- @ 178956970 years 7 mons 29 days ago
+ @ 178956970 years 8 mons 29 days ago
 (1 row)
 
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
 ERROR:  interval out of range
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
 ERROR:  interval out of range
 -- test fractional second input, and detection of duplicate units
 SET DATESTYLE = 'ISO';
@@ -1882,233 +1876,144 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)
 
--- infinite intervals
-SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
-ERROR:  interval out of range
-LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
-                        ^
-SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
-ERROR:  interval out of range
-LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
-                        ^
-CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
-INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
-SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
-            i            | isfinite 
--------------------------+----------
- infinity                | f
- -infinity               | f
- @ 1 year 2 days 3 hours | t
-(3 rows)
-
-SELECT date '1995-08-06' + interval 'infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT date '1995-08-06' + interval '-infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval 'infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval '-infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval 'infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval '-infinity';
-ERROR:  interval out of range
-SELECT date '-infinity' + interval 'infinity';
-ERROR:  interval out of range
-SELECT date '-infinity' + interval '-infinity';
- ?column?  
------------
- -infinity
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+                                   interval                                   
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
 (1 row)
 
-SELECT date 'infinity' - interval 'infinity';
-ERROR:  interval out of range
-SELECT date 'infinity' - interval '-infinity';
- ?column? 
-----------
- infinity
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+                                 interval                                 
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
 (1 row)
 
-SELECT date '-infinity' - interval 'infinity';
- ?column?  
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval  
 -----------
  -infinity
 (1 row)
 
-SELECT date '-infinity' - interval '-infinity';
-ERROR:  interval out of range
-SELECT interval 'infinity' + interval 'infinity';
- ?column? 
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval 
 ----------
  infinity
 (1 row)
 
-SELECT interval 'infinity' + interval '-infinity';
-ERROR:  interval out of range
-SELECT interval '-infinity' + interval 'infinity';
-ERROR:  interval out of range
-SELECT interval '-infinity' + interval '-infinity';
- ?column?  
------------
- -infinity
-(1 row)
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+            i            | isfinite 
+-------------------------+----------
+ infinity                | f
+ -infinity               | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
 
-SELECT interval 'infinity' + interval '10 days';
- ?column? 
-----------
- infinity
-(1 row)
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+  result text;
+BEGIN
+  EXECUTE 'select '||expr INTO result;
+  RETURN result;
+EXCEPTION WHEN OTHERS THEN
+  RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+       eval(format('date %L + interval %L', d, i)) AS plus,
+       eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+             (date '1995-08-06'),
+             (date 'infinity')) AS t1(d),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+    date    | interval  |          plus          |         minus          
+------------+-----------+------------------------+------------------------
+ -infinity  | -infinity | -infinity              | timestamp out of range
+ -infinity  | infinity  | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity              | infinity
+ 1995-08-06 | infinity  | infinity               | -infinity
+ infinity   | -infinity | timestamp out of range | infinity
+ infinity   | infinity  | infinity               | timestamp out of range
+(6 rows)
 
-SELECT interval '-infinity' + interval '10 days';
- ?column?  
------------
- -infinity
-(1 row)
+SELECT i1 AS interval1, i2 AS interval2,
+       eval(format('interval %L + interval %L', i1, i2)) AS plus,
+       eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+             (interval '2 months'),
+             (interval 'infinity')) AS t1(i1),
+     (VALUES (interval '-infinity'),
+             (interval '10 days'),
+             (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 |         plus          |         minus         
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity             | interval out of range
+ -infinity | @ 10 days | -infinity             | -infinity
+ -infinity | infinity  | interval out of range | -infinity
+ @ 2 mons  | -infinity | -infinity             | infinity
+ @ 2 mons  | @ 10 days | @ 2 mons 10 days      | @ 2 mons -10 days
+ @ 2 mons  | infinity  | infinity              | -infinity
+ infinity  | -infinity | interval out of range | infinity
+ infinity  | @ 10 days | infinity              | infinity
+ infinity  | infinity  | infinity              | interval out of range
+(9 rows)
 
 SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
 ERROR:  interval out of range
 SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
 ERROR:  interval out of range
-SELECT interval 'infinity' - interval 'infinity';
-ERROR:  interval out of range
-SELECT interval 'infinity' - interval '-infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval 'infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '-infinity';
-ERROR:  interval out of range
-SELECT interval 'infinity' - interval '10 days';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '10 days';
- ?column?  
------------
- -infinity
-(1 row)
-
 SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
 ERROR:  interval out of range
 SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
 ERROR:  interval out of range
-SELECT timestamp 'infinity' + interval 'infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT timestamp 'infinity' + interval '-infinity';
-ERROR:  interval out of range
-SELECT timestamp '-infinity' + interval 'infinity';
-ERROR:  interval out of range
-SELECT timestamp '-infinity' + interval '-infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT timestamp 'infinity' - interval 'infinity';
-ERROR:  interval out of range
-SELECT timestamp 'infinity' - interval '-infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval 'infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval '-infinity';
-ERROR:  interval out of range
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval 'infinity';
- ?column? 
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval '-infinity';
-ERROR:  interval out of range
-SELECT timestamptz '-infinity' + interval 'infinity';
-ERROR:  interval out of range
-SELECT timestamptz '-infinity' + interval '-infinity';
- ?column?  
------------
- -infinity
-(1 row)
-
-SELECT timestamptz 'infinity' - interval 'infinity';
-ERROR:  interval out of range
-SELECT timestamptz 'infinity' - interval '-infinity';
- ?column? 
-----------
- infinity
-(1 row)
+SELECT t AS timestamp, i AS interval,
+       eval(format('timestamp %L + interval %L', t, i)) AS plus,
+       eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+             (timestamp '1995-08-06 12:30:15'),
+             (timestamp 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+      timestamp      | interval  |          plus          |         minus          
+---------------------+-----------+------------------------+------------------------
+ -infinity           | -infinity | -infinity              | timestamp out of range
+ -infinity           | infinity  | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity              | infinity
+ 1995-08-06 12:30:15 | infinity  | infinity               | -infinity
+ infinity            | -infinity | timestamp out of range | infinity
+ infinity            | infinity  | infinity               | timestamp out of range
+(6 rows)
 
-SELECT timestamptz '-infinity' - interval 'infinity';
- ?column?  
------------
- -infinity
-(1 row)
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+       eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+       eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+             (timestamptz '1995-08-06 12:30:15 GMT'),
+             (timestamptz 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+     timestamptz     | interval  |          plus          |         minus          
+---------------------+-----------+------------------------+------------------------
+ -infinity           | -infinity | -infinity              | timestamp out of range
+ -infinity           | infinity  | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity              | infinity
+ 1995-08-06 12:30:15 | infinity  | infinity               | -infinity
+ infinity            | -infinity | timestamp out of range | infinity
+ infinity            | infinity  | infinity               | timestamp out of range
+(6 rows)
 
-SELECT timestamptz '-infinity' - interval '-infinity';
-ERROR:  interval out of range
+-- time +/- infinite interval not supported
 SELECT time '11:27:42' + interval 'infinity';
 ERROR:  cannot add infinite interval to time
 SELECT time '11:27:42' + interval '-infinity';
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 53542e076b..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2131,7 +2131,7 @@ select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12
 ERROR:  step size cannot be infinite
 -- test arithmetic with infinite timestamps
 select timestamp 'infinity' - timestamp 'infinity';
-ERROR:  timestamp out of range
+ERROR:  interval out of range
 select timestamp 'infinity' - timestamp '-infinity';
  ?column? 
 ----------
@@ -2145,7 +2145,7 @@ select timestamp '-infinity' - timestamp 'infinity';
 (1 row)
 
 select timestamp '-infinity' - timestamp '-infinity';
-ERROR:  timestamp out of range
+ERROR:  interval out of range
 select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
  ?column? 
 ----------
@@ -2172,7 +2172,7 @@ select age(timestamp '-infinity');
 (1 row)
 
 select age(timestamp 'infinity', timestamp 'infinity');
-ERROR:  timestamp out of range
+ERROR:  interval out of range
 select age(timestamp 'infinity', timestamp '-infinity');
    age    
 ----------
@@ -2186,4 +2186,4 @@ select age(timestamp '-infinity', timestamp 'infinity');
 (1 row)
 
 select age(timestamp '-infinity', timestamp '-infinity');
-ERROR:  timestamp out of range
+ERROR:  interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index e3f0d918b9..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3216,7 +3216,7 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 
 -- test arithmetic with infinite timestamps
 SELECT timestamptz 'infinity' - timestamptz 'infinity';
-ERROR:  timestamp out of range
+ERROR:  interval out of range
 SELECT timestamptz 'infinity' - timestamptz '-infinity';
  ?column? 
 ----------
@@ -3230,7 +3230,7 @@ SELECT timestamptz '-infinity' - timestamptz 'infinity';
 (1 row)
 
 SELECT timestamptz '-infinity' - timestamptz '-infinity';
-ERROR:  timestamp out of range
+ERROR:  interval out of range
 SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
  ?column? 
 ----------
@@ -3257,7 +3257,7 @@ SELECT age(timestamptz '-infinity');
 (1 row)
 
 SELECT age(timestamptz 'infinity', timestamptz 'infinity');
-ERROR:  timestamp out of range
+ERROR:  interval out of range
 SELECT age(timestamptz 'infinity', timestamptz '-infinity');
    age    
 ----------
@@ -3271,4 +3271,4 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
 (1 row)
 
 SELECT age(timestamptz '-infinity', timestamptz '-infinity');
-ERROR:  timestamp out of range
+ERROR:  interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 63f36e069e..6b8c3c3413 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
     f_timestamp timestamp
 );
 insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
 (1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
 (2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
 (3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
 (7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
 (8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
 (9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
 select id, f_time, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_time range between
              '70 min'::interval preceding and '2 hours'::interval following);
  id |  f_time  | first_value | last_value 
 ----+----------+-------------+------------
-  1 | 11:00:00 |           1 |          3
+  0 | 10:00:00 |           0 |          2
+  1 | 11:00:00 |           0 |          3
   2 | 12:00:00 |           1 |          4
   3 | 13:00:00 |           2 |          6
   4 | 14:00:00 |           3 |          6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
   6 | 15:00:00 |           4 |          7
   7 | 17:00:00 |           7 |          9
   8 | 18:00:00 |           7 |         10
-  9 | 19:00:00 |           8 |         10
- 10 | 20:00:00 |           9 |         10
-(10 rows)
+  9 | 19:00:00 |           8 |         11
+ 10 | 20:00:00 |           9 |         11
+ 11 | 21:00:00 |          10 |         11
+(12 rows)
 
 select id, f_time, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
              '70 min' preceding and '2 hours' following);
  id |  f_time  | first_value | last_value 
 ----+----------+-------------+------------
- 10 | 20:00:00 |          10 |          8
+ 11 | 21:00:00 |          11 |          9
+ 10 | 20:00:00 |          11 |          8
   9 | 19:00:00 |          10 |          7
   8 | 18:00:00 |           9 |          7
   7 | 17:00:00 |           8 |          5
@@ -2414,9 +2419,70 @@ window w as (order by f_time desc range between
   5 | 15:00:00 |           6 |          3
   4 | 14:00:00 |           6 |          2
   3 | 13:00:00 |           4 |          1
-  2 | 12:00:00 |           3 |          1
-  1 | 11:00:00 |           2 |          1
-(10 rows)
+  2 | 12:00:00 |           3 |          0
+  1 | 11:00:00 |           2 |          0
+  0 | 10:00:00 |           1 |          0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |  f_time  | first_value | last_value 
+----+----------+-------------+------------
+  0 | 10:00:00 |           0 |         11
+  1 | 11:00:00 |           0 |         11
+  2 | 12:00:00 |           0 |         11
+  3 | 13:00:00 |           0 |         11
+  4 | 14:00:00 |           0 |         11
+  5 | 15:00:00 |           0 |         11
+  6 | 15:00:00 |           0 |         11
+  7 | 17:00:00 |           0 |         11
+  8 | 18:00:00 |           0 |         11
+  9 | 19:00:00 |           0 |         11
+ 10 | 20:00:00 |           0 |         11
+ 11 | 21:00:00 |           0 |         11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |  f_time  | first_value | last_value 
+----+----------+-------------+------------
+  0 | 10:00:00 |             |           
+  1 | 11:00:00 |             |           
+  2 | 12:00:00 |             |           
+  3 | 13:00:00 |             |           
+  4 | 14:00:00 |             |           
+  5 | 15:00:00 |             |           
+  6 | 15:00:00 |             |           
+  7 | 17:00:00 |             |           
+  8 | 18:00:00 |             |           
+  9 | 19:00:00 |             |           
+ 10 | 20:00:00 |             |           
+ 11 | 21:00:00 |             |           
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |  f_time  | first_value | last_value 
+----+----------+-------------+------------
+  0 | 10:00:00 |             |           
+  1 | 11:00:00 |             |           
+  2 | 12:00:00 |             |           
+  3 | 13:00:00 |             |           
+  4 | 14:00:00 |             |           
+  5 | 15:00:00 |             |           
+  6 | 15:00:00 |             |           
+  7 | 17:00:00 |             |           
+  8 | 18:00:00 |             |           
+  9 | 19:00:00 |             |           
+ 10 | 20:00:00 |             |           
+ 11 | 21:00:00 |             |           
+(12 rows)
 
 select id, f_timetz, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2424,7 +2490,8 @@ window w as (order by f_timetz range between
              '70 min'::interval preceding and '2 hours'::interval following);
  id |  f_timetz   | first_value | last_value 
 ----+-------------+-------------+------------
-  1 | 11:00:00+01 |           1 |          3
+  0 | 10:00:00+01 |           0 |          2
+  1 | 11:00:00+01 |           0 |          3
   2 | 12:00:00+01 |           1 |          4
   3 | 13:00:00+01 |           2 |          6
   4 | 14:00:00+01 |           3 |          6
@@ -2432,9 +2499,10 @@ window w as (order by f_timetz range between
   6 | 15:00:00+01 |           4 |          7
   7 | 17:00:00+01 |           7 |          9
   8 | 18:00:00+01 |           7 |         10
-  9 | 19:00:00+01 |           8 |         10
- 10 | 20:00:00+01 |           9 |         10
-(10 rows)
+  9 | 19:00:00+01 |           8 |         11
+ 10 | 20:00:00+01 |           9 |         11
+ 11 | 21:00:00+01 |          10 |         11
+(12 rows)
 
 select id, f_timetz, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2442,7 +2510,8 @@ window w as (order by f_timetz desc range between
              '70 min' preceding and '2 hours' following);
  id |  f_timetz   | first_value | last_value 
 ----+-------------+-------------+------------
- 10 | 20:00:00+01 |          10 |          8
+ 11 | 21:00:00+01 |          11 |          9
+ 10 | 20:00:00+01 |          11 |          8
   9 | 19:00:00+01 |          10 |          7
   8 | 18:00:00+01 |           9 |          7
   7 | 17:00:00+01 |           8 |          5
@@ -2450,9 +2519,70 @@ window w as (order by f_timetz desc range between
   5 | 15:00:00+01 |           6 |          3
   4 | 14:00:00+01 |           6 |          2
   3 | 13:00:00+01 |           4 |          1
-  2 | 12:00:00+01 |           3 |          1
-  1 | 11:00:00+01 |           2 |          1
-(10 rows)
+  2 | 12:00:00+01 |           3 |          0
+  1 | 11:00:00+01 |           2 |          0
+  0 | 10:00:00+01 |           1 |          0
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |  f_timetz   | first_value | last_value 
+----+-------------+-------------+------------
+  0 | 10:00:00+01 |           0 |         11
+  1 | 11:00:00+01 |           0 |         11
+  2 | 12:00:00+01 |           0 |         11
+  3 | 13:00:00+01 |           0 |         11
+  4 | 14:00:00+01 |           0 |         11
+  5 | 15:00:00+01 |           0 |         11
+  6 | 15:00:00+01 |           0 |         11
+  7 | 17:00:00+01 |           0 |         11
+  8 | 18:00:00+01 |           0 |         11
+  9 | 19:00:00+01 |           0 |         11
+ 10 | 20:00:00+01 |           0 |         11
+ 11 | 21:00:00+01 |           0 |         11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |  f_timetz   | first_value | last_value 
+----+-------------+-------------+------------
+  0 | 10:00:00+01 |             |           
+  1 | 11:00:00+01 |             |           
+  2 | 12:00:00+01 |             |           
+  3 | 13:00:00+01 |             |           
+  4 | 14:00:00+01 |             |           
+  5 | 15:00:00+01 |             |           
+  6 | 15:00:00+01 |             |           
+  7 | 17:00:00+01 |             |           
+  8 | 18:00:00+01 |             |           
+  9 | 19:00:00+01 |             |           
+ 10 | 20:00:00+01 |             |           
+ 11 | 21:00:00+01 |             |           
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |  f_timetz   | first_value | last_value 
+----+-------------+-------------+------------
+  0 | 10:00:00+01 |             |           
+  1 | 11:00:00+01 |             |           
+  2 | 12:00:00+01 |             |           
+  3 | 13:00:00+01 |             |           
+  4 | 14:00:00+01 |             |           
+  5 | 15:00:00+01 |             |           
+  6 | 15:00:00+01 |             |           
+  7 | 17:00:00+01 |             |           
+  8 | 18:00:00+01 |             |           
+  9 | 19:00:00+01 |             |           
+ 10 | 20:00:00+01 |             |           
+ 11 | 21:00:00+01 |             |           
+(12 rows)
 
 select id, f_interval, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2460,6 +2590,7 @@ window w as (order by f_interval range between
              '1 year'::interval preceding and '1 year'::interval following);
  id | f_interval | first_value | last_value 
 ----+------------+-------------+------------
+  0 | -infinity  |           0 |          0
   1 | @ 1 year   |           1 |          2
   2 | @ 2 years  |           1 |          3
   3 | @ 3 years  |           2 |          4
@@ -2470,7 +2601,8 @@ window w as (order by f_interval range between
   8 | @ 8 years  |           7 |          9
   9 | @ 9 years  |           8 |         10
  10 | @ 10 years |           9 |         10
-(10 rows)
+ 11 | infinity   |          11 |         11
+(12 rows)
 
 select id, f_interval, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2478,6 +2610,7 @@ window w as (order by f_interval desc range between
              '1 year' preceding and '1 year' following);
  id | f_interval | first_value | last_value 
 ----+------------+-------------+------------
+ 11 | infinity   |          11 |         11
  10 | @ 10 years |          10 |          9
   9 | @ 9 years  |          10 |          8
   8 | @ 8 years  |           9 |          7
@@ -2488,7 +2621,68 @@ window w as (order by f_interval desc range between
   3 | @ 3 years  |           4 |          2
   2 | @ 2 years  |           3 |          1
   1 | @ 1 year   |           2 |          1
-(10 rows)
+  0 | -infinity  |           0 |          0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value 
+----+------------+-------------+------------
+  0 | -infinity  |           0 |         11
+  1 | @ 1 year   |           0 |         11
+  2 | @ 2 years  |           0 |         11
+  3 | @ 3 years  |           0 |         11
+  4 | @ 4 years  |           0 |         11
+  5 | @ 5 years  |           0 |         11
+  6 | @ 5 years  |           0 |         11
+  7 | @ 7 years  |           0 |         11
+  8 | @ 8 years  |           0 |         11
+  9 | @ 9 years  |           0 |         11
+ 10 | @ 10 years |           0 |         11
+ 11 | infinity   |           0 |         11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value 
+----+------------+-------------+------------
+  0 | -infinity  |           0 |          0
+  1 | @ 1 year   |           0 |          0
+  2 | @ 2 years  |           0 |          0
+  3 | @ 3 years  |           0 |          0
+  4 | @ 4 years  |           0 |          0
+  5 | @ 5 years  |           0 |          0
+  6 | @ 5 years  |           0 |          0
+  7 | @ 7 years  |           0 |          0
+  8 | @ 8 years  |           0 |          0
+  9 | @ 9 years  |           0 |          0
+ 10 | @ 10 years |           0 |          0
+ 11 | infinity   |           0 |         11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value 
+----+------------+-------------+------------
+  0 | -infinity  |           0 |         11
+  1 | @ 1 year   |          11 |         11
+  2 | @ 2 years  |          11 |         11
+  3 | @ 3 years  |          11 |         11
+  4 | @ 4 years  |          11 |         11
+  5 | @ 5 years  |          11 |         11
+  6 | @ 5 years  |          11 |         11
+  7 | @ 7 years  |          11 |         11
+  8 | @ 8 years  |          11 |         11
+  9 | @ 9 years  |          11 |         11
+ 10 | @ 10 years |          11 |         11
+ 11 | infinity   |          11 |         11
+(12 rows)
 
 select id, f_timestamptz, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2496,6 +2690,7 @@ window w as (order by f_timestamptz range between
              '1 year'::interval preceding and '1 year'::interval following);
  id |        f_timestamptz         | first_value | last_value 
 ----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |          0
   1 | Thu Oct 19 02:23:54 2000 PDT |           1 |          3
   2 | Fri Oct 19 02:23:54 2001 PDT |           1 |          4
   3 | Fri Oct 19 02:23:54 2001 PDT |           1 |          4
@@ -2506,7 +2701,8 @@ window w as (order by f_timestamptz range between
   8 | Thu Oct 19 02:23:54 2006 PDT |           7 |          9
   9 | Fri Oct 19 02:23:54 2007 PDT |           8 |         10
  10 | Sun Oct 19 02:23:54 2008 PDT |           9 |         10
-(10 rows)
+ 11 | infinity                     |          11 |         11
+(12 rows)
 
 select id, f_timestamptz, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2514,6 +2710,7 @@ window w as (order by f_timestamptz desc range between
              '1 year' preceding and '1 year' following);
  id |        f_timestamptz         | first_value | last_value 
 ----+------------------------------+-------------+------------
+ 11 | infinity                     |          11 |         11
  10 | Sun Oct 19 02:23:54 2008 PDT |          10 |          9
   9 | Fri Oct 19 02:23:54 2007 PDT |          10 |          8
   8 | Thu Oct 19 02:23:54 2006 PDT |           9 |          7
@@ -2524,7 +2721,68 @@ window w as (order by f_timestamptz desc range between
   3 | Fri Oct 19 02:23:54 2001 PDT |           4 |          1
   2 | Fri Oct 19 02:23:54 2001 PDT |           4 |          1
   1 | Thu Oct 19 02:23:54 2000 PDT |           3 |          1
-(10 rows)
+  0 | -infinity                    |           0 |          0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |        f_timestamptz         | first_value | last_value 
+----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |         11
+  1 | Thu Oct 19 02:23:54 2000 PDT |           0 |         11
+  2 | Fri Oct 19 02:23:54 2001 PDT |           0 |         11
+  3 | Fri Oct 19 02:23:54 2001 PDT |           0 |         11
+  4 | Sat Oct 19 02:23:54 2002 PDT |           0 |         11
+  5 | Sun Oct 19 02:23:54 2003 PDT |           0 |         11
+  6 | Tue Oct 19 02:23:54 2004 PDT |           0 |         11
+  7 | Wed Oct 19 02:23:54 2005 PDT |           0 |         11
+  8 | Thu Oct 19 02:23:54 2006 PDT |           0 |         11
+  9 | Fri Oct 19 02:23:54 2007 PDT |           0 |         11
+ 10 | Sun Oct 19 02:23:54 2008 PDT |           0 |         11
+ 11 | infinity                     |           0 |         11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |        f_timestamptz         | first_value | last_value 
+----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |          0
+  1 | Thu Oct 19 02:23:54 2000 PDT |           0 |          0
+  2 | Fri Oct 19 02:23:54 2001 PDT |           0 |          0
+  3 | Fri Oct 19 02:23:54 2001 PDT |           0 |          0
+  4 | Sat Oct 19 02:23:54 2002 PDT |           0 |          0
+  5 | Sun Oct 19 02:23:54 2003 PDT |           0 |          0
+  6 | Tue Oct 19 02:23:54 2004 PDT |           0 |          0
+  7 | Wed Oct 19 02:23:54 2005 PDT |           0 |          0
+  8 | Thu Oct 19 02:23:54 2006 PDT |           0 |          0
+  9 | Fri Oct 19 02:23:54 2007 PDT |           0 |          0
+ 10 | Sun Oct 19 02:23:54 2008 PDT |           0 |          0
+ 11 | infinity                     |           0 |         11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |        f_timestamptz         | first_value | last_value 
+----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |         11
+  1 | Thu Oct 19 02:23:54 2000 PDT |          11 |         11
+  2 | Fri Oct 19 02:23:54 2001 PDT |          11 |         11
+  3 | Fri Oct 19 02:23:54 2001 PDT |          11 |         11
+  4 | Sat Oct 19 02:23:54 2002 PDT |          11 |         11
+  5 | Sun Oct 19 02:23:54 2003 PDT |          11 |         11
+  6 | Tue Oct 19 02:23:54 2004 PDT |          11 |         11
+  7 | Wed Oct 19 02:23:54 2005 PDT |          11 |         11
+  8 | Thu Oct 19 02:23:54 2006 PDT |          11 |         11
+  9 | Fri Oct 19 02:23:54 2007 PDT |          11 |         11
+ 10 | Sun Oct 19 02:23:54 2008 PDT |          11 |         11
+ 11 | infinity                     |          11 |         11
+(12 rows)
 
 select id, f_timestamp, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2532,6 +2790,7 @@ window w as (order by f_timestamp range between
              '1 year'::interval preceding and '1 year'::interval following);
  id |       f_timestamp        | first_value | last_value 
 ----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |          0
   1 | Thu Oct 19 10:23:54 2000 |           1 |          3
   2 | Fri Oct 19 10:23:54 2001 |           1 |          4
   3 | Fri Oct 19 10:23:54 2001 |           1 |          4
@@ -2542,7 +2801,8 @@ window w as (order by f_timestamp range between
   8 | Thu Oct 19 10:23:54 2006 |           7 |          9
   9 | Fri Oct 19 10:23:54 2007 |           8 |         10
  10 | Sun Oct 19 10:23:54 2008 |           9 |         10
-(10 rows)
+ 11 | infinity                 |          11 |         11
+(12 rows)
 
 select id, f_timestamp, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2550,6 +2810,7 @@ window w as (order by f_timestamp desc range between
              '1 year' preceding and '1 year' following);
  id |       f_timestamp        | first_value | last_value 
 ----+--------------------------+-------------+------------
+ 11 | infinity                 |          11 |         11
  10 | Sun Oct 19 10:23:54 2008 |          10 |          9
   9 | Fri Oct 19 10:23:54 2007 |          10 |          8
   8 | Thu Oct 19 10:23:54 2006 |           9 |          7
@@ -2560,7 +2821,68 @@ window w as (order by f_timestamp desc range between
   3 | Fri Oct 19 10:23:54 2001 |           4 |          1
   2 | Fri Oct 19 10:23:54 2001 |           4 |          1
   1 | Thu Oct 19 10:23:54 2000 |           3 |          1
-(10 rows)
+  0 | -infinity                |           0 |          0
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |       f_timestamp        | first_value | last_value 
+----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |         11
+  1 | Thu Oct 19 10:23:54 2000 |           0 |         11
+  2 | Fri Oct 19 10:23:54 2001 |           0 |         11
+  3 | Fri Oct 19 10:23:54 2001 |           0 |         11
+  4 | Sat Oct 19 10:23:54 2002 |           0 |         11
+  5 | Sun Oct 19 10:23:54 2003 |           0 |         11
+  6 | Tue Oct 19 10:23:54 2004 |           0 |         11
+  7 | Wed Oct 19 10:23:54 2005 |           0 |         11
+  8 | Thu Oct 19 10:23:54 2006 |           0 |         11
+  9 | Fri Oct 19 10:23:54 2007 |           0 |         11
+ 10 | Sun Oct 19 10:23:54 2008 |           0 |         11
+ 11 | infinity                 |           0 |         11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |       f_timestamp        | first_value | last_value 
+----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |          0
+  1 | Thu Oct 19 10:23:54 2000 |           0 |          0
+  2 | Fri Oct 19 10:23:54 2001 |           0 |          0
+  3 | Fri Oct 19 10:23:54 2001 |           0 |          0
+  4 | Sat Oct 19 10:23:54 2002 |           0 |          0
+  5 | Sun Oct 19 10:23:54 2003 |           0 |          0
+  6 | Tue Oct 19 10:23:54 2004 |           0 |          0
+  7 | Wed Oct 19 10:23:54 2005 |           0 |          0
+  8 | Thu Oct 19 10:23:54 2006 |           0 |          0
+  9 | Fri Oct 19 10:23:54 2007 |           0 |          0
+ 10 | Sun Oct 19 10:23:54 2008 |           0 |          0
+ 11 | infinity                 |           0 |         11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |       f_timestamp        | first_value | last_value 
+----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |         11
+  1 | Thu Oct 19 10:23:54 2000 |          11 |         11
+  2 | Fri Oct 19 10:23:54 2001 |          11 |         11
+  3 | Fri Oct 19 10:23:54 2001 |          11 |         11
+  4 | Sat Oct 19 10:23:54 2002 |          11 |         11
+  5 | Sun Oct 19 10:23:54 2003 |          11 |         11
+  6 | Tue Oct 19 10:23:54 2004 |          11 |         11
+  7 | Wed Oct 19 10:23:54 2005 |          11 |         11
+  8 | Thu Oct 19 10:23:54 2006 |          11 |         11
+  9 | Fri Oct 19 10:23:54 2007 |          11 |         11
+ 10 | Sun Oct 19 10:23:54 2008 |          11 |         11
+ 11 | infinity                 |          11 |         11
+(12 rows)
 
 -- RANGE offset PRECEDING/FOLLOWING error cases
 select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
@@ -4375,36 +4697,34 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
  4 | 
 (4 rows)
 
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
 SELECT  x
         ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
-        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
         ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
-        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
 FROM (VALUES (NULL::interval),
                ('infinity'::interval),
-               ('2147483647 days 2147483646 months'), -- extreme interval value
-               ('infinity'::timestamptz - now()),
-               ('-2147483648 days -2147483647 months'), -- extreme interval value
+               ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+               ('-infinity'::interval),
+               ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
                ('infinity'::interval),
                ('6 days'::interval),
                ('7 days'::interval),
                (NULL::interval),
                ('-infinity'::interval)) v(x);
-                      x                       |   curr_next_avg   |  prev1_curr_avg   | curr_next_sum | prev1_curr_sum 
-----------------------------------------------+-------------------+-------------------+---------------+----------------
-                                              | infinity          |                   | infinity      | 
- infinity                                     | infinity          | infinity          | infinity      | infinity
- @ 178956970 years 6 mons 2147483647 days     | infinity          | infinity          | infinity      | infinity
- infinity                                     | infinity          | infinity          | infinity      | infinity
- @ 178956970 years 7 mons 2147483648 days ago | infinity          | infinity          | infinity      | infinity
- infinity                                     | infinity          | infinity          | infinity      | infinity
- @ 6 days                                     | @ 6 days 12 hours | infinity          | @ 13 days     | infinity
- @ 7 days                                     | @ 7 days          | @ 6 days 12 hours | @ 7 days      | @ 13 days
-                                              | -infinity         | @ 7 days          | -infinity     | @ 7 days
- -infinity                                    | -infinity         | -infinity         | -infinity     | -infinity
+                                      x                                       |   curr_next_avg   |   prev_curr_avg   | curr_next_sum | prev_curr_sum 
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+                                                                              | infinity          |                   | infinity      | 
+ infinity                                                                     | infinity          | infinity          | infinity      | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity         | infinity          | -infinity     | infinity
+ -infinity                                                                    | -infinity         | -infinity         | -infinity     | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs     | infinity          | -infinity         | infinity      | -infinity
+ infinity                                                                     | infinity          | infinity          | infinity      | infinity
+ @ 6 days                                                                     | @ 6 days 12 hours | infinity          | @ 13 days     | infinity
+ @ 7 days                                                                     | @ 7 days          | @ 6 days 12 hours | @ 7 days      | @ 13 days
+                                                                              | -infinity         | @ 7 days          | -infinity     | @ 7 days
+ -infinity                                                                    | -infinity         | -infinity         | -infinity     | -infinity
 (10 rows)
 
 --should fail.
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 414c12be1c..1bffa50fe9 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -73,11 +73,11 @@ SELECT r1.*, r2.*
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
-  ('2147483647 days 2147483646 months'),
-  ('2147483647 days -2147483647 months'),
+  ('2147483647 days 2147483647 months'),
+  ('2147483647 days -2147483648 months'),
   ('1 year'),
-  ('-2147483648 days 2147483646 months'),
-  ('-2147483648 days -2147483647 months');
+  ('-2147483648 days 2147483647 months'),
+  ('-2147483648 days -2147483648 months');
 -- these should fail as out-of-range
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -164,8 +164,7 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
 SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
 
 SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483646 months 30 days');
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
 
 -- test justify_interval()
 
@@ -173,14 +172,12 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
 
 SELECT justify_interval(interval '2147483647 days 24 hrs');
 SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483646 months 30 days');
-SELECT justify_interval(interval '2147483646 months 60 days');
-SELECT justify_interval(interval '-2147483647 months -30 days');
-SELECT justify_interval(interval '-2147483647 months -60 days');
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
 
 -- test fractional second input, and detection of duplicate units
 SET DATESTYLE = 'ISO';
@@ -608,6 +605,14 @@ SELECT f1,
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
 
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
 -- infinite intervals
 SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
 SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
@@ -617,54 +622,64 @@ INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2
 
 SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
 
-SELECT date '1995-08-06' + interval 'infinity';
-SELECT date '1995-08-06' + interval '-infinity';
-SELECT date '1995-08-06' - interval 'infinity';
-SELECT date '1995-08-06' - interval '-infinity';
-SELECT date 'infinity' + interval 'infinity';
-SELECT date 'infinity' + interval '-infinity';
-SELECT date '-infinity' + interval 'infinity';
-SELECT date '-infinity' + interval '-infinity';
-SELECT date 'infinity' - interval 'infinity';
-SELECT date 'infinity' - interval '-infinity';
-SELECT date '-infinity' - interval 'infinity';
-SELECT date '-infinity' - interval '-infinity';
-SELECT interval 'infinity' + interval 'infinity';
-SELECT interval 'infinity' + interval '-infinity';
-SELECT interval '-infinity' + interval 'infinity';
-SELECT interval '-infinity' + interval '-infinity';
-SELECT interval 'infinity' + interval '10 days';
-SELECT interval '-infinity' + interval '10 days';
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+  result text;
+BEGIN
+  EXECUTE 'select '||expr INTO result;
+  RETURN result;
+EXCEPTION WHEN OTHERS THEN
+  RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+       eval(format('date %L + interval %L', d, i)) AS plus,
+       eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+             (date '1995-08-06'),
+             (date 'infinity')) AS t1(d),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+       eval(format('interval %L + interval %L', i1, i2)) AS plus,
+       eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+             (interval '2 months'),
+             (interval 'infinity')) AS t1(i1),
+     (VALUES (interval '-infinity'),
+             (interval '10 days'),
+             (interval 'infinity')) AS t2(i2);
+
 SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
 SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
-SELECT interval 'infinity' - interval 'infinity';
-SELECT interval 'infinity' - interval '-infinity';
-SELECT interval '-infinity' - interval 'infinity';
-SELECT interval '-infinity' - interval '-infinity';
-SELECT interval 'infinity' - interval '10 days';
-SELECT interval '-infinity' - interval '10 days';
 SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
 SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
-SELECT timestamp 'infinity' + interval 'infinity';
-SELECT timestamp 'infinity' + interval '-infinity';
-SELECT timestamp '-infinity' + interval 'infinity';
-SELECT timestamp '-infinity' + interval '-infinity';
-SELECT timestamp 'infinity' - interval 'infinity';
-SELECT timestamp 'infinity' - interval '-infinity';
-SELECT timestamp '-infinity' - interval 'infinity';
-SELECT timestamp '-infinity' - interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
-SELECT timestamptz 'infinity' + interval 'infinity';
-SELECT timestamptz 'infinity' + interval '-infinity';
-SELECT timestamptz '-infinity' + interval 'infinity';
-SELECT timestamptz '-infinity' + interval '-infinity';
-SELECT timestamptz 'infinity' - interval 'infinity';
-SELECT timestamptz 'infinity' - interval '-infinity';
-SELECT timestamptz '-infinity' - interval 'infinity';
-SELECT timestamptz '-infinity' - interval '-infinity';
+
+SELECT t AS timestamp, i AS interval,
+       eval(format('timestamp %L + interval %L', t, i)) AS plus,
+       eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+             (timestamp '1995-08-06 12:30:15'),
+             (timestamp 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+       eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+       eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+             (timestamptz '1995-08-06 12:30:15 GMT'),
+             (timestamptz 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
 SELECT time '11:27:42' + interval 'infinity';
 SELECT time '11:27:42' + interval '-infinity';
 SELECT time '11:27:42' - interval 'infinity';
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6dd9885949..c2a4cb8d64 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
 );
 
 insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
 (1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
 (2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
 (3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
 (7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
 (8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
 (9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
 
 select id, f_time, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -694,6 +696,21 @@ from datetimes
 window w as (order by f_time desc range between
              '70 min' preceding and '2 hours' following);
 
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval following and 'infinity'::interval following);
+
 select id, f_timetz, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timetz range between
@@ -704,6 +721,21 @@ from datetimes
 window w as (order by f_timetz desc range between
              '70 min' preceding and '2 hours' following);
 
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval following and 'infinity'::interval following);
+
 select id, f_interval, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_interval range between
@@ -714,6 +746,21 @@ from datetimes
 window w as (order by f_interval desc range between
              '1 year' preceding and '1 year' following);
 
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval following and 'infinity'::interval following);
+
 select id, f_timestamptz, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timestamptz range between
@@ -724,6 +771,21 @@ from datetimes
 window w as (order by f_timestamptz desc range between
              '1 year' preceding and '1 year' following);
 
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval following and 'infinity'::interval following);
+
 select id, f_timestamp, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timestamp range between
@@ -734,6 +796,21 @@ from datetimes
 window w as (order by f_timestamp desc range between
              '1 year' preceding and '1 year' following);
 
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval following and 'infinity'::interval following);
+
 -- RANGE offset PRECEDING/FOLLOWING error cases
 select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
 	exclude ties), salary, enroll_date from empsalary;
@@ -1591,19 +1668,17 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
 SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
 
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
 SELECT  x
         ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
-        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
         ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
-        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
 FROM (VALUES (NULL::interval),
                ('infinity'::interval),
-               ('2147483647 days 2147483646 months'), -- extreme interval value
-               ('infinity'::timestamptz - now()),
-               ('-2147483648 days -2147483647 months'), -- extreme interval value
+               ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+               ('-infinity'::interval),
+               ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
                ('infinity'::interval),
                ('6 days'::interval),
                ('7 days'::interval),
-- 
2.35.3

