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 @@
*
*
* 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,
struct pg_tm * tm, fsec_t *fsec);
static int DecodeTimezone(char *str, int *tzp);
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);
......@@ -805,7 +808,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
}
else
{
dterr = DecodeDate(field[i], fmask, &tmask, tm);
dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm);
if (dterr)
return dterr;
}
......@@ -1000,7 +1004,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
/* Embedded decimal and no date yet? */
if (cp != NULL && !(fmask & DTK_DATE_M))
{
dterr = DecodeDate(field[i], fmask, &tmask, tm);
dterr = DecodeDate(field[i], fmask,
&tmask, &is2digits, tm);
if (dterr)
return dterr;
}
......@@ -1234,51 +1239,14 @@ DecodeDateTime(char **field, int *ftype, int nf,
if (tmask & fmask)
return DTERR_BAD_FORMAT;
fmask |= tmask;
}
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;
}
}
} /* end loop over fields */
/* 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 */
if (fmask & DTK_M(DAY))
{
if (tm->tm_mday < 1 || tm->tm_mday > 31)
return DTERR_MD_FIELD_OVERFLOW;
}
/* 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)
return DTERR_FIELD_OVERFLOW;
if (mer == AM && tm->tm_hour == 12)
......@@ -1296,14 +1264,6 @@ DecodeDateTime(char **field, int *ftype, int nf,
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
* it before, because we need the date to resolve DST status).
......@@ -1486,6 +1446,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
int val;
int dterr;
bool is2digits = FALSE;
bool bc = FALSE;
int mer = HR24;
pg_tz *namedTz = NULL;
......@@ -1517,7 +1478,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
if (i == 0 && nf >= 2 &&
(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)
return dterr;
}
......@@ -1783,7 +1745,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
*/
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)
return dterr;
}
......@@ -1912,6 +1875,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
mer = val;
break;
case ADBC:
bc = (val == BC);
break;
case UNITS:
tmask = 0;
ptype = val;
......@@ -1960,8 +1927,14 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
if (tmask & fmask)
return DTERR_BAD_FORMAT;
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)
return DTERR_FIELD_OVERFLOW;
if (mer == AM && tm->tm_hour == 12)
......@@ -2047,10 +2020,15 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
* Decode date string which includes delimiters.
* 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
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;
int nf = 0;
......@@ -2058,13 +2036,13 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
len;
int dterr;
bool haveTextMonth = FALSE;
bool bc = FALSE;
bool is2digits = FALSE;
int type,
val,
dmask = 0;
char *field[MAXDATEFIELDS];
*tmask = 0;
/* parse this string... */
while (*str != '\0' && nf < MAXDATEFIELDS)
{
......@@ -2090,14 +2068,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
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 */
for (i = 0; i < nf; i++)
{
......@@ -2115,10 +2085,6 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
haveTextMonth = TRUE;
break;
case ADBC:
bc = (val == BC);
break;
default:
return DTERR_BAD_FORMAT;
}
......@@ -2144,7 +2110,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
dterr = DecodeNumber(len, field[i], haveTextMonth, fmask,
&dmask, tm,
&fsec, &is2digits);
&fsec, is2digits);
if (dterr)
return dterr;
......@@ -2158,6 +2124,20 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
return DTERR_BAD_FORMAT;
/* validation of the field values must wait until ValidateDate() */
return 0;
}
/* ValidateDate()
* Check valid year/month/day values, handle BC and DOY cases
* Return 0 if okay, a DTERR code if not.
*/
static int
ValidateDate(int fmask, bool is2digits, bool bc, struct pg_tm * tm)
{
if (fmask & DTK_M(YEAR))
{
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
{
......@@ -2176,6 +2156,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
}
/* now that we have correct year, decode DOY */
if (fmask & DTK_M(DOY))
......@@ -2185,16 +2166,29 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
}
/* 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;
}
/* check for valid day */
/* minimal check for valid day */
if (fmask & DTK_M(DAY))
{
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)
{
/*
* 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;
}
......
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