#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_type.h"
+#include "common/int.h"
#include "common/string.h"
#include "funcapi.h"
#include "miscadmin.h"
static int DecodeNumberField(int len, char *str,
int fmask, int *tmask,
struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
+static int DecodeTimeCommon(char *str, int fmask, int range,
+ int *tmask, struct pg_itm *itm);
static int DecodeTime(char *str, int fmask, int range,
int *tmask, struct pg_tm *tm, fsec_t *fsec);
+static int DecodeTimeForInterval(char *str, int fmask, int range,
+ int *tmask, struct pg_itm_in *itm_in);
static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
- int scale);
-static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
- int scale);
+static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
+static bool AdjustFractMicroseconds(double frac, int64 scale,
+ struct pg_itm_in *itm_in);
+static bool AdjustFractDays(double frac, int scale,
+ struct pg_itm_in *itm_in);
+static bool AdjustFractYears(double frac, int scale,
+ struct pg_itm_in *itm_in);
+static bool AdjustMicroseconds(int64 val, double fval, int64 scale,
+ struct pg_itm_in *itm_in);
+static bool AdjustDays(int64 val, int scale,
+ struct pg_itm_in *itm_in);
+static bool AdjustMonths(int64 val, struct pg_itm_in *itm_in);
+static bool AdjustYears(int64 val, int scale,
+ struct pg_itm_in *itm_in);
static int DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
pg_time_t *tp);
static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
* Returns a pointer to the new end of string. No NUL terminator is put
* there; callers are responsible for NUL terminating str themselves.
*
- * Note that any sign is stripped from the input seconds values.
+ * Note that any sign is stripped from the input sec and fsec values.
*/
static char *
AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
/*
* If we still have a non-zero value then precision must have not been
- * enough to print the number. We punt the problem to pg_ltostr(),
+ * enough to print the number. We punt the problem to pg_ultostr(),
* which will generate a correct answer in the minimum valid width.
*/
if (value)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
+
+/*
+ * Add val * multiplier to *sum.
+ * Returns true if successful, false on overflow.
+ */
+static bool
+int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ if (pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum))
+ return false;
+ return true;
+}
+
/*
- * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
- * We assume the input frac is less than 1 so overflow is not an issue.
+ * Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
*/
-static void
-AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+static bool
+AdjustFractMicroseconds(double frac, int64 scale,
+ struct pg_itm_in *itm_in)
{
- int sec;
+ int64 usec;
+ /* Fast path for common case */
if (frac == 0)
- return;
+ return true;
+
+ /*
+ * We assume the input frac has abs value less than 1, so overflow of frac
+ * or usec is not an issue for interesting values of scale.
+ */
frac *= scale;
- sec = (int) frac;
- tm->tm_sec += sec;
- frac -= sec;
- *fsec += rint(frac * 1000000);
+ usec = (int64) frac;
+
+ /* Round off any fractional microsecond */
+ frac -= usec;
+ if (frac > 0.5)
+ usec++;
+ else if (frac < -0.5)
+ usec--;
+
+ return !pg_add_s64_overflow(itm_in->tm_usec, usec, &itm_in->tm_usec);
}
-/* As above, but initial scale produces days */
-static void
-AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
+/*
+ * Multiply frac by scale (to produce days). Add the integral part of the
+ * result to itm_in->tm_mday, the fractional part to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustFractDays(double frac, int scale,
+ struct pg_itm_in *itm_in)
{
int extra_days;
+ /* Fast path for common case */
if (frac == 0)
- return;
+ return true;
+
+ /*
+ * We assume the input frac has abs value less than 1, so overflow of frac
+ * or extra_days is not an issue.
+ */
frac *= scale;
extra_days = (int) frac;
- tm->tm_mday += extra_days;
+
+ /* ... but this could overflow, if tm_mday is already nonzero */
+ if (pg_add_s32_overflow(itm_in->tm_mday, extra_days, &itm_in->tm_mday))
+ return false;
+
+ /* Handle any fractional day */
frac -= extra_days;
- AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
+ return AdjustFractMicroseconds(frac, USECS_PER_DAY, itm_in);
+}
+
+/*
+ * Multiply frac by scale (to produce years), then further scale up to months.
+ * Add the integral part of the result to itm_in->tm_mon, discarding any
+ * fractional part.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustFractYears(double frac, int scale,
+ struct pg_itm_in *itm_in)
+{
+ /*
+ * As above, we assume abs(frac) < 1, so this can't overflow for any
+ * interesting value of scale.
+ */
+ int extra_months = (int) rint(frac * scale * MONTHS_PER_YEAR);
+
+ return !pg_add_s32_overflow(itm_in->tm_mon, extra_months, &itm_in->tm_mon);
+}
+
+/*
+ * Add (val + fval) * scale to itm_in->tm_usec.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustMicroseconds(int64 val, double fval, int64 scale,
+ struct pg_itm_in *itm_in)
+{
+ /* Handle the integer part */
+ if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ return false;
+ /* Handle the float part */
+ return AdjustFractMicroseconds(fval, scale, itm_in);
+}
+
+/*
+ * Multiply val by scale (to produce days) and add to itm_in->tm_mday.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustDays(int64 val, int scale, struct pg_itm_in *itm_in)
+{
+ int days;
+
+ if (val < INT_MIN || val > INT_MAX)
+ return false;
+ return !pg_mul_s32_overflow((int32) val, scale, &days) &&
+ !pg_add_s32_overflow(itm_in->tm_mday, days, &itm_in->tm_mday);
+}
+
+/*
+ * Add val to itm_in->tm_mon (no need for scale here, as val is always
+ * in months already).
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustMonths(int64 val, struct pg_itm_in *itm_in)
+{
+ if (val < INT_MIN || val > INT_MAX)
+ return false;
+ return !pg_add_s32_overflow(itm_in->tm_mon, (int32) val, &itm_in->tm_mon);
}
+/*
+ * Multiply val by scale (to produce years) and add to itm_in->tm_year.
+ * Returns true if successful, false if itm_in overflows.
+ */
+static bool
+AdjustYears(int64 val, int scale,
+ struct pg_itm_in *itm_in)
+{
+ int years;
+
+ if (val < INT_MIN || val > INT_MAX)
+ return false;
+ return !pg_mul_s32_overflow((int32) val, scale, &years) &&
+ !pg_add_s32_overflow(itm_in->tm_year, years, &itm_in->tm_year);
+}
+
+
/* Fetch a fractional-second value with suitable error checking */
static int
ParseFractionalSecond(char *cp, fsec_t *fsec)
}
-/* DecodeTime()
+/* DecodeTimeCommon()
* Decode time string which includes delimiters.
* Return 0 if okay, a DTERR code if not.
+ * tmask and itm are output parameters.
*
- * Only check the lower limit on hours, since this same code can be
- * used to represent time spans.
+ * This code is shared between the timestamp and interval cases.
+ * We return a struct pg_itm (of which only the tm_usec, tm_sec, tm_min,
+ * and tm_hour fields are used) and let the wrapper functions below
+ * convert and range-check as necessary.
*/
static int
-DecodeTime(char *str, int fmask, int range,
- int *tmask, struct pg_tm *tm, fsec_t *fsec)
+DecodeTimeCommon(char *str, int fmask, int range,
+ int *tmask, struct pg_itm *itm)
{
char *cp;
int dterr;
+ fsec_t fsec = 0;
*tmask = DTK_TIME_M;
errno = 0;
- tm->tm_hour = strtoint(str, &cp, 10);
+ itm->tm_hour = strtoi64(str, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp != ':')
return DTERR_BAD_FORMAT;
errno = 0;
- tm->tm_min = strtoint(cp + 1, &cp, 10);
+ itm->tm_min = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
if (*cp == '\0')
{
- tm->tm_sec = 0;
- *fsec = 0;
+ itm->tm_sec = 0;
/* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
{
- tm->tm_sec = tm->tm_min;
- tm->tm_min = tm->tm_hour;
- tm->tm_hour = 0;
+ if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
+ return DTERR_FIELD_OVERFLOW;
+ itm->tm_sec = itm->tm_min;
+ itm->tm_min = (int) itm->tm_hour;
+ itm->tm_hour = 0;
}
}
else if (*cp == '.')
{
/* always assume mm:ss.sss is MINUTE TO SECOND */
- dterr = ParseFractionalSecond(cp, fsec);
+ dterr = ParseFractionalSecond(cp, &fsec);
if (dterr)
return dterr;
- tm->tm_sec = tm->tm_min;
- tm->tm_min = tm->tm_hour;
- tm->tm_hour = 0;
+ if (itm->tm_hour > INT_MAX || itm->tm_hour < INT_MIN)
+ return DTERR_FIELD_OVERFLOW;
+ itm->tm_sec = itm->tm_min;
+ itm->tm_min = (int) itm->tm_hour;
+ itm->tm_hour = 0;
}
else if (*cp == ':')
{
errno = 0;
- tm->tm_sec = strtoint(cp + 1, &cp, 10);
+ itm->tm_sec = strtoint(cp + 1, &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
- if (*cp == '\0')
- *fsec = 0;
- else if (*cp == '.')
+ if (*cp == '.')
{
- dterr = ParseFractionalSecond(cp, fsec);
+ dterr = ParseFractionalSecond(cp, &fsec);
if (dterr)
return dterr;
}
- else
+ else if (*cp != '\0')
return DTERR_BAD_FORMAT;
}
else
return DTERR_BAD_FORMAT;
- /* do a sanity check */
- if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
- tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
- *fsec < INT64CONST(0) ||
- *fsec > USECS_PER_SEC)
+ /* do a sanity check; but caller must check the range of tm_hour */
+ if (itm->tm_hour < 0 ||
+ itm->tm_min < 0 || itm->tm_min > MINS_PER_HOUR - 1 ||
+ itm->tm_sec < 0 || itm->tm_sec > SECS_PER_MINUTE ||
+ fsec < 0 || fsec > USECS_PER_SEC)
+ return DTERR_FIELD_OVERFLOW;
+
+ itm->tm_usec = (int) fsec;
+
+ return 0;
+}
+
+/* DecodeTime()
+ * Decode time string which includes delimiters.
+ * Return 0 if okay, a DTERR code if not.
+ *
+ * This version is used for timestamps. The results are returned into
+ * the tm_hour/tm_min/tm_sec fields of *tm, and microseconds into *fsec.
+ */
+static int
+DecodeTime(char *str, int fmask, int range,
+ int *tmask, struct pg_tm *tm, fsec_t *fsec)
+{
+ struct pg_itm itm;
+ int dterr;
+
+ dterr = DecodeTimeCommon(str, fmask, range,
+ tmask, &itm);
+ if (dterr)
+ return dterr;
+
+ if (itm.tm_hour > INT_MAX)
+ return DTERR_FIELD_OVERFLOW;
+ tm->tm_hour = (int) itm.tm_hour;
+ tm->tm_min = itm.tm_min;
+ tm->tm_sec = itm.tm_sec;
+ *fsec = itm.tm_usec;
+
+ return 0;
+}
+
+/* DecodeTimeForInterval()
+ * Decode time string which includes delimiters.
+ * Return 0 if okay, a DTERR code if not.
+ *
+ * This version is used for intervals. The results are returned into
+ * itm_in->tm_usec.
+ */
+static int
+DecodeTimeForInterval(char *str, int fmask, int range,
+ int *tmask, struct pg_itm_in *itm_in)
+{
+ struct pg_itm itm;
+ int dterr;
+
+ dterr = DecodeTimeCommon(str, fmask, range,
+ tmask, &itm);
+ if (dterr)
+ return dterr;
+
+ itm_in->tm_usec = itm.tm_usec;
+ if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
}
-/* ClearPgTm
+/* ClearPgItmIn
*
- * Zero out a pg_tm and associated fsec_t
+ * Zero out a pg_itm_in
*/
static inline void
-ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
+ClearPgItmIn(struct pg_itm_in *itm_in)
{
- tm->tm_year = 0;
- tm->tm_mon = 0;
- tm->tm_mday = 0;
- tm->tm_hour = 0;
- tm->tm_min = 0;
- tm->tm_sec = 0;
- *fsec = 0;
+ itm_in->tm_usec = 0;
+ itm_in->tm_mday = 0;
+ itm_in->tm_mon = 0;
+ itm_in->tm_year = 0;
}
/* DecodeInterval()
* Interpret previously parsed fields for general time interval.
* Returns 0 if successful, DTERR code if bogus input detected.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
*
* Allow "date" field DTK_DATE since this could be just
* an unsigned floating point number. - thomas 1997-11-16
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
- int *dtype, struct pg_tm *tm, fsec_t *fsec)
+ int *dtype, struct pg_itm_in *itm_in)
{
+ bool force_negative = false;
bool is_before = false;
char *cp;
int fmask = 0,
tmask,
- type;
+ type,
+ uval;
int i;
int dterr;
- int val;
+ int64 val;
double fval;
*dtype = DTK_DELTA;
type = IGNORE_DTF;
- ClearPgTm(tm, fsec);
+ ClearPgItmIn(itm_in);
+
+ /*----------
+ * The SQL standard defines the interval literal
+ * '-1 1:00:00'
+ * to mean "negative 1 days and negative 1 hours", while Postgres
+ * traditionally treats this as meaning "negative 1 days and positive
+ * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign
+ * to all fields if there are no other explicit signs.
+ *
+ * We leave the signs alone if there are additional explicit signs.
+ * This protects us against misinterpreting postgres-style dump output,
+ * since the postgres-style output code has always put an explicit sign on
+ * all fields following a negative field. But note that SQL-spec output
+ * is ambiguous and can be misinterpreted on load! (So it's best practice
+ * to dump in postgres style, not SQL style.)
+ *----------
+ */
+ if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
+ {
+ force_negative = true;
+ /* Check for additional explicit signs */
+ for (i = 1; i < nf; i++)
+ {
+ if (*field[i] == '-' || *field[i] == '+')
+ {
+ force_negative = false;
+ break;
+ }
+ }
+ }
/* read through list backwards to pick up units before values */
for (i = nf - 1; i >= 0; i--)
switch (ftype[i])
{
case DTK_TIME:
- dterr = DecodeTime(field[i], fmask, range,
- &tmask, tm, fsec);
+ dterr = DecodeTimeForInterval(field[i], fmask, range,
+ &tmask, itm_in);
if (dterr)
return dterr;
+ if (force_negative &&
+ itm_in->tm_usec > 0)
+ itm_in->tm_usec = -itm_in->tm_usec;
type = DTK_DAY;
break;
* like DTK_TIME case above, plus handling the sign.
*/
if (strchr(field[i] + 1, ':') != NULL &&
- DecodeTime(field[i] + 1, fmask, range,
- &tmask, tm, fsec) == 0)
+ DecodeTimeForInterval(field[i] + 1, fmask, range,
+ &tmask, itm_in) == 0)
{
if (*field[i] == '-')
{
- /* flip the sign on all fields */
- tm->tm_hour = -tm->tm_hour;
- tm->tm_min = -tm->tm_min;
- tm->tm_sec = -tm->tm_sec;
- *fsec = -(*fsec);
+ /* flip the sign on time field */
+ if (itm_in->tm_usec == PG_INT64_MIN)
+ return DTERR_FIELD_OVERFLOW;
+ itm_in->tm_usec = -itm_in->tm_usec;
}
+ if (force_negative &&
+ itm_in->tm_usec > 0)
+ itm_in->tm_usec = -itm_in->tm_usec;
+
/*
* Set the next type to be a day, if units are not
* specified. This handles the case of '1 +02:03' since we
}
errno = 0;
- val = strtoint(field[i], &cp, 10);
+ val = strtoi64(field[i], &cp, 10);
if (errno == ERANGE)
return DTERR_FIELD_OVERFLOW;
type = DTK_MONTH;
if (*field[i] == '-')
val2 = -val2;
- if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
- ((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
+ if (pg_mul_s64_overflow(val, MONTHS_PER_YEAR, &val))
+ return DTERR_FIELD_OVERFLOW;
+ if (pg_add_s64_overflow(val, val2, &val))
return DTERR_FIELD_OVERFLOW;
- val = val * MONTHS_PER_YEAR + val2;
fval = 0;
}
else if (*cp == '.')
tmask = 0; /* DTK_M(type); */
+ if (force_negative)
+ {
+ /* val and fval should be of same sign, but test anyway */
+ if (val > 0)
+ val = -val;
+ if (fval > 0)
+ fval = -fval;
+ }
+
switch (type)
{
case DTK_MICROSEC:
- *fsec += rint(val + fval);
+ if (!AdjustMicroseconds(val, fval, 1, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MICROSECOND);
break;
case DTK_MILLISEC:
- /* avoid overflowing the fsec field */
- tm->tm_sec += val / 1000;
- val -= (val / 1000) * 1000;
- *fsec += rint((val + fval) * 1000);
+ if (!AdjustMicroseconds(val, fval, 1000, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MILLISECOND);
break;
case DTK_SECOND:
- tm->tm_sec += val;
- *fsec += rint(fval * 1000000);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+ return DTERR_FIELD_OVERFLOW;
/*
* If any subseconds were specified, consider this
break;
case DTK_MINUTE:
- tm->tm_min += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MINUTE);
break;
case DTK_HOUR:
- tm->tm_hour += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(HOUR);
type = DTK_DAY; /* set for next field */
break;
case DTK_DAY:
- tm->tm_mday += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+ if (!AdjustDays(val, 1, itm_in) ||
+ !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(DAY);
break;
case DTK_WEEK:
- tm->tm_mday += val * 7;
- AdjustFractDays(fval, tm, fsec, 7);
+ if (!AdjustDays(val, 7, itm_in) ||
+ !AdjustFractDays(fval, 7, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(WEEK);
break;
case DTK_MONTH:
- tm->tm_mon += val;
- AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+ if (!AdjustMonths(val, itm_in) ||
+ !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MONTH);
break;
case DTK_YEAR:
- tm->tm_year += val;
- tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+ if (!AdjustYears(val, 1, itm_in) ||
+ !AdjustFractYears(fval, 1, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(YEAR);
break;
case DTK_DECADE:
- tm->tm_year += val * 10;
- tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 10);
+ if (!AdjustYears(val, 10, itm_in) ||
+ !AdjustFractYears(fval, 10, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(DECADE);
break;
case DTK_CENTURY:
- tm->tm_year += val * 100;
- tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 100);
+ if (!AdjustYears(val, 100, itm_in) ||
+ !AdjustFractYears(fval, 100, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(CENTURY);
break;
case DTK_MILLENNIUM:
- tm->tm_year += val * 1000;
- tm->tm_mon += rint(fval * MONTHS_PER_YEAR * 1000);
+ if (!AdjustYears(val, 1000, itm_in) ||
+ !AdjustFractYears(fval, 1000, itm_in))
+ return DTERR_FIELD_OVERFLOW;
tmask = DTK_M(MILLENNIUM);
break;
case DTK_STRING:
case DTK_SPECIAL:
- type = DecodeUnits(i, field[i], &val);
+ type = DecodeUnits(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
switch (type)
{
case UNITS:
- type = val;
+ type = uval;
break;
case AGO:
is_before = true;
- type = val;
+ type = uval;
break;
case RESERV:
tmask = (DTK_DATE_M | DTK_TIME_M);
- *dtype = val;
+ *dtype = uval;
break;
default:
if (fmask == 0)
return DTERR_BAD_FORMAT;
- /* ensure fractional seconds are fractional */
- if (*fsec != 0)
- {
- int sec;
-
- sec = *fsec / USECS_PER_SEC;
- *fsec -= sec * USECS_PER_SEC;
- tm->tm_sec += sec;
- }
-
- /*----------
- * The SQL standard defines the interval literal
- * '-1 1:00:00'
- * to mean "negative 1 days and negative 1 hours", while Postgres
- * traditionally treats this as meaning "negative 1 days and positive
- * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign
- * to all fields if there are no other explicit signs.
- *
- * We leave the signs alone if there are additional explicit signs.
- * This protects us against misinterpreting postgres-style dump output,
- * since the postgres-style output code has always put an explicit sign on
- * all fields following a negative field. But note that SQL-spec output
- * is ambiguous and can be misinterpreted on load! (So it's best practice
- * to dump in postgres style, not SQL style.)
- *----------
- */
- if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
- {
- /* Check for additional explicit signs */
- bool more_signs = false;
-
- for (i = 1; i < nf; i++)
- {
- if (*field[i] == '-' || *field[i] == '+')
- {
- more_signs = true;
- break;
- }
- }
-
- if (!more_signs)
- {
- /*
- * Rather than re-determining which field was field[0], just force
- * 'em all negative.
- */
- if (*fsec > 0)
- *fsec = -(*fsec);
- if (tm->tm_sec > 0)
- tm->tm_sec = -tm->tm_sec;
- if (tm->tm_min > 0)
- tm->tm_min = -tm->tm_min;
- if (tm->tm_hour > 0)
- tm->tm_hour = -tm->tm_hour;
- if (tm->tm_mday > 0)
- tm->tm_mday = -tm->tm_mday;
- if (tm->tm_mon > 0)
- tm->tm_mon = -tm->tm_mon;
- if (tm->tm_year > 0)
- tm->tm_year = -tm->tm_year;
- }
- }
-
/* finally, AGO negates everything */
if (is_before)
{
- *fsec = -(*fsec);
- tm->tm_sec = -tm->tm_sec;
- tm->tm_min = -tm->tm_min;
- tm->tm_hour = -tm->tm_hour;
- tm->tm_mday = -tm->tm_mday;
- tm->tm_mon = -tm->tm_mon;
- tm->tm_year = -tm->tm_year;
+ if (itm_in->tm_usec == PG_INT64_MIN ||
+ itm_in->tm_mday == INT_MIN ||
+ itm_in->tm_mon == INT_MIN ||
+ itm_in->tm_year == INT_MIN)
+ return DTERR_FIELD_OVERFLOW;
+
+ itm_in->tm_usec = -itm_in->tm_usec;
+ itm_in->tm_mday = -itm_in->tm_mday;
+ itm_in->tm_mon = -itm_in->tm_mon;
+ itm_in->tm_year = -itm_in->tm_year;
}
return 0;
* Returns 0 or DTERR code.
*/
static int
-ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
+ParseISO8601Number(char *str, char **endptr, int64 *ipart, double *fpart)
{
- double val;
+ int sign = 1;
- if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
- return DTERR_BAD_FORMAT;
+ *ipart = 0;
+ *fpart = 0.0;
+
+ /* Parse sign if there is any */
+ if (*str == '-')
+ {
+ sign = -1;
+ str++;
+ }
+
+ *endptr = str;
errno = 0;
- val = strtod(str, endptr);
- /* did we not see anything that looks like a double? */
+
+ /* Parse int64 part if there is any */
+ if (isdigit((unsigned char) **endptr))
+ *ipart = strtoi64(*endptr, endptr, 10) * sign;
+
+ /* Parse fractional part if there is any */
+ if (**endptr == '.')
+ *fpart = strtod(*endptr, endptr) * sign;
+
+ /* did we not see anything that looks like a number? */
if (*endptr == str || errno != 0)
return DTERR_BAD_FORMAT;
- /* watch out for overflow */
- if (val < INT_MIN || val > INT_MAX)
- return DTERR_FIELD_OVERFLOW;
- /* be very sure we truncate towards zero (cf dtrunc()) */
- if (val >= 0)
- *ipart = (int) floor(val);
- else
- *ipart = (int) -floor(-val);
- *fpart = val - *ipart;
+
return 0;
}
* Returns 0 if successful, DTERR code if bogus input detected.
* Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
* ISO8601, otherwise this could cause unexpected error messages.
- * dtype, tm, fsec are output parameters.
+ * dtype and itm_in are output parameters.
*
* A couple exceptions from the spec:
* - a week field ('W') may coexist with other units
*/
int
DecodeISO8601Interval(char *str,
- int *dtype, struct pg_tm *tm, fsec_t *fsec)
+ int *dtype, struct pg_itm_in *itm_in)
{
bool datepart = true;
bool havefield = false;
*dtype = DTK_DELTA;
- ClearPgTm(tm, fsec);
+ ClearPgItmIn(itm_in);
if (strlen(str) < 2 || str[0] != 'P')
return DTERR_BAD_FORMAT;
while (*str)
{
char *fieldstart;
- int val;
+ int64 val;
double fval;
char unit;
int dterr;
switch (unit) /* before T: Y M W D */
{
case 'Y':
- tm->tm_year += val;
- tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+ if (!AdjustYears(val, 1, itm_in) ||
+ !AdjustFractYears(fval, 1, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case 'M':
- tm->tm_mon += val;
- AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+ if (!AdjustMonths(val, itm_in) ||
+ !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case 'W':
- tm->tm_mday += val * 7;
- AdjustFractDays(fval, tm, fsec, 7);
+ if (!AdjustDays(val, 7, itm_in) ||
+ !AdjustFractDays(fval, 7, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case 'D':
- tm->tm_mday += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+ if (!AdjustDays(val, 1, itm_in) ||
+ !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case 'T': /* ISO 8601 4.4.3.3 Alternative Format / Basic */
case '\0':
if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
{
- tm->tm_year += val / 10000;
- tm->tm_mon += (val / 100) % 100;
- tm->tm_mday += val % 100;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+ if (!AdjustYears(val / 10000, 1, itm_in) ||
+ !AdjustMonths((val / 100) % 100, itm_in) ||
+ !AdjustDays(val % 100, 1, itm_in) ||
+ !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (unit == '\0')
return 0;
datepart = false;
if (havefield)
return DTERR_BAD_FORMAT;
- tm->tm_year += val;
- tm->tm_mon += rint(fval * MONTHS_PER_YEAR);
+ if (!AdjustYears(val, 1, itm_in) ||
+ !AdjustFractYears(fval, 1, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (unit == '\0')
return 0;
if (unit == 'T')
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
- tm->tm_mon += val;
- AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
+ if (!AdjustMonths(val, itm_in) ||
+ !AdjustFractDays(fval, DAYS_PER_MONTH, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
if (*str == 'T')
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
- tm->tm_mday += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
+ if (!AdjustDays(val, 1, itm_in) ||
+ !AdjustFractMicroseconds(fval, USECS_PER_DAY, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
if (*str == 'T')
switch (unit) /* after T: H M S */
{
case 'H':
- tm->tm_hour += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case 'M':
- tm->tm_min += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case 'S':
- tm->tm_sec += val;
- AdjustFractSeconds(fval, tm, fsec, 1);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+ return DTERR_FIELD_OVERFLOW;
break;
case '\0': /* ISO 8601 4.4.3.3 Alternative Format */
if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
{
- tm->tm_hour += val / 10000;
- tm->tm_min += (val / 100) % 100;
- tm->tm_sec += val % 100;
- AdjustFractSeconds(fval, tm, fsec, 1);
+ if (!AdjustMicroseconds(val / 10000, 0, USECS_PER_HOUR, itm_in) ||
+ !AdjustMicroseconds((val / 100) % 100, 0, USECS_PER_MINUTE, itm_in) ||
+ !AdjustMicroseconds(val % 100, 0, USECS_PER_SEC, itm_in) ||
+ !AdjustFractMicroseconds(fval, 1, itm_in))
+ return DTERR_FIELD_OVERFLOW;
return 0;
}
/* Else fall through to extended alternative format */
if (havefield)
return DTERR_BAD_FORMAT;
- tm->tm_hour += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_HOUR, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (unit == '\0')
return 0;
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
- tm->tm_min += val;
- AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_MINUTE, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
if (*str != ':')
dterr = ParseISO8601Number(str, &str, &val, &fval);
if (dterr)
return dterr;
- tm->tm_sec += val;
- AdjustFractSeconds(fval, tm, fsec, 1);
+ if (!AdjustMicroseconds(val, fval, USECS_PER_SEC, itm_in))
+ return DTERR_FIELD_OVERFLOW;
if (*str == '\0')
return 0;
return DTERR_BAD_FORMAT;
/* Append an ISO-8601-style interval field, but only if value isn't zero */
static char *
-AddISO8601IntPart(char *cp, int value, char units)
+AddISO8601IntPart(char *cp, int64 value, char units)
{
if (value == 0)
return cp;
- sprintf(cp, "%d%c", value, units);
+ sprintf(cp, "%lld%c", (long long) value, units);
return cp + strlen(cp);
}
/* Append a postgres-style interval field, but only if value isn't zero */
static char *
-AddPostgresIntPart(char *cp, int value, const char *units,
+AddPostgresIntPart(char *cp, int64 value, const char *units,
bool *is_zero, bool *is_before)
{
if (value == 0)
return cp;
- sprintf(cp, "%s%s%d %s%s",
+ sprintf(cp, "%s%s%lld %s%s",
(!*is_zero) ? " " : "",
(*is_before && value > 0) ? "+" : "",
- value,
+ (long long) value,
units,
(value != 1) ? "s" : "");
/* Append a verbose-style interval field, but only if value isn't zero */
static char *
-AddVerboseIntPart(char *cp, int value, const char *units,
+AddVerboseIntPart(char *cp, int64 value, const char *units,
bool *is_zero, bool *is_before)
{
if (value == 0)
if (*is_zero)
{
*is_before = (value < 0);
- value = abs(value);
+ value = Abs(value);
}
else if (*is_before)
value = -value;
- sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
+ sprintf(cp, " %lld %s%s", (long long) value, units, (value == 1) ? "" : "s");
*is_zero = false;
return cp + strlen(cp);
}
* "day-time literal"s (that look like ('4 5:6:7')
*/
void
-EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
+EncodeInterval(struct pg_itm *itm, int style, char *str)
{
char *cp = str;
- int year = tm->tm_year;
- int mon = tm->tm_mon;
- int mday = tm->tm_mday;
- int hour = tm->tm_hour;
- int min = tm->tm_min;
- int sec = tm->tm_sec;
+ int year = itm->tm_year;
+ int mon = itm->tm_mon;
+ int64 mday = itm->tm_mday; /* tm_mday could be INT_MIN */
+ int64 hour = itm->tm_hour;
+ int min = itm->tm_min;
+ int sec = itm->tm_sec;
+ int fsec = itm->tm_usec;
bool is_before = false;
bool is_zero = true;
char sec_sign = (hour < 0 || min < 0 ||
sec < 0 || fsec < 0) ? '-' : '+';
- sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
+ sprintf(cp, "%c%d-%d %c%lld %c%lld:%02d:",
year_sign, abs(year), abs(mon),
- day_sign, abs(mday),
- sec_sign, abs(hour), abs(min));
+ day_sign, (long long) Abs(mday),
+ sec_sign, (long long) Abs(hour), abs(min));
cp += strlen(cp);
cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
}
else if (has_day)
{
- sprintf(cp, "%d %d:%02d:", mday, hour, min);
+ sprintf(cp, "%lld %lld:%02d:",
+ (long long) mday, (long long) hour, min);
cp += strlen(cp);
cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
}
else
{
- sprintf(cp, "%d:%02d:", hour, min);
+ sprintf(cp, "%lld:%02d:", (long long) hour, min);
cp += strlen(cp);
cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
{
bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
- sprintf(cp, "%s%s%02d:%02d:",
+ sprintf(cp, "%s%s%02lld:%02d:",
is_zero ? "" : " ",
(minus ? "-" : (is_before ? "+" : "")),
- abs(hour), abs(min));
+ (long long) Abs(hour), abs(min));
cp += strlen(cp);
cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
*cp = '\0';
int gmtoffset;
bool is_dst;
unsigned char *p;
- struct pg_tm tm;
+ struct pg_itm_in itm_in;
Interval *resInterval;
/* stuff done only on the first call of the function */
values[0] = CStringGetTextDatum(buffer);
- /* Convert offset (in seconds) to an interval */
- MemSet(&tm, 0, sizeof(struct pg_tm));
- tm.tm_sec = gmtoffset;
+ /* Convert offset (in seconds) to an interval; can't overflow */
+ MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+ itm_in.tm_usec = (int64) gmtoffset * USECS_PER_SEC;
resInterval = (Interval *) palloc(sizeof(Interval));
- tm2interval(&tm, 0, resInterval);
+ (void) itmin2interval(&itm_in, resInterval);
values[1] = IntervalPGetDatum(resInterval);
values[2] = BoolGetDatum(is_dst);
fsec_t fsec;
const char *tzn;
Interval *resInterval;
- struct pg_tm itm;
+ struct pg_itm_in itm_in;
SetSingleFuncCall(fcinfo, 0);
values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
values[1] = CStringGetTextDatum(tzn ? tzn : "");
- MemSet(&itm, 0, sizeof(struct pg_tm));
- itm.tm_sec = -tzoff;
+ /* Convert tzoff to an interval; can't overflow */
+ MemSet(&itm_in, 0, sizeof(struct pg_itm_in));
+ itm_in.tm_usec = (int64) -tzoff * USECS_PER_SEC;
resInterval = (Interval *) palloc(sizeof(Interval));
- tm2interval(&itm, 0, resInterval);
+ (void) itmin2interval(&itm_in, resInterval);
values[2] = IntervalPGetDatum(resInterval);
values[3] = BoolGetDatum(tm.tm_isdst > 0);
/* ----------
* Datetime to char conversion
+ *
+ * To support intervals as well as timestamps, we use a custom "tm" struct
+ * that is almost like struct pg_tm, but has a 64-bit tm_hour field.
+ * We omit the tm_isdst and tm_zone fields, which are not used here.
* ----------
*/
+struct fmt_tm
+{
+ int tm_sec;
+ int tm_min;
+ int64 tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+ int tm_wday;
+ int tm_yday;
+ long int tm_gmtoff;
+};
+
typedef struct TmToChar
{
- struct pg_tm tm; /* classic 'tm' struct */
+ struct fmt_tm tm; /* almost the classic 'tm' struct */
fsec_t fsec; /* fractional seconds */
const char *tzn; /* timezone */
} TmToChar;
#define tmtcTzn(_X) ((_X)->tzn)
#define tmtcFsec(_X) ((_X)->fsec)
+/* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
+#define COPY_tm(_DST, _SRC) \
+do { \
+ (_DST)->tm_sec = (_SRC)->tm_sec; \
+ (_DST)->tm_min = (_SRC)->tm_min; \
+ (_DST)->tm_hour = (_SRC)->tm_hour; \
+ (_DST)->tm_mday = (_SRC)->tm_mday; \
+ (_DST)->tm_mon = (_SRC)->tm_mon; \
+ (_DST)->tm_year = (_SRC)->tm_year; \
+ (_DST)->tm_wday = (_SRC)->tm_wday; \
+ (_DST)->tm_yday = (_SRC)->tm_yday; \
+ (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
+} while(0)
+
+/* Caution: this is used to zero both pg_tm and fmt_tm structs */
#define ZERO_tm(_X) \
do { \
- (_X)->tm_sec = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \
- (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \
- (_X)->tm_mday = (_X)->tm_mon = 1; \
- (_X)->tm_zone = NULL; \
+ memset(_X, 0, sizeof(*(_X))); \
+ (_X)->tm_mday = (_X)->tm_mon = 1; \
} while(0)
#define ZERO_tmtc(_X) \
{
FormatNode *n;
char *s;
- struct pg_tm *tm = &in->tm;
+ struct fmt_tm *tm = &in->tm;
int i;
/* cache localized days and months */
* display time as shown on a 12-hour clock, even for
* intervals
*/
- sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
- tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 :
- tm->tm_hour % (HOURS_PER_DAY / 2));
+ sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+ tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
+ (long long) (HOURS_PER_DAY / 2) :
+ (long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
case DCH_HH24:
- sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
- tm->tm_hour);
+ sprintf(s, "%0*lld", S_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
+ (long long) tm->tm_hour);
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
break;
#undef DCH_to_char_fsec
case DCH_SSSS:
- sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR +
- tm->tm_min * SECS_PER_MINUTE +
- tm->tm_sec);
+ sprintf(s, "%lld",
+ (long long) (tm->tm_hour * SECS_PER_HOUR +
+ tm->tm_min * SECS_PER_MINUTE +
+ tm->tm_sec));
if (S_THth(n->suffix))
str_numth(s, s, S_TH_TYPE(n->suffix));
s += strlen(s);
text *fmt = PG_GETARG_TEXT_PP(1),
*res;
TmToChar tmtc;
- struct pg_tm *tm;
+ struct pg_tm tt;
+ struct fmt_tm *tm;
int thisdate;
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
- if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0)
+ if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ COPY_tm(tm, &tt);
thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = (thisdate + 1) % 7;
*res;
TmToChar tmtc;
int tz;
- struct pg_tm *tm;
+ struct pg_tm tt;
+ struct fmt_tm *tm;
int thisdate;
if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
- if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
+ if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
+ COPY_tm(tm, &tt);
thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
tm->tm_wday = (thisdate + 1) % 7;
text *fmt = PG_GETARG_TEXT_PP(1),
*res;
TmToChar tmtc;
- struct pg_tm *tm;
+ struct fmt_tm *tm;
+ struct pg_itm tt,
+ *itm = &tt;
if (VARSIZE_ANY_EXHDR(fmt) <= 0)
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
tm = tmtcTm(&tmtc);
- if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0)
- PG_RETURN_NULL();
+ interval2itm(*it, itm);
+ tmtc.fsec = itm->tm_usec;
+ tm->tm_sec = itm->tm_sec;
+ tm->tm_min = itm->tm_min;
+ tm->tm_hour = itm->tm_hour;
+ tm->tm_mday = itm->tm_mday;
+ tm->tm_mon = itm->tm_mon;
+ tm->tm_year = itm->tm_year;
/* wday is meaningless, yday approximates the total span in days */
tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
#endif
int32 typmod = PG_GETARG_INT32(2);
Interval *result;
- fsec_t fsec;
- struct pg_tm tt,
- *tm = &tt;
+ struct pg_itm_in tt,
+ *itm_in = &tt;
int dtype;
int nf;
int range;
int ftype[MAXDATEFIELDS];
char workbuf[256];
- tm->tm_year = 0;
- tm->tm_mon = 0;
- tm->tm_mday = 0;
- tm->tm_hour = 0;
- tm->tm_min = 0;
- tm->tm_sec = 0;
- fsec = 0;
+ itm_in->tm_year = 0;
+ itm_in->tm_mon = 0;
+ itm_in->tm_mday = 0;
+ itm_in->tm_usec = 0;
if (typmod >= 0)
range = INTERVAL_RANGE(typmod);
ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeInterval(field, ftype, nf, range,
- &dtype, tm, &fsec);
+ &dtype, itm_in);
/* if those functions think it's a bad format, try ISO8601 style */
if (dterr == DTERR_BAD_FORMAT)
dterr = DecodeISO8601Interval(str,
- &dtype, tm, &fsec);
+ &dtype, itm_in);
if (dterr != 0)
{
switch (dtype)
{
case DTK_DELTA:
- if (tm2interval(tm, fsec, result) != 0)
+ if (itmin2interval(itm_in, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
{
Interval *span = PG_GETARG_INTERVAL_P(0);
char *result;
- struct pg_tm tt,
- *tm = &tt;
- fsec_t fsec;
+ struct pg_itm tt,
+ *itm = &tt;
char buf[MAXDATELEN + 1];
- if (interval2tm(*span, tm, &fsec) != 0)
- elog(ERROR, "could not convert interval to tm");
-
- EncodeInterval(tm, fsec, IntervalStyle, buf);
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
}
-/* interval2tm()
- * Convert an interval data type to a tm structure.
+/* interval2itm()
+ * Convert an Interval to a pg_itm structure.
+ * Note: overflow is not possible, because the pg_itm fields are
+ * wide enough for all possible conversion results.
*/
-int
-interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec)
+void
+interval2itm(Interval span, struct pg_itm *itm)
{
TimeOffset time;
TimeOffset tfrac;
- tm->tm_year = span.month / MONTHS_PER_YEAR;
- tm->tm_mon = span.month % MONTHS_PER_YEAR;
- tm->tm_mday = span.day;
+ itm->tm_year = span.month / MONTHS_PER_YEAR;
+ itm->tm_mon = span.month % MONTHS_PER_YEAR;
+ itm->tm_mday = span.day;
time = span.time;
tfrac = time / USECS_PER_HOUR;
time -= tfrac * USECS_PER_HOUR;
- tm->tm_hour = tfrac;
- if (!SAMESIGN(tm->tm_hour, tfrac))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ itm->tm_hour = tfrac;
tfrac = time / USECS_PER_MINUTE;
time -= tfrac * USECS_PER_MINUTE;
- tm->tm_min = tfrac;
+ itm->tm_min = (int) tfrac;
tfrac = time / USECS_PER_SEC;
- *fsec = time - (tfrac * USECS_PER_SEC);
- tm->tm_sec = tfrac;
+ time -= tfrac * USECS_PER_SEC;
+ itm->tm_sec = (int) tfrac;
+ itm->tm_usec = (int) time;
+}
+
+/* itm2interval()
+ * Convert a pg_itm structure to an Interval.
+ * Returns 0 if OK, -1 on overflow.
+ */
+int
+itm2interval(struct pg_itm *itm, Interval *span)
+{
+ int64 total_months = (int64) itm->tm_year * MONTHS_PER_YEAR + itm->tm_mon;
+ if (total_months > INT_MAX || total_months < INT_MIN)
+ return -1;
+ span->month = (int32) total_months;
+ span->day = itm->tm_mday;
+ if (pg_mul_s64_overflow(itm->tm_hour, USECS_PER_HOUR,
+ &span->time))
+ return -1;
+ /* tm_min, tm_sec are 32 bits, so intermediate products can't overflow */
+ if (pg_add_s64_overflow(span->time, itm->tm_min * USECS_PER_MINUTE,
+ &span->time))
+ return -1;
+ if (pg_add_s64_overflow(span->time, itm->tm_sec * USECS_PER_SEC,
+ &span->time))
+ return -1;
+ if (pg_add_s64_overflow(span->time, itm->tm_usec,
+ &span->time))
+ return -1;
return 0;
}
+/* itmin2interval()
+ * Convert a pg_itm_in structure to an Interval.
+ * Returns 0 if OK, -1 on overflow.
+ */
int
-tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span)
+itmin2interval(struct pg_itm_in *itm_in, Interval *span)
{
- double total_months = (double) tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
+ int64 total_months = (int64) itm_in->tm_year * MONTHS_PER_YEAR + itm_in->tm_mon;
if (total_months > INT_MAX || total_months < INT_MIN)
return -1;
- span->month = total_months;
- span->day = tm->tm_mday;
- span->time = (((((tm->tm_hour * INT64CONST(60)) +
- tm->tm_min) * INT64CONST(60)) +
- tm->tm_sec) * USECS_PER_SEC) + fsec;
-
+ span->month = (int32) total_months;
+ span->day = itm_in->tm_mday;
+ span->time = itm_in->tm_usec;
return 0;
}
Timestamp dt1 = PG_GETARG_TIMESTAMP(0);
Timestamp dt2 = PG_GETARG_TIMESTAMP(1);
Interval *result;
- fsec_t fsec,
- fsec1,
+ fsec_t fsec1,
fsec2;
- struct pg_tm tt,
+ struct pg_itm tt,
*tm = &tt;
struct pg_tm tt1,
*tm1 = &tt1;
timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
- fsec = fsec1 - fsec2;
+ tm->tm_usec = fsec1 - fsec2;
tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
tm->tm_min = tm1->tm_min - tm2->tm_min;
tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
/* flip sign if necessary... */
if (dt1 < dt2)
{
- fsec = -fsec;
+ tm->tm_usec = -tm->tm_usec;
tm->tm_sec = -tm->tm_sec;
tm->tm_min = -tm->tm_min;
tm->tm_hour = -tm->tm_hour;
}
/* propagate any negative fields into the next higher field */
- while (fsec < 0)
+ while (tm->tm_usec < 0)
{
- fsec += USECS_PER_SEC;
+ tm->tm_usec += USECS_PER_SEC;
tm->tm_sec--;
}
/* recover sign if necessary... */
if (dt1 < dt2)
{
- fsec = -fsec;
+ tm->tm_usec = -tm->tm_usec;
tm->tm_sec = -tm->tm_sec;
tm->tm_min = -tm->tm_min;
tm->tm_hour = -tm->tm_hour;
tm->tm_year = -tm->tm_year;
}
- if (tm2interval(tm, fsec, result) != 0)
+ if (itm2interval(tm, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
TimestampTz dt1 = PG_GETARG_TIMESTAMPTZ(0);
TimestampTz dt2 = PG_GETARG_TIMESTAMPTZ(1);
Interval *result;
- fsec_t fsec,
- fsec1,
+ fsec_t fsec1,
fsec2;
- struct pg_tm tt,
+ struct pg_itm tt,
*tm = &tt;
struct pg_tm tt1,
*tm1 = &tt1;
timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
- fsec = fsec1 - fsec2;
+ tm->tm_usec = fsec1 - fsec2;
tm->tm_sec = tm1->tm_sec - tm2->tm_sec;
tm->tm_min = tm1->tm_min - tm2->tm_min;
tm->tm_hour = tm1->tm_hour - tm2->tm_hour;
/* flip sign if necessary... */
if (dt1 < dt2)
{
- fsec = -fsec;
+ tm->tm_usec = -tm->tm_usec;
tm->tm_sec = -tm->tm_sec;
tm->tm_min = -tm->tm_min;
tm->tm_hour = -tm->tm_hour;
}
/* propagate any negative fields into the next higher field */
- while (fsec < 0)
+ while (tm->tm_usec < 0)
{
- fsec += USECS_PER_SEC;
+ tm->tm_usec += USECS_PER_SEC;
tm->tm_sec--;
}
/* recover sign if necessary... */
if (dt1 < dt2)
{
- fsec = -fsec;
+ tm->tm_usec = -tm->tm_usec;
tm->tm_sec = -tm->tm_sec;
tm->tm_min = -tm->tm_min;
tm->tm_hour = -tm->tm_hour;
tm->tm_year = -tm->tm_year;
}
- if (tm2interval(tm, fsec, result) != 0)
+ if (itm2interval(tm, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
int type,
val;
char *lowunits;
- fsec_t fsec;
- struct pg_tm tt,
+ struct pg_itm tt,
*tm = &tt;
result = (Interval *) palloc(sizeof(Interval));
if (type == UNITS)
{
- if (interval2tm(*interval, tm, &fsec) == 0)
- {
+ interval2itm(*interval, tm);
switch (val)
{
case DTK_MILLENNIUM:
tm->tm_sec = 0;
/* FALL THRU */
case DTK_SECOND:
- fsec = 0;
+ tm->tm_usec = 0;
break;
case DTK_MILLISEC:
- fsec = (fsec / 1000) * 1000;
+ tm->tm_usec = (tm->tm_usec / 1000) * 1000;
break;
case DTK_MICROSEC:
break;
(val == DTK_WEEK) ? errdetail("Months usually have fractional weeks.") : 0));
}
- if (tm2interval(tm, fsec, result) != 0)
+ if (itm2interval(tm, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
- }
- else
- elog(ERROR, "could not convert interval to tm");
}
else
{
int type,
val;
char *lowunits;
- fsec_t fsec;
- struct pg_tm tt,
+ struct pg_itm tt,
*tm = &tt;
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
if (type == UNITS)
{
- if (interval2tm(*interval, tm, &fsec) == 0)
- {
+ interval2itm(*interval, tm);
switch (val)
{
case DTK_MICROSEC:
- intresult = tm->tm_sec * INT64CONST(1000000) + fsec;
+ intresult = tm->tm_sec * INT64CONST(1000000) + tm->tm_usec;
break;
case DTK_MILLISEC:
* tm->tm_sec * 1000 + fsec / 1000
* = (tm->tm_sec * 1'000'000 + fsec) / 1000
*/
- PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 3));
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 3));
else
- PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + fsec / 1000.0);
+ PG_RETURN_FLOAT8(tm->tm_sec * 1000.0 + tm->tm_usec / 1000.0);
break;
case DTK_SECOND:
* tm->tm_sec + fsec / 1'000'000
* = (tm->tm_sec * 1'000'000 + fsec) / 1'000'000
*/
- PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + fsec, 6));
+ PG_RETURN_NUMERIC(int64_div_fast_to_numeric(tm->tm_sec * INT64CONST(1000000) + tm->tm_usec, 6));
else
- PG_RETURN_FLOAT8(tm->tm_sec + fsec / 1000000.0);
+ PG_RETURN_FLOAT8(tm->tm_sec + tm->tm_usec / 1000000.0);
break;
case DTK_MINUTE:
lowunits, format_type_be(INTERVALOID))));
intresult = 0;
}
- }
- else
- {
- elog(ERROR, "could not convert interval to tm");
- intresult = 0;
- }
}
else if (type == RESERV && val == DTK_EPOCH)
{
typedef int64 TimeOffset;
typedef int32 fsec_t; /* fractional seconds (in microseconds) */
+
+/*
+ * Storage format for type interval.
+ */
typedef struct
{
TimeOffset time; /* all time units other than days, months and
int32 month; /* months and years, after time for alignment */
} Interval;
+/*
+ * Data structure representing a broken-down interval.
+ *
+ * For historical reasons, this is modeled on struct pg_tm for timestamps.
+ * Unlike the situation for timestamps, there's no magic interpretation
+ * needed for months or years: they're just zero or not. Note that fields
+ * can be negative; however, because of the divisions done while converting
+ * from struct Interval, only tm_mday could be INT_MIN. This is important
+ * because we may need to negate the values in some code paths.
+ */
+struct pg_itm
+{
+ int tm_usec;
+ int tm_sec;
+ int tm_min;
+ int64 tm_hour; /* needs to be wide */
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+};
+
+/*
+ * Data structure for decoding intervals. We could just use struct pg_itm,
+ * but then the requirement for tm_usec to be 64 bits would propagate to
+ * places where it's not really needed. Also, omitting the fields that
+ * aren't used during decoding seems like a good error-prevention measure.
+ */
+struct pg_itm_in
+{
+ int64 tm_usec; /* needs to be wide */
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+};
+
/* Limits on the "precision" option (typmod) for these data types */
#define MAX_TIMESTAMP_PRECISION 6
typedef int64 pg_time_t;
/*
+ * Data structure representing a broken-down timestamp.
+ *
* CAUTION: the IANA timezone library (src/timezone/) follows the POSIX
* convention that tm_mon counts from 0 and tm_year is relative to 1900.
* However, Postgres' datetime functions generally treat tm_mon as counting
const char *tm_zone;
};
+/* These structs are opaque outside the timezone library */
typedef struct pg_tz pg_tz;
typedef struct pg_tzenum pg_tzenum;
int nf, int *dtype,
struct pg_tm *tm, fsec_t *fsec, int *tzp);
extern int DecodeInterval(char **field, int *ftype, int nf, int range,
- int *dtype, struct pg_tm *tm, fsec_t *fsec);
+ int *dtype, struct pg_itm_in *itm_in);
extern int DecodeISO8601Interval(char *str,
- int *dtype, struct pg_tm *tm, fsec_t *fsec);
+ int *dtype, struct pg_itm_in *itm_in);
extern void DateTimeParseError(int dterr, const char *str,
const char *datatype) pg_attribute_noreturn();
extern void EncodeDateOnly(struct pg_tm *tm, int style, char *str);
extern void EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str);
extern void EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str);
-extern void EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str);
+extern void EncodeInterval(struct pg_itm *itm, int style, char *str);
extern void EncodeSpecialTimestamp(Timestamp dt, char *str);
extern int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp dt, int *hour, int *min, int *sec, fsec_t *fsec);
-extern int interval2tm(Interval span, struct pg_tm *tm, fsec_t *fsec);
-extern int tm2interval(struct pg_tm *tm, fsec_t fsec, Interval *span);
+extern void interval2itm(Interval span, struct pg_itm *itm);
+extern int itm2interval(struct pg_itm *itm, Interval *span);
+extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
extern Timestamp SetEpochTimestamp(void);
extern void GetEpochTime(struct pg_tm *tm);
@ 0.7 secs | @ 0.7 secs | @ 0.7 secs
(1 row)
+-- test time fields using entire 64 bit microseconds range
+select interval '2562047788.01521550194 hours';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-2562047788.01521550222 hours';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '153722867280.912930117 minutes';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-153722867280.912930133 minutes';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '9223372036854.775807 seconds';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-9223372036854.775808 seconds';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '9223372036854775.807 milliseconds';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-9223372036854775.808 milliseconds';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval '9223372036854775807 microseconds';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval '-9223372036854775808 microseconds';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval 'PT2562047788H54.775807S';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval 'PT-2562047788H-54.775808S';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+select interval 'PT2562047788:00:54.775807';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval 'PT2562047788.0152155019444';
+ interval
+-----------------------------------
+ @ 2562047788 hours 54.775807 secs
+(1 row)
+
+select interval 'PT-2562047788.0152155022222';
+ interval
+---------------------------------------
+ @ 2562047788 hours 54.775808 secs ago
+(1 row)
+
+-- overflow each date/time field
+select interval '2147483648 years';
+ERROR: interval field value out of range: "2147483648 years"
+LINE 1: select interval '2147483648 years';
+ ^
+select interval '-2147483649 years';
+ERROR: interval field value out of range: "-2147483649 years"
+LINE 1: select interval '-2147483649 years';
+ ^
+select interval '2147483648 months';
+ERROR: interval field value out of range: "2147483648 months"
+LINE 1: select interval '2147483648 months';
+ ^
+select interval '-2147483649 months';
+ERROR: interval field value out of range: "-2147483649 months"
+LINE 1: select interval '-2147483649 months';
+ ^
+select interval '2147483648 days';
+ERROR: interval field value out of range: "2147483648 days"
+LINE 1: select interval '2147483648 days';
+ ^
+select interval '-2147483649 days';
+ERROR: interval field value out of range: "-2147483649 days"
+LINE 1: select interval '-2147483649 days';
+ ^
+select interval '2562047789 hours';
+ERROR: interval field value out of range: "2562047789 hours"
+LINE 1: select interval '2562047789 hours';
+ ^
+select interval '-2562047789 hours';
+ERROR: interval field value out of range: "-2562047789 hours"
+LINE 1: select interval '-2562047789 hours';
+ ^
+select interval '153722867281 minutes';
+ERROR: interval field value out of range: "153722867281 minutes"
+LINE 1: select interval '153722867281 minutes';
+ ^
+select interval '-153722867281 minutes';
+ERROR: interval field value out of range: "-153722867281 minutes"
+LINE 1: select interval '-153722867281 minutes';
+ ^
+select interval '9223372036855 seconds';
+ERROR: interval field value out of range: "9223372036855 seconds"
+LINE 1: select interval '9223372036855 seconds';
+ ^
+select interval '-9223372036855 seconds';
+ERROR: interval field value out of range: "-9223372036855 seconds"
+LINE 1: select interval '-9223372036855 seconds';
+ ^
+select interval '9223372036854777 millisecond';
+ERROR: interval field value out of range: "9223372036854777 millisecond"
+LINE 1: select interval '9223372036854777 millisecond';
+ ^
+select interval '-9223372036854777 millisecond';
+ERROR: interval field value out of range: "-9223372036854777 millisecond"
+LINE 1: select interval '-9223372036854777 millisecond';
+ ^
+select interval '9223372036854775808 microsecond';
+ERROR: interval field value out of range: "9223372036854775808 microsecond"
+LINE 1: select interval '9223372036854775808 microsecond';
+ ^
+select interval '-9223372036854775809 microsecond';
+ERROR: interval field value out of range: "-9223372036854775809 microsecond"
+LINE 1: select interval '-9223372036854775809 microsecond';
+ ^
+select interval 'P2147483648';
+ERROR: interval field value out of range: "P2147483648"
+LINE 1: select interval 'P2147483648';
+ ^
+select interval 'P-2147483649';
+ERROR: interval field value out of range: "P-2147483649"
+LINE 1: select interval 'P-2147483649';
+ ^
+select interval 'P1-2147483647-2147483647';
+ERROR: interval out of range
+LINE 1: select interval 'P1-2147483647-2147483647';
+ ^
+select interval 'PT2562047789';
+ERROR: interval field value out of range: "PT2562047789"
+LINE 1: select interval 'PT2562047789';
+ ^
+select interval 'PT-2562047789';
+ERROR: interval field value out of range: "PT-2562047789"
+LINE 1: select interval 'PT-2562047789';
+ ^
+-- overflow with date/time unit aliases
+select interval '2147483647 weeks';
+ERROR: interval field value out of range: "2147483647 weeks"
+LINE 1: select interval '2147483647 weeks';
+ ^
+select interval '-2147483648 weeks';
+ERROR: interval field value out of range: "-2147483648 weeks"
+LINE 1: select interval '-2147483648 weeks';
+ ^
+select interval '2147483647 decades';
+ERROR: interval field value out of range: "2147483647 decades"
+LINE 1: select interval '2147483647 decades';
+ ^
+select interval '-2147483648 decades';
+ERROR: interval field value out of range: "-2147483648 decades"
+LINE 1: select interval '-2147483648 decades';
+ ^
+select interval '2147483647 centuries';
+ERROR: interval field value out of range: "2147483647 centuries"
+LINE 1: select interval '2147483647 centuries';
+ ^
+select interval '-2147483648 centuries';
+ERROR: interval field value out of range: "-2147483648 centuries"
+LINE 1: select interval '-2147483648 centuries';
+ ^
+select interval '2147483647 millennium';
+ERROR: interval field value out of range: "2147483647 millennium"
+LINE 1: select interval '2147483647 millennium';
+ ^
+select interval '-2147483648 millennium';
+ERROR: interval field value out of range: "-2147483648 millennium"
+LINE 1: select interval '-2147483648 millennium';
+ ^
+select interval '1 week 2147483647 days';
+ERROR: interval field value out of range: "1 week 2147483647 days"
+LINE 1: select interval '1 week 2147483647 days';
+ ^
+select interval '-1 week -2147483648 days';
+ERROR: interval field value out of range: "-1 week -2147483648 days"
+LINE 1: select interval '-1 week -2147483648 days';
+ ^
+select interval '2147483647 days 1 week';
+ERROR: interval field value out of range: "2147483647 days 1 week"
+LINE 1: select interval '2147483647 days 1 week';
+ ^
+select interval '-2147483648 days -1 week';
+ERROR: interval field value out of range: "-2147483648 days -1 week"
+LINE 1: select interval '-2147483648 days -1 week';
+ ^
+select interval 'P1W2147483647D';
+ERROR: interval field value out of range: "P1W2147483647D"
+LINE 1: select interval 'P1W2147483647D';
+ ^
+select interval 'P-1W-2147483648D';
+ERROR: interval field value out of range: "P-1W-2147483648D"
+LINE 1: select interval 'P-1W-2147483648D';
+ ^
+select interval 'P2147483647D1W';
+ERROR: interval field value out of range: "P2147483647D1W"
+LINE 1: select interval 'P2147483647D1W';
+ ^
+select interval 'P-2147483648D-1W';
+ERROR: interval field value out of range: "P-2147483648D-1W"
+LINE 1: select interval 'P-2147483648D-1W';
+ ^
+select interval '1 decade 2147483647 years';
+ERROR: interval field value out of range: "1 decade 2147483647 years"
+LINE 1: select interval '1 decade 2147483647 years';
+ ^
+select interval '1 century 2147483647 years';
+ERROR: interval field value out of range: "1 century 2147483647 years"
+LINE 1: select interval '1 century 2147483647 years';
+ ^
+select interval '1 millennium 2147483647 years';
+ERROR: interval field value out of range: "1 millennium 2147483647 years"
+LINE 1: select interval '1 millennium 2147483647 years';
+ ^
+select interval '-1 decade -2147483648 years';
+ERROR: interval field value out of range: "-1 decade -2147483648 years"
+LINE 1: select interval '-1 decade -2147483648 years';
+ ^
+select interval '-1 century -2147483648 years';
+ERROR: interval field value out of range: "-1 century -2147483648 years"
+LINE 1: select interval '-1 century -2147483648 years';
+ ^
+select interval '-1 millennium -2147483648 years';
+ERROR: interval field value out of range: "-1 millennium -2147483648 years"
+LINE 1: select interval '-1 millennium -2147483648 years';
+ ^
+select interval '2147483647 years 1 decade';
+ERROR: interval field value out of range: "2147483647 years 1 decade"
+LINE 1: select interval '2147483647 years 1 decade';
+ ^
+select interval '2147483647 years 1 century';
+ERROR: interval field value out of range: "2147483647 years 1 century"
+LINE 1: select interval '2147483647 years 1 century';
+ ^
+select interval '2147483647 years 1 millennium';
+ERROR: interval field value out of range: "2147483647 years 1 millennium"
+LINE 1: select interval '2147483647 years 1 millennium';
+ ^
+select interval '-2147483648 years -1 decade';
+ERROR: interval field value out of range: "-2147483648 years -1 decade"
+LINE 1: select interval '-2147483648 years -1 decade';
+ ^
+select interval '-2147483648 years -1 century';
+ERROR: interval field value out of range: "-2147483648 years -1 century"
+LINE 1: select interval '-2147483648 years -1 century';
+ ^
+select interval '-2147483648 years -1 millennium';
+ERROR: interval field value out of range: "-2147483648 years -1 millennium"
+LINE 1: select interval '-2147483648 years -1 millennium';
+ ^
+-- overflowing with fractional fields - postgres format
+select interval '0.1 millennium 2147483647 months';
+ERROR: interval field value out of range: "0.1 millennium 2147483647 months"
+LINE 1: select interval '0.1 millennium 2147483647 months';
+ ^
+select interval '0.1 centuries 2147483647 months';
+ERROR: interval field value out of range: "0.1 centuries 2147483647 months"
+LINE 1: select interval '0.1 centuries 2147483647 months';
+ ^
+select interval '0.1 decades 2147483647 months';
+ERROR: interval field value out of range: "0.1 decades 2147483647 months"
+LINE 1: select interval '0.1 decades 2147483647 months';
+ ^
+select interval '0.1 yrs 2147483647 months';
+ERROR: interval field value out of range: "0.1 yrs 2147483647 months"
+LINE 1: select interval '0.1 yrs 2147483647 months';
+ ^
+select interval '-0.1 millennium -2147483648 months';
+ERROR: interval field value out of range: "-0.1 millennium -2147483648 months"
+LINE 1: select interval '-0.1 millennium -2147483648 months';
+ ^
+select interval '-0.1 centuries -2147483648 months';
+ERROR: interval field value out of range: "-0.1 centuries -2147483648 months"
+LINE 1: select interval '-0.1 centuries -2147483648 months';
+ ^
+select interval '-0.1 decades -2147483648 months';
+ERROR: interval field value out of range: "-0.1 decades -2147483648 months"
+LINE 1: select interval '-0.1 decades -2147483648 months';
+ ^
+select interval '-0.1 yrs -2147483648 months';
+ERROR: interval field value out of range: "-0.1 yrs -2147483648 months"
+LINE 1: select interval '-0.1 yrs -2147483648 months';
+ ^
+select interval '2147483647 months 0.1 millennium';
+ERROR: interval field value out of range: "2147483647 months 0.1 millennium"
+LINE 1: select interval '2147483647 months 0.1 millennium';
+ ^
+select interval '2147483647 months 0.1 centuries';
+ERROR: interval field value out of range: "2147483647 months 0.1 centuries"
+LINE 1: select interval '2147483647 months 0.1 centuries';
+ ^
+select interval '2147483647 months 0.1 decades';
+ERROR: interval field value out of range: "2147483647 months 0.1 decades"
+LINE 1: select interval '2147483647 months 0.1 decades';
+ ^
+select interval '2147483647 months 0.1 yrs';
+ERROR: interval field value out of range: "2147483647 months 0.1 yrs"
+LINE 1: select interval '2147483647 months 0.1 yrs';
+ ^
+select interval '-2147483648 months -0.1 millennium';
+ERROR: interval field value out of range: "-2147483648 months -0.1 millennium"
+LINE 1: select interval '-2147483648 months -0.1 millennium';
+ ^
+select interval '-2147483648 months -0.1 centuries';
+ERROR: interval field value out of range: "-2147483648 months -0.1 centuries"
+LINE 1: select interval '-2147483648 months -0.1 centuries';
+ ^
+select interval '-2147483648 months -0.1 decades';
+ERROR: interval field value out of range: "-2147483648 months -0.1 decades"
+LINE 1: select interval '-2147483648 months -0.1 decades';
+ ^
+select interval '-2147483648 months -0.1 yrs';
+ERROR: interval field value out of range: "-2147483648 months -0.1 yrs"
+LINE 1: select interval '-2147483648 months -0.1 yrs';
+ ^
+select interval '0.1 months 2147483647 days';
+ERROR: interval field value out of range: "0.1 months 2147483647 days"
+LINE 1: select interval '0.1 months 2147483647 days';
+ ^
+select interval '-0.1 months -2147483648 days';
+ERROR: interval field value out of range: "-0.1 months -2147483648 days"
+LINE 1: select interval '-0.1 months -2147483648 days';
+ ^
+select interval '2147483647 days 0.1 months';
+ERROR: interval field value out of range: "2147483647 days 0.1 months"
+LINE 1: select interval '2147483647 days 0.1 months';
+ ^
+select interval '-2147483648 days -0.1 months';
+ERROR: interval field value out of range: "-2147483648 days -0.1 months"
+LINE 1: select interval '-2147483648 days -0.1 months';
+ ^
+select interval '0.5 weeks 2147483647 days';
+ERROR: interval field value out of range: "0.5 weeks 2147483647 days"
+LINE 1: select interval '0.5 weeks 2147483647 days';
+ ^
+select interval '-0.5 weeks -2147483648 days';
+ERROR: interval field value out of range: "-0.5 weeks -2147483648 days"
+LINE 1: select interval '-0.5 weeks -2147483648 days';
+ ^
+select interval '2147483647 days 0.5 weeks';
+ERROR: interval field value out of range: "2147483647 days 0.5 weeks"
+LINE 1: select interval '2147483647 days 0.5 weeks';
+ ^
+select interval '-2147483648 days -0.5 weeks';
+ERROR: interval field value out of range: "-2147483648 days -0.5 weeks"
+LINE 1: select interval '-2147483648 days -0.5 weeks';
+ ^
+select interval '0.01 months 9223372036854775807 microseconds';
+ERROR: interval field value out of range: "0.01 months 9223372036854775807 microseconds"
+LINE 1: select interval '0.01 months 9223372036854775807 microsecond...
+ ^
+select interval '-0.01 months -9223372036854775808 microseconds';
+ERROR: interval field value out of range: "-0.01 months -9223372036854775808 microseconds"
+LINE 1: select interval '-0.01 months -9223372036854775808 microseco...
+ ^
+select interval '9223372036854775807 microseconds 0.01 months';
+ERROR: interval field value out of range: "9223372036854775807 microseconds 0.01 months"
+LINE 1: select interval '9223372036854775807 microseconds 0.01 month...
+ ^
+select interval '-9223372036854775808 microseconds -0.01 months';
+ERROR: interval field value out of range: "-9223372036854775808 microseconds -0.01 months"
+LINE 1: select interval '-9223372036854775808 microseconds -0.01 mon...
+ ^
+select interval '0.1 weeks 9223372036854775807 microseconds';
+ERROR: interval field value out of range: "0.1 weeks 9223372036854775807 microseconds"
+LINE 1: select interval '0.1 weeks 9223372036854775807 microseconds'...
+ ^
+select interval '-0.1 weeks -9223372036854775808 microseconds';
+ERROR: interval field value out of range: "-0.1 weeks -9223372036854775808 microseconds"
+LINE 1: select interval '-0.1 weeks -9223372036854775808 microsecond...
+ ^
+select interval '9223372036854775807 microseconds 0.1 weeks';
+ERROR: interval field value out of range: "9223372036854775807 microseconds 0.1 weeks"
+LINE 1: select interval '9223372036854775807 microseconds 0.1 weeks'...
+ ^
+select interval '-9223372036854775808 microseconds -0.1 weeks';
+ERROR: interval field value out of range: "-9223372036854775808 microseconds -0.1 weeks"
+LINE 1: select interval '-9223372036854775808 microseconds -0.1 week...
+ ^
+select interval '0.1 days 9223372036854775807 microseconds';
+ERROR: interval field value out of range: "0.1 days 9223372036854775807 microseconds"
+LINE 1: select interval '0.1 days 9223372036854775807 microseconds';
+ ^
+select interval '-0.1 days -9223372036854775808 microseconds';
+ERROR: interval field value out of range: "-0.1 days -9223372036854775808 microseconds"
+LINE 1: select interval '-0.1 days -9223372036854775808 microseconds...
+ ^
+select interval '9223372036854775807 microseconds 0.1 days';
+ERROR: interval field value out of range: "9223372036854775807 microseconds 0.1 days"
+LINE 1: select interval '9223372036854775807 microseconds 0.1 days';
+ ^
+select interval '-9223372036854775808 microseconds -0.1 days';
+ERROR: interval field value out of range: "-9223372036854775808 microseconds -0.1 days"
+LINE 1: select interval '-9223372036854775808 microseconds -0.1 days...
+ ^
+-- overflowing with fractional fields - ISO8601 format
+select interval 'P0.1Y2147483647M';
+ERROR: interval field value out of range: "P0.1Y2147483647M"
+LINE 1: select interval 'P0.1Y2147483647M';
+ ^
+select interval 'P-0.1Y-2147483648M';
+ERROR: interval field value out of range: "P-0.1Y-2147483648M"
+LINE 1: select interval 'P-0.1Y-2147483648M';
+ ^
+select interval 'P2147483647M0.1Y';
+ERROR: interval field value out of range: "P2147483647M0.1Y"
+LINE 1: select interval 'P2147483647M0.1Y';
+ ^
+select interval 'P-2147483648M-0.1Y';
+ERROR: interval field value out of range: "P-2147483648M-0.1Y"
+LINE 1: select interval 'P-2147483648M-0.1Y';
+ ^
+select interval 'P0.1M2147483647D';
+ERROR: interval field value out of range: "P0.1M2147483647D"
+LINE 1: select interval 'P0.1M2147483647D';
+ ^
+select interval 'P-0.1M-2147483648D';
+ERROR: interval field value out of range: "P-0.1M-2147483648D"
+LINE 1: select interval 'P-0.1M-2147483648D';
+ ^
+select interval 'P2147483647D0.1M';
+ERROR: interval field value out of range: "P2147483647D0.1M"
+LINE 1: select interval 'P2147483647D0.1M';
+ ^
+select interval 'P-2147483648D-0.1M';
+ERROR: interval field value out of range: "P-2147483648D-0.1M"
+LINE 1: select interval 'P-2147483648D-0.1M';
+ ^
+select interval 'P0.5W2147483647D';
+ERROR: interval field value out of range: "P0.5W2147483647D"
+LINE 1: select interval 'P0.5W2147483647D';
+ ^
+select interval 'P-0.5W-2147483648D';
+ERROR: interval field value out of range: "P-0.5W-2147483648D"
+LINE 1: select interval 'P-0.5W-2147483648D';
+ ^
+select interval 'P2147483647D0.5W';
+ERROR: interval field value out of range: "P2147483647D0.5W"
+LINE 1: select interval 'P2147483647D0.5W';
+ ^
+select interval 'P-2147483648D-0.5W';
+ERROR: interval field value out of range: "P-2147483648D-0.5W"
+LINE 1: select interval 'P-2147483648D-0.5W';
+ ^
+select interval 'P0.01MT2562047788H54.775807S';
+ERROR: interval field value out of range: "P0.01MT2562047788H54.775807S"
+LINE 1: select interval 'P0.01MT2562047788H54.775807S';
+ ^
+select interval 'P-0.01MT-2562047788H-54.775808S';
+ERROR: interval field value out of range: "P-0.01MT-2562047788H-54.775808S"
+LINE 1: select interval 'P-0.01MT-2562047788H-54.775808S';
+ ^
+select interval 'P0.1DT2562047788H54.775807S';
+ERROR: interval field value out of range: "P0.1DT2562047788H54.775807S"
+LINE 1: select interval 'P0.1DT2562047788H54.775807S';
+ ^
+select interval 'P-0.1DT-2562047788H-54.775808S';
+ERROR: interval field value out of range: "P-0.1DT-2562047788H-54.775808S"
+LINE 1: select interval 'P-0.1DT-2562047788H-54.775808S';
+ ^
+select interval 'PT2562047788.1H54.775807S';
+ERROR: interval field value out of range: "PT2562047788.1H54.775807S"
+LINE 1: select interval 'PT2562047788.1H54.775807S';
+ ^
+select interval 'PT-2562047788.1H-54.775808S';
+ERROR: interval field value out of range: "PT-2562047788.1H-54.775808S"
+LINE 1: select interval 'PT-2562047788.1H-54.775808S';
+ ^
+select interval 'PT2562047788H0.1M54.775807S';
+ERROR: interval field value out of range: "PT2562047788H0.1M54.775807S"
+LINE 1: select interval 'PT2562047788H0.1M54.775807S';
+ ^
+select interval 'PT-2562047788H-0.1M-54.775808S';
+ERROR: interval field value out of range: "PT-2562047788H-0.1M-54.775808S"
+LINE 1: select interval 'PT-2562047788H-0.1M-54.775808S';
+ ^
+-- overflowing with fractional fields - ISO8601 alternative format
+select interval 'P0.1-2147483647-00';
+ERROR: interval field value out of range: "P0.1-2147483647-00"
+LINE 1: select interval 'P0.1-2147483647-00';
+ ^
+select interval 'P00-0.1-2147483647';
+ERROR: interval field value out of range: "P00-0.1-2147483647"
+LINE 1: select interval 'P00-0.1-2147483647';
+ ^
+select interval 'P00-0.01-00T2562047788:00:54.775807';
+ERROR: interval field value out of range: "P00-0.01-00T2562047788:00:54.775807"
+LINE 1: select interval 'P00-0.01-00T2562047788:00:54.775807';
+ ^
+select interval 'P00-00-0.1T2562047788:00:54.775807';
+ERROR: interval field value out of range: "P00-00-0.1T2562047788:00:54.775807"
+LINE 1: select interval 'P00-00-0.1T2562047788:00:54.775807';
+ ^
+select interval 'PT2562047788.1:00:54.775807';
+ERROR: interval field value out of range: "PT2562047788.1:00:54.775807"
+LINE 1: select interval 'PT2562047788.1:00:54.775807';
+ ^
+select interval 'PT2562047788:01.:54.775807';
+ERROR: interval field value out of range: "PT2562047788:01.:54.775807"
+LINE 1: select interval 'PT2562047788:01.:54.775807';
+ ^
+-- overflowing with fractional fields - SQL standard format
+select interval '0.1 2562047788:0:54.775807';
+ERROR: interval field value out of range: "0.1 2562047788:0:54.775807"
+LINE 1: select interval '0.1 2562047788:0:54.775807';
+ ^
+select interval '0.1 2562047788:0:54.775808 ago';
+ERROR: interval field value out of range: "0.1 2562047788:0:54.775808 ago"
+LINE 1: select interval '0.1 2562047788:0:54.775808 ago';
+ ^
+select interval '2562047788.1:0:54.775807';
+ERROR: interval field value out of range: "2562047788.1:0:54.775807"
+LINE 1: select interval '2562047788.1:0:54.775807';
+ ^
+select interval '2562047788.1:0:54.775808 ago';
+ERROR: interval field value out of range: "2562047788.1:0:54.775808 ago"
+LINE 1: select interval '2562047788.1:0:54.775808 ago';
+ ^
+select interval '2562047788:0.1:54.775807';
+ERROR: invalid input syntax for type interval: "2562047788:0.1:54.775807"
+LINE 1: select interval '2562047788:0.1:54.775807';
+ ^
+select interval '2562047788:0.1:54.775808 ago';
+ERROR: invalid input syntax for type interval: "2562047788:0.1:54.775808 ago"
+LINE 1: select interval '2562047788:0.1:54.775808 ago';
+ ^
+-- overflowing using AGO with INT_MIN
+select interval '-2147483648 months ago';
+ERROR: interval field value out of range: "-2147483648 months ago"
+LINE 1: select interval '-2147483648 months ago';
+ ^
+select interval '-2147483648 days ago';
+ERROR: interval field value out of range: "-2147483648 days ago"
+LINE 1: select interval '-2147483648 days ago';
+ ^
+select interval '-9223372036854775808 microseconds ago';
+ERROR: interval field value out of range: "-9223372036854775808 microseconds ago"
+LINE 1: select interval '-9223372036854775808 microseconds ago';
+ ^
+select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
+LINE 1: select interval '-2147483648 months -2147483648 days -922337...
+ ^
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+--------------------------------------------------------------------
+ -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to sql_standard;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+---------------------------------------------------
+ -178956970-8 -2147483648 -2562047788:00:54.775808
+(1 row)
+
+SET IntervalStyle to iso_8601;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------------------------------------------------
+ P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+(1 row)
+
+SET IntervalStyle to postgres_verbose;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+(1 row)
+
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
t
select interval '1 year 2 mons 3 days 04:05:06.699999';
select interval '0:0:0.7', interval '@ 0.70 secs', interval '0.7 seconds';
+-- test time fields using entire 64 bit microseconds range
+select interval '2562047788.01521550194 hours';
+select interval '-2562047788.01521550222 hours';
+select interval '153722867280.912930117 minutes';
+select interval '-153722867280.912930133 minutes';
+select interval '9223372036854.775807 seconds';
+select interval '-9223372036854.775808 seconds';
+select interval '9223372036854775.807 milliseconds';
+select interval '-9223372036854775.808 milliseconds';
+select interval '9223372036854775807 microseconds';
+select interval '-9223372036854775808 microseconds';
+
+select interval 'PT2562047788H54.775807S';
+select interval 'PT-2562047788H-54.775808S';
+
+select interval 'PT2562047788:00:54.775807';
+
+select interval 'PT2562047788.0152155019444';
+select interval 'PT-2562047788.0152155022222';
+
+-- overflow each date/time field
+select interval '2147483648 years';
+select interval '-2147483649 years';
+select interval '2147483648 months';
+select interval '-2147483649 months';
+select interval '2147483648 days';
+select interval '-2147483649 days';
+select interval '2562047789 hours';
+select interval '-2562047789 hours';
+select interval '153722867281 minutes';
+select interval '-153722867281 minutes';
+select interval '9223372036855 seconds';
+select interval '-9223372036855 seconds';
+select interval '9223372036854777 millisecond';
+select interval '-9223372036854777 millisecond';
+select interval '9223372036854775808 microsecond';
+select interval '-9223372036854775809 microsecond';
+
+select interval 'P2147483648';
+select interval 'P-2147483649';
+select interval 'P1-2147483647-2147483647';
+select interval 'PT2562047789';
+select interval 'PT-2562047789';
+
+-- overflow with date/time unit aliases
+select interval '2147483647 weeks';
+select interval '-2147483648 weeks';
+select interval '2147483647 decades';
+select interval '-2147483648 decades';
+select interval '2147483647 centuries';
+select interval '-2147483648 centuries';
+select interval '2147483647 millennium';
+select interval '-2147483648 millennium';
+
+select interval '1 week 2147483647 days';
+select interval '-1 week -2147483648 days';
+select interval '2147483647 days 1 week';
+select interval '-2147483648 days -1 week';
+
+select interval 'P1W2147483647D';
+select interval 'P-1W-2147483648D';
+select interval 'P2147483647D1W';
+select interval 'P-2147483648D-1W';
+
+select interval '1 decade 2147483647 years';
+select interval '1 century 2147483647 years';
+select interval '1 millennium 2147483647 years';
+select interval '-1 decade -2147483648 years';
+select interval '-1 century -2147483648 years';
+select interval '-1 millennium -2147483648 years';
+
+select interval '2147483647 years 1 decade';
+select interval '2147483647 years 1 century';
+select interval '2147483647 years 1 millennium';
+select interval '-2147483648 years -1 decade';
+select interval '-2147483648 years -1 century';
+select interval '-2147483648 years -1 millennium';
+
+-- overflowing with fractional fields - postgres format
+select interval '0.1 millennium 2147483647 months';
+select interval '0.1 centuries 2147483647 months';
+select interval '0.1 decades 2147483647 months';
+select interval '0.1 yrs 2147483647 months';
+select interval '-0.1 millennium -2147483648 months';
+select interval '-0.1 centuries -2147483648 months';
+select interval '-0.1 decades -2147483648 months';
+select interval '-0.1 yrs -2147483648 months';
+
+select interval '2147483647 months 0.1 millennium';
+select interval '2147483647 months 0.1 centuries';
+select interval '2147483647 months 0.1 decades';
+select interval '2147483647 months 0.1 yrs';
+select interval '-2147483648 months -0.1 millennium';
+select interval '-2147483648 months -0.1 centuries';
+select interval '-2147483648 months -0.1 decades';
+select interval '-2147483648 months -0.1 yrs';
+
+select interval '0.1 months 2147483647 days';
+select interval '-0.1 months -2147483648 days';
+select interval '2147483647 days 0.1 months';
+select interval '-2147483648 days -0.1 months';
+
+select interval '0.5 weeks 2147483647 days';
+select interval '-0.5 weeks -2147483648 days';
+select interval '2147483647 days 0.5 weeks';
+select interval '-2147483648 days -0.5 weeks';
+
+select interval '0.01 months 9223372036854775807 microseconds';
+select interval '-0.01 months -9223372036854775808 microseconds';
+select interval '9223372036854775807 microseconds 0.01 months';
+select interval '-9223372036854775808 microseconds -0.01 months';
+
+select interval '0.1 weeks 9223372036854775807 microseconds';
+select interval '-0.1 weeks -9223372036854775808 microseconds';
+select interval '9223372036854775807 microseconds 0.1 weeks';
+select interval '-9223372036854775808 microseconds -0.1 weeks';
+
+select interval '0.1 days 9223372036854775807 microseconds';
+select interval '-0.1 days -9223372036854775808 microseconds';
+select interval '9223372036854775807 microseconds 0.1 days';
+select interval '-9223372036854775808 microseconds -0.1 days';
+
+-- overflowing with fractional fields - ISO8601 format
+select interval 'P0.1Y2147483647M';
+select interval 'P-0.1Y-2147483648M';
+select interval 'P2147483647M0.1Y';
+select interval 'P-2147483648M-0.1Y';
+
+select interval 'P0.1M2147483647D';
+select interval 'P-0.1M-2147483648D';
+select interval 'P2147483647D0.1M';
+select interval 'P-2147483648D-0.1M';
+
+select interval 'P0.5W2147483647D';
+select interval 'P-0.5W-2147483648D';
+select interval 'P2147483647D0.5W';
+select interval 'P-2147483648D-0.5W';
+
+select interval 'P0.01MT2562047788H54.775807S';
+select interval 'P-0.01MT-2562047788H-54.775808S';
+
+select interval 'P0.1DT2562047788H54.775807S';
+select interval 'P-0.1DT-2562047788H-54.775808S';
+
+select interval 'PT2562047788.1H54.775807S';
+select interval 'PT-2562047788.1H-54.775808S';
+
+select interval 'PT2562047788H0.1M54.775807S';
+select interval 'PT-2562047788H-0.1M-54.775808S';
+
+-- overflowing with fractional fields - ISO8601 alternative format
+select interval 'P0.1-2147483647-00';
+select interval 'P00-0.1-2147483647';
+select interval 'P00-0.01-00T2562047788:00:54.775807';
+select interval 'P00-00-0.1T2562047788:00:54.775807';
+select interval 'PT2562047788.1:00:54.775807';
+select interval 'PT2562047788:01.:54.775807';
+
+-- overflowing with fractional fields - SQL standard format
+select interval '0.1 2562047788:0:54.775807';
+select interval '0.1 2562047788:0:54.775808 ago';
+
+select interval '2562047788.1:0:54.775807';
+select interval '2562047788.1:0:54.775808 ago';
+
+select interval '2562047788:0.1:54.775807';
+select interval '2562047788:0.1:54.775808 ago';
+
+-- overflowing using AGO with INT_MIN
+select interval '-2147483648 months ago';
+select interval '-2147483648 days ago';
+select interval '-9223372036854775808 microseconds ago';
+select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+
+-- test that INT_MIN number is formatted properly
+SET IntervalStyle to postgres;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to sql_standard;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to iso_8601;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SET IntervalStyle to postgres_verbose;
+select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
select interval_hash('30 days'::interval) = interval_hash('1 month'::interval) as t;