From 9aae81527f3174b9b6fd6366f04435310903dab2 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Wed, 22 Sep 2010 23:48:07 -0400
Subject: [PATCH] Re-allow input of Julian dates prior to 0001-01-01 AD.

This was unintentionally broken in 8.4 while tightening up checking of
ordinary non-Julian date inputs to forbid references to "year zero".
Per bug #5672 from Benjamin Gigot.
---
 src/backend/utils/adt/datetime.c       | 37 +++++++++++++++++---------
 src/test/regress/expected/horology.out | 13 +++++++++
 src/test/regress/sql/horology.sql      |  3 +++
 3 files changed, 41 insertions(+), 12 deletions(-)

diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index b75ca09c63b..34d563605be 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -42,7 +42,7 @@ static int	DecodeTimezone(char *str, int *tzp);
 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
 static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
 		   struct pg_tm * tm);
-static int ValidateDate(int fmask, bool is2digits, bool bc,
+static int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
 			 struct pg_tm * tm);
 static void TrimTrailingZeros(char *str);
 static void AppendSeconds(char *cp, int sec, fsec_t fsec,
@@ -795,6 +795,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
 	int			dterr;
 	int			mer = HR24;
 	bool		haveTextMonth = FALSE;
+	bool		isjulian = FALSE;
 	bool		is2digits = FALSE;
 	bool		bc = FALSE;
 	pg_tz	   *namedTz = NULL;
@@ -833,10 +834,12 @@ DecodeDateTime(char **field, int *ftype, int nf,
 
 					errno = 0;
 					val = strtoi(field[i], &cp, 10);
-					if (errno == ERANGE)
+					if (errno == ERANGE || val < 0)
 						return DTERR_FIELD_OVERFLOW;
 
 					j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+					isjulian = TRUE;
+
 					/* Get the time zone from the end of the string */
 					dterr = DecodeTimezone(cp, tzp);
 					if (dterr)
@@ -1065,11 +1068,13 @@ DecodeDateTime(char **field, int *ftype, int nf,
 							break;
 
 						case DTK_JULIAN:
-							/***
-							 * previous field was a label for "julian date"?
-							 ***/
+							/* previous field was a label for "julian date" */
+							if (val < 0)
+								return DTERR_FIELD_OVERFLOW;
 							tmask = DTK_DATE_M;
 							j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+							isjulian = TRUE;
+
 							/* fractional Julian Day? */
 							if (*cp == '.')
 							{
@@ -1361,7 +1366,7 @@ DecodeDateTime(char **field, int *ftype, int nf,
 	}							/* end loop over fields */
 
 	/* do final checking/adjustment of Y/M/D fields */
-	dterr = ValidateDate(fmask, is2digits, bc, tm);
+	dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
 	if (dterr)
 		return dterr;
 
@@ -1564,6 +1569,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 	int			i;
 	int			val;
 	int			dterr;
+	bool		isjulian = FALSE;
 	bool		is2digits = FALSE;
 	bool		bc = FALSE;
 	int			mer = HR24;
@@ -1795,11 +1801,13 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 							break;
 
 						case DTK_JULIAN:
-							/***
-							 * previous field was a label for "julian date"?
-							 ***/
+							/* previous field was a label for "julian date" */
+							if (val < 0)
+								return DTERR_FIELD_OVERFLOW;
 							tmask = DTK_DATE_M;
 							j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
+							isjulian = TRUE;
+
 							if (*cp == '.')
 							{
 								double		time;
@@ -2045,7 +2053,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
 	}							/* end loop over fields */
 
 	/* do final checking/adjustment of Y/M/D fields */
-	dterr = ValidateDate(fmask, is2digits, bc, tm);
+	dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
 	if (dterr)
 		return dterr;
 
@@ -2247,11 +2255,16 @@ DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
  * Return 0 if okay, a DTERR code if not.
  */
 static int
-ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
+ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
+			 struct pg_tm * tm)
 {
 	if (fmask & DTK_M(YEAR))
 	{
-		if (bc)
+		if (isjulian)
+		{
+			/* tm_year is correct and should not be touched */
+		}
+		else if (bc)
 		{
 			/* there is no year zero in AD/BC notation */
 			if (tm->tm_year <= 0)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 26d7541b720..b13f7d7c5b5 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -264,6 +264,19 @@ SELECT time with time zone 'T040506.789 -08';
 (1 row)
 
 SET DateStyle = 'Postgres, MDY';
+-- Check Julian dates BC
+SELECT date 'J1520447' AS "Confucius' Birthday";
+ Confucius' Birthday 
+---------------------
+ 09-28-0551 BC
+(1 row)
+
+SELECT date 'J0' AS "Julian Epoch";
+ Julian Epoch  
+---------------
+ 11-24-4714 BC
+(1 row)
+
 --
 -- date, time arithmetic
 --
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 615755e3de0..97ff9f20c79 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -56,6 +56,9 @@ SELECT time with time zone 'T040506.789-08';
 SELECT time with time zone 'T040506.789 +08';
 SELECT time with time zone 'T040506.789 -08';
 SET DateStyle = 'Postgres, MDY';
+-- Check Julian dates BC
+SELECT date 'J1520447' AS "Confucius' Birthday";
+SELECT date 'J0' AS "Julian Epoch";
 
 --
 -- date, time arithmetic
-- 
GitLab