Skip to content

Commit 029dfdf

Browse files
committed
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.
1 parent 8eacb25 commit 029dfdf

File tree

2 files changed

+41
-26
lines changed

2 files changed

+41
-26
lines changed

doc/src/sgml/func.sgml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5548,6 +5548,15 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
55485548
</para>
55495549
</listitem>
55505550

5551+
<listitem>
5552+
<para>
5553+
If the year format specification is less than four digits, e.g.
5554+
<literal>YYY</>, and the supplied year is less than four digits,
5555+
the year will be adjusted to be nearest to the year 2020, e.g.
5556+
<literal>95</> becomes 1995.
5557+
</para>
5558+
</listitem>
5559+
55515560
<listitem>
55525561
<para>
55535562
The <literal>YYYY</literal> conversion from string to <type>timestamp</type> or

src/backend/utils/adt/formatting.c

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,7 @@ static void dump_node(FormatNode *node, int max);
964964

965965
static char *get_th(char *num, int type);
966966
static char *str_numth(char *dest, char *num, int type);
967+
static int adjust_partial_year_to_2020(int year);
967968
static int strspace_len(char *str);
968969
static int strdigits_len(char *str);
969970
static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode);
@@ -1968,6 +1969,31 @@ is_next_separator(FormatNode *n)
19681969
return TRUE; /* some non-digit input (separator) */
19691970
}
19701971

1972+
1973+
static int
1974+
adjust_partial_year_to_2020(int year)
1975+
{
1976+
/*
1977+
* Adjust all dates toward 2020; this is effectively what happens
1978+
* when we assume '70' is 1970 and '69' is 2069.
1979+
*/
1980+
/* Force 0-69 into the 2000's */
1981+
if (year < 70)
1982+
return year + 2000;
1983+
/* Force 70-99 into the 1900's */
1984+
else if (year >= 70 && year < 100)
1985+
return year + 1900;
1986+
/* Force 100-519 into the 2000's */
1987+
else if (year >= 100 && year < 519)
1988+
return year + 2000;
1989+
/* Force 520-999 into the 1000's */
1990+
else if (year >= 520 && year < 1000)
1991+
return year + 1000;
1992+
else
1993+
return year;
1994+
}
1995+
1996+
19711997
static int
19721998
strspace_len(char *str)
19731999
{
@@ -2930,43 +2956,23 @@ DCH_from_char(FormatNode *node, char *in, TmFromChar *out)
29302956
break;
29312957
case DCH_YYY:
29322958
case DCH_IYY:
2933-
from_char_parse_int(&out->year, &s, n);
2959+
if (from_char_parse_int(&out->year, &s, n) < 4)
2960+
out->year = adjust_partial_year_to_2020(out->year);
29342961
out->yysz = 3;
2935-
2936-
/*
2937-
* 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
2938-
* '099' = 2000 ... 2099
2939-
*/
2940-
if (out->year >= 100)
2941-
out->year += 1000;
2942-
else
2943-
out->year += 2000;
29442962
s += SKIP_THth(n->suffix);
29452963
break;
29462964
case DCH_YY:
29472965
case DCH_IY:
2948-
from_char_parse_int(&out->year, &s, n);
2966+
if (from_char_parse_int(&out->year, &s, n) < 4)
2967+
out->year = adjust_partial_year_to_2020(out->year);
29492968
out->yysz = 2;
2950-
2951-
/*
2952-
* 2-digit year: '00' ... '69' = 2000 ... 2069 '70' ... '99'
2953-
* = 1970 ... 1999
2954-
*/
2955-
if (out->year < 70)
2956-
out->year += 2000;
2957-
else
2958-
out->year += 1900;
29592969
s += SKIP_THth(n->suffix);
29602970
break;
29612971
case DCH_Y:
29622972
case DCH_I:
2963-
from_char_parse_int(&out->year, &s, n);
2973+
if (from_char_parse_int(&out->year, &s, n) < 4)
2974+
out->year = adjust_partial_year_to_2020(out->year);
29642975
out->yysz = 1;
2965-
2966-
/*
2967-
* 1-digit year: always +2000
2968-
*/
2969-
out->year += 2000;
29702976
s += SKIP_THth(n->suffix);
29712977
break;
29722978
case DCH_RM:

0 commit comments

Comments
 (0)