Commit 05506fc4 authored by Tom Lane's avatar Tom Lane

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.
parent 9956ddc1
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.185 2008/02/17 02:09:28 tgl Exp $ * $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.186 2008/02/25 23:21:01 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -40,7 +40,10 @@ static int DecodeTime(char *str, int fmask, int *tmask, ...@@ -40,7 +40,10 @@ static int DecodeTime(char *str, int fmask, int *tmask,
struct pg_tm * tm, fsec_t *fsec); struct pg_tm * tm, fsec_t *fsec);
static int DecodeTimezone(char *str, int *tzp); static int DecodeTimezone(char *str, int *tzp);
static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
static int DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm); static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm * tm);
static int ValidateDate(int fmask, bool is2digits, bool bc,
struct pg_tm * tm);
static void TrimTrailingZeros(char *str); static void TrimTrailingZeros(char *str);
...@@ -805,7 +808,8 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -805,7 +808,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
} }
else else
{ {
dterr = DecodeDate(field[i], fmask, &tmask, tm); dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm);
if (dterr) if (dterr)
return dterr; return dterr;
} }
...@@ -1000,7 +1004,8 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -1000,7 +1004,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
/* Embedded decimal and no date yet? */ /* Embedded decimal and no date yet? */
if (cp != NULL && !(fmask & DTK_DATE_M)) if (cp != NULL && !(fmask & DTK_DATE_M))
{ {
dterr = DecodeDate(field[i], fmask, &tmask, tm); dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm);
if (dterr) if (dterr)
return dterr; return dterr;
} }
...@@ -1234,51 +1239,14 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -1234,51 +1239,14 @@ DecodeDateTime(char **field, int *ftype, int nf,
if (tmask & fmask) if (tmask & fmask)
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
fmask |= tmask; fmask |= tmask;
} } /* end loop over fields */
if (fmask & DTK_M(YEAR))
{
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
{
if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("inconsistent use of year %04d and \"BC\"",
tm->tm_year)));
}
else if (is2digits)
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
}
/* now that we have correct year, decode DOY */
if (fmask & DTK_M(DOY))
{
j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
/* check for valid month */
if (fmask & DTK_M(MONTH))
{
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
return DTERR_MD_FIELD_OVERFLOW;
}
/* minimal check for valid day */ /* do final checking/adjustment of Y/M/D fields */
if (fmask & DTK_M(DAY)) dterr = ValidateDate(fmask, is2digits, bc, tm);
{ if (dterr)
if (tm->tm_mday < 1 || tm->tm_mday > 31) return dterr;
return DTERR_MD_FIELD_OVERFLOW;
}
/* handle AM/PM */
if (mer != HR24 && tm->tm_hour > 12) if (mer != HR24 && tm->tm_hour > 12)
return DTERR_FIELD_OVERFLOW; return DTERR_FIELD_OVERFLOW;
if (mer == AM && tm->tm_hour == 12) if (mer == AM && tm->tm_hour == 12)
...@@ -1296,14 +1264,6 @@ DecodeDateTime(char **field, int *ftype, int nf, ...@@ -1296,14 +1264,6 @@ DecodeDateTime(char **field, int *ftype, int nf,
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
} }
/*
* Check for valid day of month, now that we know for sure the month
* and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems
* unlikely that "Feb 29" is a YMD-order error.
*/
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return DTERR_FIELD_OVERFLOW;
/* /*
* If we had a full timezone spec, compute the offset (we could not do * If we had a full timezone spec, compute the offset (we could not do
* it before, because we need the date to resolve DST status). * it before, because we need the date to resolve DST status).
...@@ -1486,6 +1446,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -1486,6 +1446,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
int val; int val;
int dterr; int dterr;
bool is2digits = FALSE; bool is2digits = FALSE;
bool bc = FALSE;
int mer = HR24; int mer = HR24;
pg_tz *namedTz = NULL; pg_tz *namedTz = NULL;
...@@ -1517,7 +1478,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -1517,7 +1478,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
if (i == 0 && nf >= 2 && if (i == 0 && nf >= 2 &&
(ftype[nf - 1] == DTK_DATE || ftype[1] == DTK_TIME)) (ftype[nf - 1] == DTK_DATE || ftype[1] == DTK_TIME))
{ {
dterr = DecodeDate(field[i], fmask, &tmask, tm); dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm);
if (dterr) if (dterr)
return dterr; return dterr;
} }
...@@ -1783,7 +1745,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -1783,7 +1745,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
*/ */
if (i == 0 && nf >= 2 && ftype[nf - 1] == DTK_DATE) if (i == 0 && nf >= 2 && ftype[nf - 1] == DTK_DATE)
{ {
dterr = DecodeDate(field[i], fmask, &tmask, tm); dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm);
if (dterr) if (dterr)
return dterr; return dterr;
} }
...@@ -1912,6 +1875,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -1912,6 +1875,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
mer = val; mer = val;
break; break;
case ADBC:
bc = (val == BC);
break;
case UNITS: case UNITS:
tmask = 0; tmask = 0;
ptype = val; ptype = val;
...@@ -1960,8 +1927,14 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -1960,8 +1927,14 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
if (tmask & fmask) if (tmask & fmask)
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
fmask |= tmask; fmask |= tmask;
} } /* end loop over fields */
/* do final checking/adjustment of Y/M/D fields */
dterr = ValidateDate(fmask, is2digits, bc, tm);
if (dterr)
return dterr;
/* handle AM/PM */
if (mer != HR24 && tm->tm_hour > 12) if (mer != HR24 && tm->tm_hour > 12)
return DTERR_FIELD_OVERFLOW; return DTERR_FIELD_OVERFLOW;
if (mer == AM && tm->tm_hour == 12) if (mer == AM && tm->tm_hour == 12)
...@@ -2047,10 +2020,15 @@ DecodeTimeOnly(char **field, int *ftype, int nf, ...@@ -2047,10 +2020,15 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
* Decode date string which includes delimiters. * Decode date string which includes delimiters.
* Return 0 if okay, a DTERR code if not. * Return 0 if okay, a DTERR code if not.
* *
* Insist on a complete set of fields. * str: field to be parsed
* fmask: bitmask for field types already seen
* *tmask: receives bitmask for fields found here
* *is2digits: set to TRUE if we find 2-digit year
* *tm: field values are stored into appropriate members of this struct
*/ */
static int static int
DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm * tm)
{ {
fsec_t fsec; fsec_t fsec;
int nf = 0; int nf = 0;
...@@ -2058,13 +2036,13 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) ...@@ -2058,13 +2036,13 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
len; len;
int dterr; int dterr;
bool haveTextMonth = FALSE; bool haveTextMonth = FALSE;
bool bc = FALSE;
bool is2digits = FALSE;
int type, int type,
val, val,
dmask = 0; dmask = 0;
char *field[MAXDATEFIELDS]; char *field[MAXDATEFIELDS];
*tmask = 0;
/* parse this string... */ /* parse this string... */
while (*str != '\0' && nf < MAXDATEFIELDS) while (*str != '\0' && nf < MAXDATEFIELDS)
{ {
...@@ -2090,14 +2068,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) ...@@ -2090,14 +2068,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
nf++; nf++;
} }
#if 0
/* don't allow too many fields */
if (nf > 3)
return DTERR_BAD_FORMAT;
#endif
*tmask = 0;
/* look first for text fields, since that will be unambiguous month */ /* look first for text fields, since that will be unambiguous month */
for (i = 0; i < nf; i++) for (i = 0; i < nf; i++)
{ {
...@@ -2115,10 +2085,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) ...@@ -2115,10 +2085,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
haveTextMonth = TRUE; haveTextMonth = TRUE;
break; break;
case ADBC:
bc = (val == BC);
break;
default: default:
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
} }
...@@ -2144,7 +2110,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) ...@@ -2144,7 +2110,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
dterr = DecodeNumber(len, field[i], haveTextMonth, fmask, dterr = DecodeNumber(len, field[i], haveTextMonth, fmask,
&dmask, tm, &dmask, tm,
&fsec, &is2digits); &fsec, is2digits);
if (dterr) if (dterr)
return dterr; return dterr;
...@@ -2158,23 +2124,38 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) ...@@ -2158,23 +2124,38 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M) if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
return DTERR_BAD_FORMAT; return DTERR_BAD_FORMAT;
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */ /* validation of the field values must wait until ValidateDate() */
if (bc)
{ return 0;
if (tm->tm_year > 0) }
tm->tm_year = -(tm->tm_year - 1);
else /* ValidateDate()
ereport(ERROR, * Check valid year/month/day values, handle BC and DOY cases
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), * Return 0 if okay, a DTERR code if not.
errmsg("inconsistent use of year %04d and \"BC\"", */
tm->tm_year))); static int
} ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
else if (is2digits) {
if (fmask & DTK_M(YEAR))
{ {
if (tm->tm_year < 70) /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
tm->tm_year += 2000; if (bc)
else if (tm->tm_year < 100) {
tm->tm_year += 1900; if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("inconsistent use of year %04d and \"BC\"",
tm->tm_year)));
}
else if (is2digits)
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
} }
/* now that we have correct year, decode DOY */ /* now that we have correct year, decode DOY */
...@@ -2185,16 +2166,29 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm) ...@@ -2185,16 +2166,29 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
} }
/* check for valid month */ /* check for valid month */
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR) if (fmask & DTK_M(MONTH))
return DTERR_MD_FIELD_OVERFLOW; {
if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
return DTERR_MD_FIELD_OVERFLOW;
}
/* check for valid day */ /* minimal check for valid day */
if (tm->tm_mday < 1 || tm->tm_mday > 31) if (fmask & DTK_M(DAY))
return DTERR_MD_FIELD_OVERFLOW; {
if (tm->tm_mday < 1 || tm->tm_mday > 31)
return DTERR_MD_FIELD_OVERFLOW;
}
/* We don't want to hint about DateStyle for Feb 29 */ if ((fmask & DTK_DATE_M) == DTK_DATE_M)
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]) {
return DTERR_FIELD_OVERFLOW; /*
* Check for valid day of month, now that we know for sure the month
* and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems
* unlikely that "Feb 29" is a YMD-order error.
*/
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return DTERR_FIELD_OVERFLOW;
}
return 0; return 0;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment