Fix make_timestamp[tz] to accept negative years as meaning BC.
authorTom Lane <[email protected]>
Tue, 29 Sep 2020 17:48:06 +0000 (13:48 -0400)
committerTom Lane <[email protected]>
Tue, 29 Sep 2020 17:48:06 +0000 (13:48 -0400)
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
src/backend/utils/adt/timestamp.c
src/test/regress/expected/date.out
src/test/regress/expected/timestamp.out
src/test/regress/sql/date.sql
src/test/regress/sql/timestamp.sql

index 461b748d890bee643e04fc5224d2b7c93c332464..62dd7382303da61ef2205b5a241a7420670f7c61 100644 (file)
@@ -8939,6 +8939,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         </para>
         <para>
          Create date from year, month and day fields
+         (negative years signify BC)
         </para>
         <para>
          <literal>make_date(2013, 7, 15)</literal>
@@ -9004,6 +9005,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         </para>
         <para>
          Create timestamp from year, month, day, hour, minute and seconds fields
+         (negative years signify BC)
         </para>
         <para>
          <literal>make_timestamp(2013, 7, 15, 8, 15, 23.5)</literal>
@@ -9027,12 +9029,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
         </para>
         <para>
          Create timestamp with time zone from year, month, day, hour, minute
-         and seconds fields; if <parameter>timezone</parameter> is not
-         specified, the current time zone is used
+         and seconds fields (negative years signify BC).
+         If <parameter>timezone</parameter> is not
+         specified, the current time zone is used; the examples assume the
+         session time zone is <literal>Europe/London</literal>
         </para>
         <para>
          <literal>make_timestamptz(2013, 7, 15, 8, 15, 23.5)</literal>
          <returnvalue>2013-07-15 08:15:23.5+01</returnvalue>
+        </para>
+        <para>
+         <literal>make_timestamptz(2013, 7, 15, 8, 15, 23.5, 'America/New_York')</literal>
+         <returnvalue>2013-07-15 13:15:23.5+01</returnvalue>
         </para></entry>
        </row>
 
index 5fe304cea7530463c2aa2c3d13b483dfc00d869f..4128e3a739256cd25b96b88e32e25f1d82f1140e 100644 (file)
@@ -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,
index d035fe1f1e0a1aa36de2b69ba6dd924acdceda4a..1b921ce215b582575cbaec1dfccc1c66cd78bc8f 100644 (file)
@@ -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);
index 5f97505a30744d96b07650a7df20688bfbccd778..96551160901da797c017c8d9eeb563bcf55f0833 100644 (file)
@@ -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
index 488f5faa076ff64e7f48983dbf9e4ee3e1b912ba..7a734fb1a0562dd3f4bf584ce3c264ab034a2bdf 100644 (file)
@@ -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);
index 7b58c3cfa5fc9e38234eb1c6d17f8f4f2a1bf11d..727ee500845c7160ccb60f5821ed53644a7b13e3 100644 (file)
@@ -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);