diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index d05c93058a..26521661b7 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -1,4 +1,4 @@ - + Data Types @@ -2032,12 +2032,12 @@ January 8 04:05:06 1999 PST infinity - timestamp + date, timestamp later than all other time stamps -infinity - timestamp + date, timestamp earlier than all other time stamps diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e289326f2f..06ee6a0572 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,4 +1,4 @@ - + Functions and Operators @@ -5912,10 +5912,18 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})'); 3 + + isfinite(date) + boolean + Test for finite date (not +/-infinity) + isfinite(date '2001-02-16') + true + + isfinite(timestamp) boolean - Test for finite time stamp (not equal to infinity) + Test for finite time stamp (not +/-infinity) isfinite(timestamp '2001-02-16 21:28:30') true diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 6f10b666ca..ffeb754109 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.142 2008/07/07 18:09:46 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/date.c,v 1.143 2008/10/14 17:12:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,6 +38,7 @@ #endif +static void EncodeSpecialDate(DateADT dt, char *str); static int time2tm(TimeADT time, struct pg_tm * tm, fsec_t *fsec); static int timetz2tm(TimeTzADT *time, struct pg_tm * tm, fsec_t *fsec, int *tzp); static int tm2time(struct pg_tm * tm, fsec_t fsec, TimeADT *result); @@ -147,6 +148,14 @@ date_in(PG_FUNCTION_ARGS) GetEpochTime(tm); break; + case DTK_LATE: + DATE_NOEND(date); + PG_RETURN_DATEADT(date); + + case DTK_EARLY: + DATE_NOBEGIN(date); + PG_RETURN_DATEADT(date); + default: DateTimeParseError(DTERR_BAD_FORMAT, str, "date"); break; @@ -174,10 +183,14 @@ date_out(PG_FUNCTION_ARGS) *tm = &tt; char buf[MAXDATELEN + 1]; - j2date(date + POSTGRES_EPOCH_JDATE, - &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); - - EncodeDateOnly(tm, DateStyle, buf); + if (DATE_NOT_FINITE(date)) + EncodeSpecialDate(date, buf); + else + { + j2date(date + POSTGRES_EPOCH_JDATE, + &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + EncodeDateOnly(tm, DateStyle, buf); + } result = pstrdup(buf); PG_RETURN_CSTRING(result); @@ -208,6 +221,20 @@ date_send(PG_FUNCTION_ARGS) PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } +/* + * Convert reserved date values to string. + */ +static void +EncodeSpecialDate(DateADT dt, char *str) +{ + if (DATE_IS_NOBEGIN(dt)) + strcpy(str, EARLY); + else if (DATE_IS_NOEND(dt)) + strcpy(str, LATE); + else /* shouldn't happen */ + elog(ERROR, "invalid argument for EncodeSpecialDate"); +} + /* * Comparison functions for dates @@ -280,6 +307,14 @@ date_cmp(PG_FUNCTION_ARGS) PG_RETURN_INT32(0); } +Datum +date_finite(PG_FUNCTION_ARGS) +{ + DateADT date = PG_GETARG_DATEADT(0); + + PG_RETURN_BOOL(!DATE_NOT_FINITE(date)); +} + Datum date_larger(PG_FUNCTION_ARGS) { @@ -306,6 +341,11 @@ date_mi(PG_FUNCTION_ARGS) DateADT dateVal1 = PG_GETARG_DATEADT(0); DateADT dateVal2 = PG_GETARG_DATEADT(1); + if (DATE_NOT_FINITE(dateVal1) || DATE_NOT_FINITE(dateVal2)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("cannot subtract infinite dates"))); + PG_RETURN_INT32((int32) (dateVal1 - dateVal2)); } @@ -318,6 +358,9 @@ date_pli(PG_FUNCTION_ARGS) DateADT dateVal = PG_GETARG_DATEADT(0); int32 days = PG_GETARG_INT32(1); + if (DATE_NOT_FINITE(dateVal)) + days = 0; /* can't change infinity */ + PG_RETURN_DATEADT(dateVal + days); } @@ -329,6 +372,9 @@ date_mii(PG_FUNCTION_ARGS) DateADT dateVal = PG_GETARG_DATEADT(0); int32 days = PG_GETARG_INT32(1); + if (DATE_NOT_FINITE(dateVal)) + days = 0; /* can't change infinity */ + PG_RETURN_DATEADT(dateVal - days); } @@ -342,18 +388,25 @@ date2timestamp(DateADT dateVal) { Timestamp result; + if (DATE_IS_NOBEGIN(dateVal)) + TIMESTAMP_NOBEGIN(result); + else if (DATE_IS_NOEND(dateVal)) + TIMESTAMP_NOEND(result); + else + { #ifdef HAVE_INT64_TIMESTAMP - /* date is days since 2000, timestamp is microseconds since same... */ - result = dateVal * USECS_PER_DAY; - /* Date's range is wider than timestamp's, so must check for overflow */ - if (result / USECS_PER_DAY != dateVal) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + /* date is days since 2000, timestamp is microseconds since same... */ + result = dateVal * USECS_PER_DAY; + /* Date's range is wider than timestamp's, so check for overflow */ + if (result / USECS_PER_DAY != dateVal) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); #else - /* date is days since 2000, timestamp is seconds since same... */ - result = dateVal * (double) SECS_PER_DAY; + /* date is days since 2000, timestamp is seconds since same... */ + result = dateVal * (double) SECS_PER_DAY; #endif + } return result; } @@ -366,24 +419,30 @@ date2timestamptz(DateADT dateVal) *tm = &tt; int tz; - j2date(dateVal + POSTGRES_EPOCH_JDATE, - &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); - - tm->tm_hour = 0; - tm->tm_min = 0; - tm->tm_sec = 0; - tz = DetermineTimeZoneOffset(tm, session_timezone); + if (DATE_IS_NOBEGIN(dateVal)) + TIMESTAMP_NOBEGIN(result); + else if (DATE_IS_NOEND(dateVal)) + TIMESTAMP_NOEND(result); + else + { + j2date(dateVal + POSTGRES_EPOCH_JDATE, + &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday)); + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tz = DetermineTimeZoneOffset(tm, session_timezone); #ifdef HAVE_INT64_TIMESTAMP - result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC; - /* Date's range is wider than timestamp's, so must check for overflow */ - if ((result - tz * USECS_PER_SEC) / USECS_PER_DAY != dateVal) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("date out of range for timestamp"))); + result = dateVal * USECS_PER_DAY + tz * USECS_PER_SEC; + /* Date's range is wider than timestamp's, so check for overflow */ + if ((result - tz * USECS_PER_SEC) / USECS_PER_DAY != dateVal) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range for timestamp"))); #else - result = dateVal * (double) SECS_PER_DAY + tz; + result = dateVal * (double) SECS_PER_DAY + tz; #endif + } return result; } @@ -797,15 +856,19 @@ timestamp_date(PG_FUNCTION_ARGS) *tm = &tt; fsec_t fsec; - if (TIMESTAMP_NOT_FINITE(timestamp)) - PG_RETURN_NULL(); + if (TIMESTAMP_IS_NOBEGIN(timestamp)) + DATE_NOBEGIN(result); + else if (TIMESTAMP_IS_NOEND(timestamp)) + DATE_NOEND(result); + else + { + if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); - if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - - result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + } PG_RETURN_DATEADT(result); } @@ -840,15 +903,19 @@ timestamptz_date(PG_FUNCTION_ARGS) int tz; char *tzn; - if (TIMESTAMP_NOT_FINITE(timestamp)) - PG_RETURN_NULL(); + if (TIMESTAMP_IS_NOBEGIN(timestamp)) + DATE_NOBEGIN(result); + else if (TIMESTAMP_IS_NOEND(timestamp)) + DATE_NOEND(result); + else + { + if (timestamp2tm(timestamp, &tz, tm, &fsec, &tzn, NULL) != 0) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range"))); - if (timestamp2tm(timestamp, &tz, tm, &fsec, &tzn, NULL) != 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); - - result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + result = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE; + } PG_RETURN_DATEADT(result); } @@ -869,16 +936,19 @@ abstime_date(PG_FUNCTION_ARGS) switch (abstime) { case INVALID_ABSTIME: - case NOSTART_ABSTIME: - case NOEND_ABSTIME: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert reserved abstime value to date"))); + result = 0; /* keep compiler quiet */ + break; - /* - * pretend to drop through to make compiler think that result will - * be set - */ + case NOSTART_ABSTIME: + DATE_NOBEGIN(result); + break; + + case NOEND_ABSTIME: + DATE_NOEND(result); + break; default: abstime2tm(abstime, &tz, tm, NULL); @@ -1452,9 +1522,9 @@ datetime_timestamp(PG_FUNCTION_ARGS) TimeADT time = PG_GETARG_TIMEADT(1); Timestamp result; - result = DatumGetTimestamp(DirectFunctionCall1(date_timestamp, - DateADTGetDatum(date))); - result += time; + result = date2timestamp(date); + if (!TIMESTAMP_NOT_FINITE(result)) + result += time; PG_RETURN_TIMESTAMP(result); } @@ -2304,11 +2374,18 @@ datetimetz_timestamptz(PG_FUNCTION_ARGS) TimeTzADT *time = PG_GETARG_TIMETZADT_P(1); TimestampTz result; + if (DATE_IS_NOBEGIN(date)) + TIMESTAMP_NOBEGIN(result); + else if (DATE_IS_NOEND(date)) + TIMESTAMP_NOEND(result); + else + { #ifdef HAVE_INT64_TIMESTAMP - result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC; + result = date * USECS_PER_DAY + time->time + time->zone * USECS_PER_SEC; #else - result = date * (double) SECS_PER_DAY + time->time + time->zone; + result = date * (double) SECS_PER_DAY + time->time + time->zone; #endif + } PG_RETURN_TIMESTAMP(result); } diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 4ef1f97ea7..e728e1254f 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.78 2008/10/09 15:49:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.79 2008/10/14 17:12:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1632,6 +1632,11 @@ map_sql_value_to_xml_value(Datum value, Oid type) char buf[MAXDATELEN + 1]; date = DatumGetDateADT(value); + /* XSD doesn't support infinite values */ + if (DATE_NOT_FINITE(date)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("date out of range"))); j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); EncodeDateOnly(&tm, USE_XSD_DATES, buf); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 0ab03e0578..cd9d06f65d 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.497 2008/10/13 16:25:19 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.498 2008/10/14 17:12:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200810131 +#define CATALOG_VERSION_NO 200810141 #endif diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 70dc0a319a..9867f44f27 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.519 2008/10/13 16:25:20 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_proc.h,v 1.520 2008/10/14 17:12:33 tgl Exp $ * * NOTES * The script catalog/genbki.sh reads this file and generates .bki @@ -1772,6 +1772,8 @@ DESCR("date difference from today preserving months and years"); DATA(insert OID = 1388 ( timetz PGNSP PGUID 12 1 0 0 f f t f s 1 1266 "1184" _null_ _null_ _null_ timestamptz_timetz _null_ _null_ _null_ )); DESCR("convert timestamptz to timetz"); +DATA(insert OID = 1373 ( isfinite PGNSP PGUID 12 1 0 0 f f t f i 1 16 "1082" _null_ _null_ _null_ date_finite _null_ _null_ _null_ )); +DESCR("finite date?"); DATA(insert OID = 1389 ( isfinite PGNSP PGUID 12 1 0 0 f f t f i 1 16 "1184" _null_ _null_ _null_ timestamp_finite _null_ _null_ _null_ )); DESCR("finite timestamp?"); DATA(insert OID = 1390 ( isfinite PGNSP PGUID 12 1 0 0 f f t f i 1 16 "1186" _null_ _null_ _null_ interval_finite _null_ _null_ _null_ )); diff --git a/src/include/utils/date.h b/src/include/utils/date.h index 5f23549836..9015c3db69 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/date.h,v 1.40 2008/03/21 01:31:43 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/date.h,v 1.41 2008/10/14 17:12:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,6 +33,20 @@ typedef struct int32 zone; /* numeric time zone, in seconds */ } TimeTzADT; +/* + * Infinity and minus infinity must be the max and min values of DateADT. + * We could use INT_MIN and INT_MAX here, but seems better to not assume that + * int32 == int. + */ +#define DATEVAL_NOBEGIN ((DateADT) (-0x7fffffff - 1)) +#define DATEVAL_NOEND ((DateADT) 0x7fffffff) + +#define DATE_NOBEGIN(j) ((j) = DATEVAL_NOBEGIN) +#define DATE_IS_NOBEGIN(j) ((j) == DATEVAL_NOBEGIN) +#define DATE_NOEND(j) ((j) = DATEVAL_NOEND) +#define DATE_IS_NOEND(j) ((j) == DATEVAL_NOEND) +#define DATE_NOT_FINITE(j) (DATE_IS_NOBEGIN(j) || DATE_IS_NOEND(j)) + /* * Macros for fmgr-callable functions. * @@ -90,6 +104,7 @@ extern Datum date_le(PG_FUNCTION_ARGS); extern Datum date_gt(PG_FUNCTION_ARGS); extern Datum date_ge(PG_FUNCTION_ARGS); extern Datum date_cmp(PG_FUNCTION_ARGS); +extern Datum date_finite(PG_FUNCTION_ARGS); extern Datum date_larger(PG_FUNCTION_ARGS); extern Datum date_smaller(PG_FUNCTION_ARGS); extern Datum date_mi(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index a4acf3e9c9..b603745077 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -1157,3 +1157,30 @@ SELECT DATE_TRUNC('DECADE', DATE '0002-12-31 BC'); -- 0011-01-01 BC Mon Jan 01 00:00:00 0011 PST BC (1 row) +-- +-- test infinity +-- +select 'infinity'::date, '-infinity'::date; + date | date +----------+----------- + infinity | -infinity +(1 row) + +select 'infinity'::date > 'today'::date as t; + t +--- + t +(1 row) + +select '-infinity'::date < 'today'::date as t; + t +--- + t +(1 row) + +select isfinite('infinity'::date), isfinite('-infinity'::date), isfinite('today'::date); + isfinite | isfinite | isfinite +----------+----------+---------- + f | f | t +(1 row) + diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index 97ddbe9e45..d179ddf09b 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -269,3 +269,10 @@ SELECT DATE_TRUNC('CENTURY', DATE '0055-08-10 BC'); -- 0100-01-01 BC SELECT DATE_TRUNC('DECADE', DATE '1993-12-25'); -- 1990-01-01 SELECT DATE_TRUNC('DECADE', DATE '0004-12-25'); -- 0001-01-01 BC SELECT DATE_TRUNC('DECADE', DATE '0002-12-31 BC'); -- 0011-01-01 BC +-- +-- test infinity +-- +select 'infinity'::date, '-infinity'::date; +select 'infinity'::date > 'today'::date as t; +select '-infinity'::date < 'today'::date as t; +select isfinite('infinity'::date), isfinite('-infinity'::date), isfinite('today'::date);