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.
This commit is contained in:
Tom Lane 2008-02-25 23:21:29 +00:00
parent 36cc9b7cea
commit caf9e44a86

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.137.4.4 2008/01/02 22:05:28 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/datetime.c,v 1.137.4.5 2008/02/25 23:21:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -36,7 +36,10 @@ static int DecodeTime(char *str, int fmask, int *tmask,
static int DecodeTimezone(char *str, int *tzp);
static int DecodePosixTimezone(char *str, int *tzp);
static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
static int DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm);
static int DecodeDate(char *str, int fmask, int *tmask, int *is2digits,
struct pg_tm * tm);
static int ValidateDate(int fmask, int is2digits, int bc,
struct pg_tm * tm);
static void TrimTrailingZeros(char *str);
@ -1068,7 +1071,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;
}
@ -1277,7 +1281,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;
}
@ -1503,56 +1508,19 @@ DecodeDateTime(char **field, int *ftype, int nf,
if (tmask & fmask)
return DTERR_BAD_FORMAT;
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;
}
}
/* do final checking/adjustment of Y/M/D fields */
dterr = ValidateDate(fmask, is2digits, bc, tm);
if (dterr)
return dterr;
/* 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 > 12)
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;
}
if ((mer != HR24) && (tm->tm_hour > 12))
/* handle AM/PM */
if (mer != HR24 && tm->tm_hour > 12)
return DTERR_FIELD_OVERFLOW;
if ((mer == AM) && (tm->tm_hour == 12))
if (mer == AM && tm->tm_hour == 12)
tm->tm_hour = 0;
else if ((mer == PM) && (tm->tm_hour != 12))
else if (mer == PM && tm->tm_hour != 12)
tm->tm_hour += 12;
/* do additional checking for full date specs... */
@ -1565,14 +1533,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;
/* timezone not specified? then find local timezone if possible */
if ((tzp != NULL) && (!(fmask & DTK_M(TZ))))
{
@ -1736,6 +1696,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
int val;
int dterr;
int is2digits = FALSE;
int bc = FALSE;
int mer = HR24;
*dtype = DTK_TIME;
@ -1767,7 +1728,8 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
&& ((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;
}
@ -2039,7 +2001,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;
}
@ -2168,6 +2131,10 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
mer = val;
break;
case ADBC:
bc = (val == BC);
break;
case UNITS:
tmask = 0;
ptype = val;
@ -2203,9 +2170,15 @@ DecodeTimeOnly(char **field, int *ftype, int nf,
if (tmask & fmask)
return DTERR_BAD_FORMAT;
fmask |= tmask;
}
} /* end loop over fields */
if ((mer != HR24) && (tm->tm_hour > 12))
/* 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))
tm->tm_hour = 0;
@ -2264,10 +2237,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, int *is2digits,
struct pg_tm * tm)
{
fsec_t fsec;
int nf = 0;
@ -2275,13 +2253,13 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
len;
int dterr;
bool haveTextMonth = FALSE;
int bc = FALSE;
int is2digits = FALSE;
int type,
val,
dmask = 0;
char *field[MAXDATEFIELDS];
*tmask = 0;
/* parse this string... */
while ((*str != '\0') && (nf < MAXDATEFIELDS))
{
@ -2307,14 +2285,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++)
{
@ -2332,10 +2302,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;
}
@ -2361,7 +2327,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;
@ -2375,23 +2341,38 @@ 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;
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
/* 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, int is2digits, int bc, struct pg_tm * tm)
{
if (fmask & DTK_M(YEAR))
{
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;
/* 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 */
@ -2402,16 +2383,29 @@ DecodeDate(char *str, int fmask, int *tmask, struct pg_tm * tm)
}
/* check for valid month */
if (tm->tm_mon < 1 || tm->tm_mon > 12)
return DTERR_MD_FIELD_OVERFLOW;
if (fmask & DTK_M(MONTH))
{
if (tm->tm_mon < 1 || tm->tm_mon > 12)
return DTERR_MD_FIELD_OVERFLOW;
}
/* check for valid day */
if (tm->tm_mday < 1 || tm->tm_mday > 31)
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;
}
/* We don't want to hint about DateStyle for Feb 29 */
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
return DTERR_FIELD_OVERFLOW;
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;
}