Skip to content

Commit ac78c41

Browse files
committed
Fix to_char(), to_date(), and to_timestamp() to handle negative/BC
century specifications just like positive/AD centuries. Previously the behavior was either wrong or inconsistent with positive/AD handling. Centuries without years now always assume the first year of the century, which is now documented.
1 parent fbcfa90 commit ac78c41

File tree

4 files changed

+39
-22
lines changed

4 files changed

+39
-22
lines changed

doc/src/sgml/func.sgml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5575,7 +5575,9 @@ SELECT SUBSTRING('XY1234Z', 'Y*?([0-9]{1,3})');
55755575
if there is a <literal>YYY</literal>, <literal>YYYY</literal> or
55765576
<literal>Y,YYY</literal> field. If <literal>CC</literal> is used with
55775577
<literal>YY</literal> or <literal>Y</literal> then the year is computed
5578-
as <literal>(CC-1)*100+YY</literal>.
5578+
as the year in the specified century. If the century is
5579+
specified but the year is not, the first year of the century
5580+
is assumed.
55795581
</para>
55805582
</listitem>
55815583

src/backend/utils/adt/formatting.c

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2640,8 +2640,15 @@ DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid col
26402640
case DCH_CC:
26412641
if (is_interval) /* straight calculation */
26422642
i = tm->tm_year / 100;
2643-
else /* century 21 starts in 2001 */
2644-
i = (tm->tm_year - 1) / 100 + 1;
2643+
else
2644+
{
2645+
if (tm->tm_year > 0)
2646+
/* Century 20 == 1901 - 2000 */
2647+
i = (tm->tm_year - 1) / 100 + 1;
2648+
else
2649+
/* Century 6BC == 600BC - 501BC */
2650+
i = tm->tm_year / 100 - 1;
2651+
}
26452652
if (i <= 99 && i >= -99)
26462653
sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, i);
26472654
else
@@ -3465,33 +3472,41 @@ do_to_timestamp(text *date_txt, text *fmt,
34653472
/*
34663473
* If CC and YY (or Y) are provided, use YY as 2 low-order digits for
34673474
* the year in the given century. Keep in mind that the 21st century
3468-
* runs from 2001-2100, not 2000-2099.
3469-
*
3470-
* If a 4-digit year is provided, we use that and ignore CC.
3475+
* AD runs from 2001-2100, not 2000-2099; 6th century BC runs from
3476+
* 600BC to 501BC.
34713477
*/
34723478
if (tmfc.cc && tmfc.yysz <= 2)
34733479
{
3480+
if (tmfc.bc)
3481+
tmfc.cc = -tmfc.cc;
34743482
tm->tm_year = tmfc.year % 100;
34753483
if (tm->tm_year)
3476-
tm->tm_year += (tmfc.cc - 1) * 100;
3484+
{
3485+
if (tmfc.cc >= 0)
3486+
tm->tm_year += (tmfc.cc - 1) * 100;
3487+
else
3488+
tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1;
3489+
}
34773490
else
3478-
tm->tm_year = tmfc.cc * 100;
3491+
/* find century year for dates ending in "00" */
3492+
tm->tm_year = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1);
34793493
}
34803494
else
3495+
/* If a 4-digit year is provided, we use that and ignore CC. */
3496+
{
34813497
tm->tm_year = tmfc.year;
3498+
if (tmfc.bc && tm->tm_year > 0)
3499+
tm->tm_year = -(tm->tm_year - 1);
3500+
}
34823501
}
3483-
else if (tmfc.cc) /* use first year of century */
3484-
tm->tm_year = (tmfc.cc - 1) * 100 + 1;
3485-
3486-
if (tmfc.bc)
3502+
else if (tmfc.cc) /* use first year of century */
34873503
{
3488-
if (tm->tm_year > 0)
3489-
tm->tm_year = -(tm->tm_year - 1);
3504+
if (tmfc.bc)
3505+
tmfc.cc = -tmfc.cc;
3506+
if (tmfc.cc >= 0)
3507+
tm->tm_year = (tmfc.cc - 1) * 100 + 1;
34903508
else
3491-
ereport(ERROR,
3492-
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3493-
errmsg("inconsistent use of year %04d and \"BC\"",
3494-
tm->tm_year)));
3509+
tm->tm_year = tmfc.cc * 100 + 1;
34953510
}
34963511

34973512
if (tmfc.j)

src/test/regress/expected/timestamp.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,7 @@ SELECT '' AS to_char_3, to_char(d1, 'Y,YYY YYYY YYY YY Y CC Q MM WW DDD DD D J')
992992
| 1,997 1997 997 97 7 20 1 02 07 045 14 6 2450494
993993
| 1,997 1997 997 97 7 20 1 02 07 046 15 7 2450495
994994
| 1,997 1997 997 97 7 20 1 02 07 047 16 1 2450496
995-
| 0,097 0097 097 97 7 01 1 02 07 047 16 3 1686042
995+
| 0,097 0097 097 97 7 -1 1 02 07 047 16 3 1686042
996996
| 0,097 0097 097 97 7 01 1 02 07 047 16 7 1756536
997997
| 0,597 0597 597 97 7 06 1 02 07 047 16 5 1939157
998998
| 1,097 1097 097 97 7 11 1 02 07 047 16 3 2121778
@@ -1063,7 +1063,7 @@ SELECT '' AS to_char_4, to_char(d1, 'FMY,YYY FMYYYY FMYYY FMYY FMY FMCC FMQ FMMM
10631063
| 1,997 1997 997 97 7 20 1 2 7 45 14 6 2450494
10641064
| 1,997 1997 997 97 7 20 1 2 7 46 15 7 2450495
10651065
| 1,997 1997 997 97 7 20 1 2 7 47 16 1 2450496
1066-
| 0,097 97 97 97 7 1 1 2 7 47 16 3 1686042
1066+
| 0,097 97 97 97 7 -1 1 2 7 47 16 3 1686042
10671067
| 0,097 97 97 97 7 1 1 2 7 47 16 7 1756536
10681068
| 0,597 597 597 97 7 6 1 2 7 47 16 5 1939157
10691069
| 1,097 1097 97 97 7 11 1 2 7 47 16 3 2121778

src/test/regress/expected/timestamptz.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ SELECT '' AS to_char_3, to_char(d1, 'Y,YYY YYYY YYY YY Y CC Q MM WW DDD DD D J')
10741074
| 1,997 1997 997 97 7 20 1 02 07 045 14 6 2450494
10751075
| 1,997 1997 997 97 7 20 1 02 07 046 15 7 2450495
10761076
| 1,997 1997 997 97 7 20 1 02 07 047 16 1 2450496
1077-
| 0,097 0097 097 97 7 01 1 02 07 047 16 3 1686042
1077+
| 0,097 0097 097 97 7 -1 1 02 07 047 16 3 1686042
10781078
| 0,097 0097 097 97 7 01 1 02 07 047 16 7 1756536
10791079
| 0,597 0597 597 97 7 06 1 02 07 047 16 5 1939157
10801080
| 1,097 1097 097 97 7 11 1 02 07 047 16 3 2121778
@@ -1146,7 +1146,7 @@ SELECT '' AS to_char_4, to_char(d1, 'FMY,YYY FMYYYY FMYYY FMYY FMY FMCC FMQ FMMM
11461146
| 1,997 1997 997 97 7 20 1 2 7 45 14 6 2450494
11471147
| 1,997 1997 997 97 7 20 1 2 7 46 15 7 2450495
11481148
| 1,997 1997 997 97 7 20 1 2 7 47 16 1 2450496
1149-
| 0,097 97 97 97 7 1 1 2 7 47 16 3 1686042
1149+
| 0,097 97 97 97 7 -1 1 2 7 47 16 3 1686042
11501150
| 0,097 97 97 97 7 1 1 2 7 47 16 7 1756536
11511151
| 0,597 597 597 97 7 6 1 2 7 47 16 5 1939157
11521152
| 1,097 1097 97 97 7 11 1 2 7 47 16 3 2121778

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy