summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2008-10-14 17:12:33 +0000
committerTom Lane2008-10-14 17:12:33 +0000
commitd4c0fd5381662c2694a96c16e750c04e8cbd19e5 (patch)
treec1c7db29c0f95824770200e7742fee30b98f0ed0
parent87c1e25825d59476fcdf5f9aab816a55268e52c3 (diff)
Extend the date type to support infinity and -infinity, analogously to
the timestamp types. Turns out this doesn't even reduce the available range of dates, since the restriction to dates that work for Julian-date arithmetic is much tighter than the int32 range anyway. Per a longstanding TODO item.
-rw-r--r--doc/src/sgml/datatype.sgml4
-rw-r--r--doc/src/sgml/func.sgml10
-rw-r--r--src/backend/utils/adt/date.c185
-rw-r--r--src/backend/utils/adt/xml.c5
-rw-r--r--src/include/catalog/catversion.h2
-rw-r--r--src/include/catalog/pg_proc.h2
-rw-r--r--src/include/utils/date.h15
-rw-r--r--src/test/regress/expected/date.out27
-rw-r--r--src/test/regress/sql/date.sql7
9 files changed, 199 insertions, 58 deletions
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 04675e7f0e..b1680e972f 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2032,12 +2032,12 @@ January 8 04:05:06 1999 PST
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 425a6f1f99..5c6814a3c6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5913,9 +5913,17 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
</row>
<row>
+ <entry><literal><function>isfinite</function>(<type>date</type>)</literal></entry>
+ <entry><type>boolean</type></entry>
+ <entry>Test for finite date (not +/-infinity)</entry>
+ <entry><literal>isfinite(date '2001-02-16')</literal></entry>
+ <entry><literal>true</literal></entry>
+ </row>
+
+ <row>
<entry><literal><function>isfinite</function>(<type>timestamp</type>)</literal></entry>
<entry><type>boolean</type></entry>
- <entry>Test for finite time stamp (not equal to infinity)</entry>
+ <entry>Test for finite time stamp (not +/-infinity)</entry>
<entry><literal>isfinite(timestamp '2001-02-16 21:28:30')</literal></entry>
<entry><literal>true</literal></entry>
</row>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 2f814e5230..57af750289 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -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
@@ -281,6 +308,14 @@ date_cmp(PG_FUNCTION_ARGS)
}
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)
{
DateADT dateVal1 = PG_GETARG_DATEADT(0);
@@ -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 (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ 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")));
- 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 (timestamp2tm(timestamp, &tz, tm, &fsec, &tzn, NULL) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ 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")));
- 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 c06bcfec95..552594cc44 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -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 b4c1c2a83d..4a08434cc8 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -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 0540a353e9..30c5132335 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -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 d85c5bc99c..f95cf7b44f 100644
--- a/src/include/utils/date.h
+++ b/src/include/utils/date.h
@@ -34,6 +34,20 @@ typedef struct
} 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.
*
* For TimeADT, we make use of the same support routines as for float8 or int64.
@@ -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);