From a094c8ff53523e88ff9dd28ad467618039e27b58 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 29 Sep 2020 13:48:06 -0400 Subject: [PATCH] Fix make_timestamp[tz] to accept negative years as meaning BC. Previously we threw an error. But make_date already allowed the case, so it is inconsistent as well as unhelpful for make_timestamp not to. Both functions continue to reject year zero. Code and test fixes by Peter Eisentraut, doc changes by me Discussion: https://postgr.es/m/13c0992c-f15a-a0ca-d839-91d3efd965d9@2ndquadrant.com --- doc/src/sgml/func.sgml | 12 ++++++++++-- src/backend/utils/adt/timestamp.c | 14 +++++++++----- src/test/regress/expected/date.out | 2 ++ src/test/regress/expected/timestamp.out | 11 ++++++++++- src/test/regress/sql/date.sql | 1 + src/test/regress/sql/timestamp.sql | 5 ++++- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 461b748d89..62dd738230 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -8939,6 +8939,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); Create date from year, month and day fields + (negative years signify BC) make_date(2013, 7, 15) @@ -9004,6 +9005,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); Create timestamp from year, month, day, hour, minute and seconds fields + (negative years signify BC) make_timestamp(2013, 7, 15, 8, 15, 23.5) @@ -9027,12 +9029,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}'); Create timestamp with time zone from year, month, day, hour, minute - and seconds fields; if timezone is not - specified, the current time zone is used + and seconds fields (negative years signify BC). + If timezone is not + specified, the current time zone is used; the examples assume the + session time zone is Europe/London make_timestamptz(2013, 7, 15, 8, 15, 23.5) 2013-07-15 08:15:23.5+01 + + + make_timestamptz(2013, 7, 15, 8, 15, 23.5, 'America/New_York') + 2013-07-15 13:15:23.5+01 diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 5fe304cea7..4128e3a739 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -556,17 +556,21 @@ make_timestamp_internal(int year, int month, int day, TimeOffset date; TimeOffset time; int dterr; + bool bc = false; Timestamp result; tm.tm_year = year; tm.tm_mon = month; tm.tm_mday = day; - /* - * Note: we'll reject zero or negative year values. Perhaps negatives - * should be allowed to represent BC years? - */ - dterr = ValidateDate(DTK_DATE_M, false, false, false, &tm); + /* Handle negative years as BC */ + if (tm.tm_year < 0) + { + bc = true; + tm.tm_year = -tm.tm_year; + } + + dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm); if (dterr != 0) ereport(ERROR, diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index d035fe1f1e..1b921ce215 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -1607,6 +1607,8 @@ select make_time(8, 20, 0.0); (1 row) -- should fail +select make_date(0, 7, 15); +ERROR: date field value out of range: 0-07-15 select make_date(2013, 2, 30); ERROR: date field value out of range: 2013-02-30 select make_date(2013, 13, 1); diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index 5f97505a30..9655116090 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -1704,9 +1704,18 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff (4 rows) -- timestamp numeric fields constructor -SELECT make_timestamp(2014,12,28,6,30,45.887); +SELECT make_timestamp(2014, 12, 28, 6, 30, 45.887); make_timestamp ------------------------------ Sun Dec 28 06:30:45.887 2014 (1 row) +SELECT make_timestamp(-44, 3, 15, 12, 30, 15); + make_timestamp +----------------------------- + Fri Mar 15 12:30:15 0044 BC +(1 row) + +-- should fail +select make_timestamp(0, 7, 15, 12, 30, 15); +ERROR: date field value out of range: 0-07-15 diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index 488f5faa07..7a734fb1a0 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -378,6 +378,7 @@ select make_date(2013, 7, 15); select make_date(-44, 3, 15); select make_time(8, 20, 0.0); -- should fail +select make_date(0, 7, 15); select make_date(2013, 2, 30); select make_date(2013, 13, 1); select make_date(2013, 11, -1); diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql index 7b58c3cfa5..727ee50084 100644 --- a/src/test/regress/sql/timestamp.sql +++ b/src/test/regress/sql/timestamp.sql @@ -240,4 +240,7 @@ SELECT '' AS to_char_12, to_char(d, 'FF1 FF2 FF3 FF4 FF5 FF6 ff1 ff2 ff3 ff4 ff ) d(d); -- timestamp numeric fields constructor -SELECT make_timestamp(2014,12,28,6,30,45.887); +SELECT make_timestamp(2014, 12, 28, 6, 30, 45.887); +SELECT make_timestamp(-44, 3, 15, 12, 30, 15); +-- should fail +select make_timestamp(0, 7, 15, 12, 30, 15);