commit 553a298004352baacdac2b91c5c762947730e83c
Author: David G. Johnston <david.g.johnston@gmail.com>
Date:   Wed Jul 15 16:11:08 2020 +0000

    Document BC date construction behaviors
    
    Bug# 16419 complains that to_date('-1-01-01','yyyy-mm-dd') returns
    '0002-01-01 BC' violating POLA.
    
    Add tests and documentation describing how the to_* and make_* functions
    behave when given input <= 0.
    
    This documents two bugs:
    
    Our reference implementation for to_date doesn't accept zero as a
    year nor shifts negative years one year earlier.
    
    make_timestamp will not accept a negative year while make_date will.
    
    The second bug seems worth fixing and back-patching in lieu of the
    documentation change herein.
    
    The first bug seems worth fixing in HEAD while back-patching
    that portion of this patch.
    
    I (DGJ) choose this approach (with suggested  back-patch) as changing
    the behaviors is outside of my current skillset.

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 9d71678029..4c86dbfb06 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -7720,6 +7720,18 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
       </caution>
      </listitem>
 
+     <listitem>
+      <para>
+       In <function>to_timestamp</function> and <function>to_date</function>,
+       the year <literal>0</literal> is an accepted input and all dates less 
+       than or equal to <literal>0</literal> are treated as BC dates.  Since
+       the year <literal>0 BC</literal> is not defined, and the representation
+       for BC dates is to show a positive year and the BC indicator, the 
+       conversion for inputs &lt;= 0 is effectively
+       <literal>abs(input - 1) || 'BC'</literal>.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        In <function>to_timestamp</function>, millisecond
@@ -9087,6 +9099,13 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
       </tbody>
      </tgroup>
     </table>
+  
+   <warning>
+    <para>
+     The <literal>make_timestamp</literal> function does not accept negative years or,
+     otherwise provide a way to indicate BC, so it can only be used to construct AD dates.
+    </para>
+   </warning>
 
    <para>
     <indexterm>
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index 4cdf1635f2..136d1567f8 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -1449,3 +1449,54 @@ select make_time(10, 55, 100.1);
 ERROR:  time field value out of range: 10:55:100.1
 select make_time(24, 0, 2.1);
 ERROR:  time field value out of range: 24:00:2.1
+-- Behavior around "year 0"
+SELECT to_date('0'::text ,'YYYY'); -- BUG: Should fail per our reference implementation
+    to_date    
+---------------
+ 01-01-0001 BC
+(1 row)
+
+SELECT to_date('-1'::text ,'YYYY'); -- And this should result in the year -1 BC (Jan 1)
+    to_date    
+---------------
+ 01-01-0002 BC
+(1 row)
+
+select make_date(1, 1, 1);
+ make_date  
+------------
+ 01-01-0001
+(1 row)
+
+select make_date(1, 0, 0);
+ERROR:  date field value out of range: 1-00-00
+select make_date(0, 0, 0);
+ERROR:  date field value out of range: 0-00-00
+select make_date(0, 1, 1);
+ERROR:  date field value out of range: 0-01-01
+select make_date(-1, 1, 1);
+   make_date   
+---------------
+ 01-01-0001 BC
+(1 row)
+
+SELECT date '1-01-01 BC';
+     date      
+---------------
+ 01-01-0001 BC
+(1 row)
+
+SELECT date '-1-01-01';
+ERROR:  invalid input syntax for type date: "-1-01-01"
+LINE 1: SELECT date '-1-01-01';
+                    ^
+SELECT date '-0001-01-01';
+ERROR:  invalid input syntax for type date: "-0001-01-01"
+LINE 1: SELECT date '-0001-01-01';
+                    ^
+SELECT date_part('year', '0001-01-01 BC'::date);
+ date_part 
+-----------
+        -1
+(1 row)
+
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 639b50308e..f6dd324e62 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2501,6 +2501,41 @@ SELECT to_timestamp('-Infinity'::float);
 
 SELECT to_timestamp('NaN'::float);
 ERROR:  timestamp cannot be NaN
+-- Behavior around "year 0"
+SELECT to_timestamp('0'::text, 'YYYY'); -- BUG: Should fail per reference implementation
+          to_timestamp           
+---------------------------------
+ Sat Jan 01 00:00:00 0001 UTC BC
+(1 row)
+
+SELECT to_timestamp('-1'::text, 'YYYY');
+          to_timestamp           
+---------------------------------
+ Fri Jan 01 00:00:00 0002 UTC BC
+(1 row)
+
+SELECT make_timestamptz(1, 1, 1, 0, 0, 0);
+       make_timestamptz       
+------------------------------
+ Mon Jan 01 00:00:00 0001 UTC
+(1 row)
+
+SELECT make_timestamptz(0, 0, 0, 0, 0, 0);
+ERROR:  date field value out of range: 0-00-00
+SELECT make_timestamptz(0, 1, 1, 0, 0, 0);
+ERROR:  date field value out of range: 0-01-01
+SELECT make_timestamptz(-1, 1, 1, 0, 0, 0); -- BUG: make_date(-1, 1, 1) works
+ERROR:  date field value out of range: -1-01-01
+SELECT timestamptz '-1-01-01 00:00:00';
+ERROR:  invalid input syntax for type timestamp with time zone: "-1-01-01 00:00:00"
+LINE 1: SELECT timestamptz '-1-01-01 00:00:00';
+                           ^
+SELECT timestamptz '1-01-01 00:00:00 BC';
+           timestamptz           
+---------------------------------
+ Sat Jan 01 00:00:00 0001 UTC BC
+(1 row)
+
 SET TimeZone to 'Europe/Moscow';
 SELECT '2011-03-26 21:00:00 UTC'::timestamptz;
          timestamptz          
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 1c3adf70ce..788ae11ee5 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -334,9 +334,23 @@ SELECT EXTRACT(MICROSEC  FROM DATE 'infinity');     -- ERROR:  timestamp units "
 select make_date(2013, 7, 15);
 select make_date(-44, 3, 15);
 select make_time(8, 20, 0.0);
+
 -- should fail
 select make_date(2013, 2, 30);
 select make_date(2013, 13, 1);
 select make_date(2013, 11, -1);
 select make_time(10, 55, 100.1);
 select make_time(24, 0, 2.1);
+
+-- Behavior around "year 0"
+SELECT to_date('0'::text ,'YYYY'); -- BUG: Should fail per our reference implementation
+SELECT to_date('-1'::text ,'YYYY'); -- And this should result in the year -1 BC (Jan 1)
+select make_date(1, 1, 1);
+select make_date(1, 0, 0);
+select make_date(0, 0, 0);
+select make_date(0, 1, 1);
+select make_date(-1, 1, 1);
+SELECT date '1-01-01 BC';
+SELECT date '-1-01-01';
+SELECT date '-0001-01-01';
+SELECT date_part('year', '0001-01-01 BC'::date);
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 300302dafd..f36dea33bf 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -428,6 +428,15 @@ SELECT to_timestamp(' Infinity'::float);
 SELECT to_timestamp('-Infinity'::float);
 SELECT to_timestamp('NaN'::float);
 
+-- Behavior around "year 0"
+SELECT to_timestamp('0'::text, 'YYYY'); -- BUG: Should fail per reference implementation
+SELECT to_timestamp('-1'::text, 'YYYY');
+SELECT make_timestamptz(1, 1, 1, 0, 0, 0);
+SELECT make_timestamptz(0, 0, 0, 0, 0, 0);
+SELECT make_timestamptz(0, 1, 1, 0, 0, 0);
+SELECT make_timestamptz(-1, 1, 1, 0, 0, 0); -- BUG: make_date(-1, 1, 1) works
+SELECT timestamptz '-1-01-01 00:00:00';
+SELECT timestamptz '1-01-01 00:00:00 BC';
 
 SET TimeZone to 'Europe/Moscow';
 
