Skip to content

Commit 1f51400

Browse files
committed
Fix datetime input to behave correctly for Feb 29 in years BC.
Formerly, DecodeDate attempted to verify the day-of-the-month exactly, but it was under the misapprehension that it would know whether we were looking at a BC year or not. In reality this check can't be made until the calling function (eg DecodeDateTime) has processed all the fields. So, split the BC adjustment and validity checks out into a new function ValidateDate that is called only after processing all the fields. In passing, this patch makes DecodeTimeOnly work for BC inputs, which it never did before. (The historical veracity of all this is nonexistent, of course, but if we're going to say we support proleptic Gregorian calendar then we should do it correctly. In any case the unpatched code is broken because it could emit dates that it would then reject on re-inputting.) Per report from Bernd Helmle. Back-patch as far as 8.0; in 7.x we were not using our own calendar support and so this seems a bit too risky to put into 7.4.
1 parent 53c64ad commit 1f51400

File tree

1 file changed

+93
-99
lines changed

1 file changed

+93
-99
lines changed

src/backend/utils/adt/datetime.c

Lines changed: 93 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.184 2008/01/01 19:45:52 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.184.2.1 2008/02/25 23:21:08 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -40,7 +40,10 @@ static int DecodeTime(char *str, int fmask, int *tmask,
4040
struct pg_tm * tm, fsec_t *fsec);
4141
static int DecodeTimezone(char *str, int *tzp);
4242
static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
43-
static int DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm);
43+
static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
44+
struct pg_tm * tm);
45+
static int ValidateDate(int fmask, bool is2digits, bool bc,
46+
struct pg_tm * tm);
4447
static void TrimTrailingZeros(char *str);
4548

4649

@@ -803,7 +806,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
803806
}
804807
else
805808
{
806-
dterr = DecodeDate(field[i], fmask, &tmask, tm);
809+
dterr = DecodeDate(field[i], fmask,
810+
&tmask, &is2digits, tm);
807811
if (dterr)
808812
return dterr;
809813
}
@@ -998,7 +1002,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
9981002
/* Embedded decimal and no date yet? */
9991003
if (cp != NULL && !(fmask & DTK_DATE_M))
10001004
{
1001-
dterr = DecodeDate(field[i], fmask, &tmask, tm);
1005+
dterr = DecodeDate(field[i], fmask,
1006+
&tmask, &is2digits, tm);
10021007
if (dterr)
10031008
return dterr;
10041009
}
@@ -1232,51 +1237,14 @@ DecodeDateTime(char **field, int *ftype, int nf,
12321237
if (tmask & fmask)
12331238
return DTERR_BAD_FORMAT;
12341239
fmask |= tmask;
1235-
}
1236-
1237-
if (fmask & DTK_M(YEAR))
1238-
{
1239-
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
1240-
if (bc)
1241-
{
1242-
if (tm->tm_year > 0)
1243-
tm->tm_year = -(tm->tm_year - 1);
1244-
else
1245-
ereport(ERROR,
1246-
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
1247-
errmsg("inconsistent use of year %04d and \"BC\"",
1248-
tm->tm_year)));
1249-
}
1250-
else if (is2digits)
1251-
{
1252-
if (tm->tm_year < 70)
1253-
tm->tm_year += 2000;
1254-
else if (tm->tm_year < 100)
1255-
tm->tm_year += 1900;
1256-
}
1257-
}
1258-
1259-
/* now that we have correct year, decode DOY */
1260-
if (fmask & DTK_M(DOY))
1261-
{
1262-
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
1263-
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1264-
}
1265-
1266-
/* check for valid month */
1267-
if (fmask & DTK_M(MONTH))
1268-
{
1269-
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
1270-
return DTERR_MD_FIELD_OVERFLOW;
1271-
}
1240+
} /* end loop over fields */
12721241

1273-
/* minimal check for valid day */
1274-
if (fmask & DTK_M(DAY))
1275-
{
1276-
if (tm->tm_mday < 1 || tm->tm_mday > 31)
1277-
return DTERR_MD_FIELD_OVERFLOW;
1278-
}
1242+
/* do final checking/adjustment of Y/M/D fields */
1243+
dterr = ValidateDate(fmask, is2digits, bc, tm);
1244+
if (dterr)
1245+
return dterr;
12791246

1247+
/* handle AM/PM */
12801248
if (mer != HR24 && tm->tm_hour > 12)
12811249
return DTERR_FIELD_OVERFLOW;
12821250
if (mer == AM && tm->tm_hour == 12)
@@ -1294,14 +1262,6 @@ DecodeDateTime(char **field, int *ftype, int nf,
12941262
return DTERR_BAD_FORMAT;
12951263
}
12961264

1297-
/*
1298-
* Check for valid day of month, now that we know for sure the month
1299-
* and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems
1300-
* unlikely that "Feb 29" is a YMD-order error.
1301-
*/
1302-
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
1303-
return DTERR_FIELD_OVERFLOW;
1304-
13051265
/*
13061266
* If we had a full timezone spec, compute the offset (we could not do
13071267
* it before, because we need the date to resolve DST status).
@@ -1484,6 +1444,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
14841444
int val;
14851445
int dterr;
14861446
bool is2digits = FALSE;
1447+
bool bc = FALSE;
14871448
int mer = HR24;
14881449
pg_tz *namedTz = NULL;
14891450

@@ -1515,7 +1476,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
15151476
if (i == 0 && nf >= 2 &&
15161477
(ftype[nf - 1] == DTK_DATE || ftype[1] == DTK_TIME))
15171478
{
1518-
dterr = DecodeDate(field[i], fmask, &tmask, tm);
1479+
dterr = DecodeDate(field[i], fmask,
1480+
&tmask, &is2digits, tm);
15191481
if (dterr)
15201482
return dterr;
15211483
}
@@ -1781,7 +1743,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
17811743
*/
17821744
if (i == 0 && nf >= 2 && ftype[nf - 1] == DTK_DATE)
17831745
{
1784-
dterr = DecodeDate(field[i], fmask, &tmask, tm);
1746+
dterr = DecodeDate(field[i], fmask,
1747+
&tmask, &is2digits, tm);
17851748
if (dterr)
17861749
return dterr;
17871750
}
@@ -1910,6 +1873,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
19101873
mer = val;
19111874
break;
19121875

1876+
case ADBC:
1877+
bc = (val == BC);
1878+
break;
1879+
19131880
case UNITS:
19141881
tmask = 0;
19151882
ptype = val;
@@ -1958,8 +1925,14 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
19581925
if (tmask & fmask)
19591926
return DTERR_BAD_FORMAT;
19601927
fmask |= tmask;
1961-
}
1928+
} /* end loop over fields */
19621929

1930+
/* do final checking/adjustment of Y/M/D fields */
1931+
dterr = ValidateDate(fmask, is2digits, bc, tm);
1932+
if (dterr)
1933+
return dterr;
1934+
1935+
/* handle AM/PM */
19631936
if (mer != HR24 && tm->tm_hour > 12)
19641937
return DTERR_FIELD_OVERFLOW;
19651938
if (mer == AM && tm->tm_hour == 12)
@@ -2045,24 +2018,29 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
20452018
* Decode date string which includes delimiters.
20462019
* Return 0 if okay, a DTERR code if not.
20472020
*
2048-
* Insist on a complete set of fields.
2021+
* str: field to be parsed
2022+
* fmask: bitmask for field types already seen
2023+
* *tmask: receives bitmask for fields found here
2024+
* *is2digits: set to TRUE if we find 2-digit year
2025+
* *tm: field values are stored into appropriate members of this struct
20492026
*/
20502027
static int
2051-
DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
2028+
DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
2029+
struct pg_tm * tm)
20522030
{
20532031
fsec_t fsec;
20542032
int nf = 0;
20552033
int i,
20562034
len;
20572035
int dterr;
20582036
bool haveTextMonth = FALSE;
2059-
bool bc = FALSE;
2060-
bool is2digits = FALSE;
20612037
int type,
20622038
val,
20632039
dmask = 0;
20642040
char *field[MAXDATEFIELDS];
20652041

2042+
*tmask = 0;
2043+
20662044
/* parse this string... */
20672045
while (*str != '\0' && nf < MAXDATEFIELDS)
20682046
{
@@ -2088,14 +2066,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
20882066
nf++;
20892067
}
20902068

2091-
#if 0
2092-
/* don't allow too many fields */
2093-
if (nf > 3)
2094-
return DTERR_BAD_FORMAT;
2095-
#endif
2096-
2097-
*tmask = 0;
2098-
20992069
/* look first for text fields, since that will be unambiguous month */
21002070
for (i = 0; i < nf; i++)
21012071
{
@@ -2113,10 +2083,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
21132083
haveTextMonth = TRUE;
21142084
break;
21152085

2116-
case ADBC:
2117-
bc = (val == BC);
2118-
break;
2119-
21202086
default:
21212087
return DTERR_BAD_FORMAT;
21222088
}
@@ -2142,7 +2108,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
21422108

21432109
dterr = DecodeNumber(len, field[i], haveTextMonth, fmask,
21442110
&dmask, tm,
2145-
&fsec, &is2digits);
2111+
&fsec, is2digits);
21462112
if (dterr)
21472113
return dterr;
21482114

@@ -2156,23 +2122,38 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
21562122
if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
21572123
return DTERR_BAD_FORMAT;
21582124

2159-
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
2160-
if (bc)
2161-
{
2162-
if (tm->tm_year > 0)
2163-
tm->tm_year = -(tm->tm_year - 1);
2164-
else
2165-
ereport(ERROR,
2166-
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2167-
errmsg("inconsistent use of year %04d and \"BC\"",
2168-
tm->tm_year)));
2169-
}
2170-
else if (is2digits)
2125+
/* validation of the field values must wait until ValidateDate() */
2126+
2127+
return 0;
2128+
}
2129+
2130+
/* ValidateDate()
2131+
* Check valid year/month/day values, handle BC and DOY cases
2132+
* Return 0 if okay, a DTERR code if not.
2133+
*/
2134+
static int
2135+
ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
2136+
{
2137+
if (fmask & DTK_M(YEAR))
21712138
{
2172-
if (tm->tm_year < 70)
2173-
tm->tm_year += 2000;
2174-
else if (tm->tm_year < 100)
2175-
tm->tm_year += 1900;
2139+
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
2140+
if (bc)
2141+
{
2142+
if (tm->tm_year > 0)
2143+
tm->tm_year = -(tm->tm_year - 1);
2144+
else
2145+
ereport(ERROR,
2146+
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2147+
errmsg("inconsistent use of year %04d and \"BC\"",
2148+
tm->tm_year)));
2149+
}
2150+
else if (is2digits)
2151+
{
2152+
if (tm->tm_year < 70)
2153+
tm->tm_year += 2000;
2154+
else if (tm->tm_year < 100)
2155+
tm->tm_year += 1900;
2156+
}
21762157
}
21772158

21782159
/* now that we have correct year, decode DOY */
@@ -2183,16 +2164,29 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
21832164
}
21842165

21852166
/* check for valid month */
2186-
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
2187-
return DTERR_MD_FIELD_OVERFLOW;
2167+
if (fmask & DTK_M(MONTH))
2168+
{
2169+
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
2170+
return DTERR_MD_FIELD_OVERFLOW;
2171+
}
21882172

2189-
/* check for valid day */
2190-
if (tm->tm_mday < 1 || tm->tm_mday > 31)
2191-
return DTERR_MD_FIELD_OVERFLOW;
2173+
/* minimal check for valid day */
2174+
if (fmask & DTK_M(DAY))
2175+
{
2176+
if (tm->tm_mday < 1 || tm->tm_mday > 31)
2177+
return DTERR_MD_FIELD_OVERFLOW;
2178+
}
21922179

2193-
/* We don't want to hint about DateStyle for Feb 29 */
2194-
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
2195-
return DTERR_FIELD_OVERFLOW;
2180+
if ((fmask & DTK_DATE_M) == DTK_DATE_M)
2181+
{
2182+
/*
2183+
* Check for valid day of month, now that we know for sure the month
2184+
* and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems
2185+
* unlikely that "Feb 29" is a YMD-order error.
2186+
*/
2187+
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
2188+
return DTERR_FIELD_OVERFLOW;
2189+
}
21962190

21972191
return 0;
21982192
}

0 commit comments

Comments
 (0)