From 029dfdf1157b6d837a7b7211cd35b00c6bcd767c Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Wed, 7 Sep 2011 09:47:51 -0400
Subject: [PATCH] Fix to_date() and to_timestamp() to handle year masks of
 length < 4 so they wrap toward year 2020, rather than the inconsistent
 behavior we had before.

---
 doc/src/sgml/func.sgml             |  9 +++++
 src/backend/utils/adt/formatting.c | 58 ++++++++++++++++--------------
 2 files changed, 41 insertions(+), 26 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c03dd6c8a3e..0b6a109432e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -5548,6 +5548,15 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       If the year format specification is less than four digits, e.g.
+       <literal>YYY</>, and the supplied year is less than four digits,
+       the year will be adjusted to be nearest to the year 2020, e.g.
+       <literal>95</> becomes 1995.
+      </para>
+     </listitem>
+
      <listitem>
       <para>
        The <literal>YYYY</literal> conversion from string to <type>timestamp</type> or
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 726a1f45523..7efd9883628 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -964,6 +964,7 @@ static void dump_node(FormatNode *node, int max);
 
 static char *get_th(char *num, int type);
 static char *str_numth(char *dest, char *num, int type);
+static int	adjust_partial_year_to_2020(int year);
 static int	strspace_len(char *str);
 static int	strdigits_len(char *str);
 static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
@@ -1968,6 +1969,31 @@ is_next_separator(FormatNode *n)
 	return TRUE;				/* some non-digit input (separator) */
 }
 
+
+static int
+adjust_partial_year_to_2020(int year)
+{
+	/*
+	 * Adjust all dates toward 2020;  this is effectively what happens
+	 * when we assume '70' is 1970 and '69' is 2069.
+	 */
+	/* Force 0-69 into the 2000's */
+	if (year < 70)
+		return year + 2000;
+	/* Force 70-99 into the 1900's */
+	else if (year >= 70 && year < 100)
+		return year + 1900;
+	/* Force 100-519 into the 2000's */
+	else if (year >= 100 && year < 519)
+		return year + 2000;
+	/* Force 520-999 into the 1000's */
+	else if (year >= 520 && year < 1000)
+		return year + 1000;
+	else
+		return year;
+}
+
+
 static int
 strspace_len(char *str)
 {
@@ -2930,43 +2956,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
 				break;
 			case DCH_YYY:
 			case DCH_IYY:
-				from_char_parse_int(&out->year, &s, n);
+				if (from_char_parse_int(&out->year, &s, n) < 4)
+					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 3;
-
-				/*
-				 * 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
-				 * '099' = 2000 ... 2099
-				 */
-				if (out->year >= 100)
-					out->year += 1000;
-				else
-					out->year += 2000;
 				s += SKIP_THth(n->suffix);
 				break;
 			case DCH_YY:
 			case DCH_IY:
-				from_char_parse_int(&out->year, &s, n);
+				if (from_char_parse_int(&out->year, &s, n) < 4)
+					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 2;
-
-				/*
-				 * 2-digit year: '00' ... '69'	= 2000 ... 2069 '70' ... '99'
-				 * = 1970 ... 1999
-				 */
-				if (out->year < 70)
-					out->year += 2000;
-				else
-					out->year += 1900;
 				s += SKIP_THth(n->suffix);
 				break;
 			case DCH_Y:
 			case DCH_I:
-				from_char_parse_int(&out->year, &s, n);
+				if (from_char_parse_int(&out->year, &s, n) < 4)
+					out->year = adjust_partial_year_to_2020(out->year);
 				out->yysz = 1;
-
-				/*
-				 * 1-digit year: always +2000
-				 */
-				out->year += 2000;
 				s += SKIP_THth(n->suffix);
 				break;
 			case DCH_RM:
-- 
GitLab