This patch fixes some problems in date handling for atypical dates.

Here is a summary:
Be more careful to check input string lengths as well as values
 when deciding whether a field is a year field.  Assume *anything* longer
 than 2 digits (if it isn't a special-case doy) is a valid year.
 This should fix the "Y1K" and "Y10K" problems
  pointed out by Massimo recently.
Check usage of BC to require a positive-valued year; before just used it
 to flip the sign of the year without checking. This led to problems
 near year zero.
Allow a 5 digit "concatenated date" of 2 digit year plus day of year.
Do 2->4 digit year correction for 6 and 5 digit "concatenated dates".
 Somehow forgot this originally. Guess not many folks use it...
Move common macros to dt.h.
REL6_4
Thomas G. Lockhart 27 years ago
parent a0071f1393
commit 2a6faa5368
  1. 7
      src/backend/utils/adt/datetime.c
  2. 227
      src/backend/utils/adt/dt.c

@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.25.2.1 1998/12/31 16:34:47 thomas Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.25.2.2 1999/02/13 05:59:34 thomas Exp $
*
*-------------------------------------------------------------------------
*/
@ -27,12 +27,13 @@
static int date2tm(DateADT dateVal, int *tzp, struct tm * tm, double *fsec, char **tzn);
#if 0
static int day_tab[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
#define isleap(y) (((y % 4) == 0 && (y % 100) != 0) || (y % 400) == 0)
#endif
#define UTIME_MINYEAR (1901)
#define UTIME_MINMONTH (12)
@ -99,10 +100,12 @@ date_in(char *str)
elog(ERROR, "Unrecognized date external representation %s", str);
}
#if 0
if (tm->tm_year < 0 || tm->tm_year > 32767)
elog(ERROR, "date_in: year must be limited to values 0 through 32767 in '%s'", str);
if (tm->tm_mon < 1 || tm->tm_mon > 12)
elog(ERROR, "date_in: month must be limited to values 1 through 12 in '%s'", str);
#endif
if (tm->tm_mday < 1 || tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
elog(ERROR, "date_in: day must be limited to values 1 through %d in '%s'",
day_tab[isleap(tm->tm_year)][tm->tm_mon - 1], str);

@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/Attic/dt.c,v 1.59.2.1 1998/12/31 16:34:48 thomas Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/Attic/dt.c,v 1.59.2.2 1999/02/13 05:59:34 thomas Exp $
*
*-------------------------------------------------------------------------
*/
@ -31,11 +31,12 @@
#endif
#include "utils/builtins.h"
static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
static int DecodeNumber(int flen, char *field,
int fmask, int *tmask, struct tm * tm, double *fsec);
int fmask, int *tmask, struct tm * tm, double *fsec, int *is2digits);
static int DecodeNumberField(int len, char *str,
int fmask, int *tmask, struct tm * tm, double *fsec);
int fmask, int *tmask, struct tm * tm, double *fsec, int *is2digits);
static int DecodeSpecial(int field, char *lowtoken, int *val);
static int DecodeTime(char *str, int fmask, int *tmask,
struct tm * tm, double *fsec);
@ -50,12 +51,20 @@ static double time2t(const int hour, const int min, const double sec);
static int timespan2tm(TimeSpan span, struct tm * tm, float8 *fsec);
static int tm2timespan(struct tm * tm, double fsec, TimeSpan *span);
#define USE_DATE_CACHE 1
#define ROUND_ALL 0
#if 0
#define isleap(y) (((y % 4) == 0) && (((y % 100) != 0) || ((y % 400) == 0)))
int mdays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
#endif
int day_tab[2][13] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}};
char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
@ -240,7 +249,7 @@ timespan_in(char *str)
case DTK_DELTA:
if (tm2timespan(tm, fsec, span) != 0)
{
#if FALSE
#if NOT_USED
TIMESPAN_INVALID(span);
#endif
elog(ERROR, "Bad timespan external representation '%s'", str);
@ -873,6 +882,7 @@ datetime_pl_span(DateTime *datetime, TimeSpan *span)
}
/* adjust for end of month boundary problems... */
#if 0
if (tm->tm_mday > mdays[tm->tm_mon - 1])
{
if ((tm->tm_mon == 2) && isleap(tm->tm_year))
@ -880,6 +890,9 @@ datetime_pl_span(DateTime *datetime, TimeSpan *span)
else
tm->tm_mday = mdays[tm->tm_mon - 1];
}
#endif
if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]);
#ifdef DATEDEBUG
printf("datetime_pl_span- date becomes %04d-%02d-%02d %02d:%02d:%02d\n",
@ -1184,16 +1197,22 @@ datetime_age(DateTime *datetime1, DateTime *datetime2)
{
if (dt1 < dt2)
{
#if 0
tm->tm_mday += mdays[tm1->tm_mon - 1];
if (isleap(tm1->tm_year) && (tm1->tm_mon == 2))
tm->tm_mday++;
#endif
tm->tm_mday += day_tab[isleap(tm1->tm_year)][tm1->tm_mon - 1];
tm->tm_mon--;
}
else
{
#if 0
tm->tm_mday += mdays[tm2->tm_mon - 1];
if (isleap(tm2->tm_year) && (tm2->tm_mon == 2))
tm->tm_mday++;
#endif
tm->tm_mday += day_tab[isleap(tm2->tm_year)][tm2->tm_mon - 1];
tm->tm_mon--;
}
}
@ -1393,7 +1412,7 @@ datetime_trunc(text *units, DateTime *datetime)
if (DATETIME_NOT_FINITE(*datetime))
{
#if FALSE
#if NOT_USED
/* should return null but Postgres doesn't like that currently. - tgl 97/06/12 */
elog(ERROR, "Datetime is not finite", NULL);
#endif
@ -1475,7 +1494,7 @@ datetime_trunc(text *units, DateTime *datetime)
if (tm2datetime(tm, fsec, &tz, result) != 0)
elog(ERROR, "Unable to truncate datetime to '%s'", lowunits);
#if FALSE
#if NOT_USED
}
else if ((type == RESERV) && (val == DTK_EPOCH))
{
@ -1533,7 +1552,7 @@ timespan_trunc(text *units, TimeSpan *timespan)
if (TIMESPAN_IS_INVALID(*timespan))
{
#if FALSE
#if NOT_USED
elog(ERROR, "Timespan is not finite", NULL);
#endif
result = NULL;
@ -1591,7 +1610,7 @@ timespan_trunc(text *units, TimeSpan *timespan)
result = NULL;
}
#if FALSE
#if NOT_USED
}
else if ((type == RESERV) && (val == DTK_EPOCH))
{
@ -1659,7 +1678,7 @@ datetime_part(text *units, DateTime *datetime)
if (DATETIME_NOT_FINITE(*datetime))
{
#if FALSE
#if NOT_USED
/* should return null but Postgres doesn't like that currently. - tgl 97/06/12 */
elog(ERROR, "Datetime is not finite", NULL);
#endif
@ -1824,7 +1843,7 @@ timespan_part(text *units, TimeSpan *timespan)
if (TIMESPAN_IS_INVALID(*timespan))
{
#if FALSE
#if NOT_USED
elog(ERROR, "Timespan is not finite", NULL);
#endif
*result = 0;
@ -1874,15 +1893,15 @@ timespan_part(text *units, TimeSpan *timespan)
break;
case DTK_DECADE:
*result = (tm->tm_year / 10) + 1;
*result = (tm->tm_year / 10);
break;
case DTK_CENTURY:
*result = (tm->tm_year / 100) + 1;
*result = (tm->tm_year / 100);
break;
case DTK_MILLENIUM:
*result = (tm->tm_year / 1000) + 1;
*result = (tm->tm_year / 1000);
break;
default:
@ -2036,7 +2055,7 @@ static datetkn datetktbl[] = {
{"adt", DTZ, NEG(18)}, /* Atlantic Daylight Time */
{"aesst", DTZ, 66}, /* E. Australia */
{"aest", TZ, 60}, /* Australia Eastern Std Time */
{"ahst", TZ, 60}, /* Alaska-Hawaii Std Time */
{"ahst", TZ, NEG(60)}, /* Alaska-Hawaii Std Time */
{"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
{"am", AMPM, AM},
{"apr", MONTH, 4},
@ -2087,12 +2106,12 @@ static datetkn datetktbl[] = {
{"hmt", DTZ, 18}, /* Hellas ? ? */
{"hst", TZ, NEG(60)}, /* Hawaii Std Time */
{"idle", TZ, 72}, /* Intl. Date Line, East */
{"idlw", TZ, NEG(72)}, /* Intl. Date Line,, est */
{"idlw", TZ, NEG(72)}, /* Intl. Date Line, West */
{LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */
{INVALID, RESERV, DTK_INVALID}, /* "invalid" reserved for invalid
* time */
{INVALID, RESERV, DTK_INVALID},
/* "invalid" reserved for invalid time */
{"ist", TZ, 12}, /* Israel */
{"it", TZ, 22}, /* Iran Time */
{"it", TZ, 21}, /* Iran Time */
{"jan", MONTH, 1},
{"january", MONTH, 1},
{"jst", TZ, 54}, /* Japan Std Time,USSR Zone 8 */
@ -2283,6 +2302,8 @@ datetkn *deltacache[MAXDATEFIELDS] = {NULL};
* These routines will be used by other date/time packages - tgl 97/02/25
*/
#if 0
XXX moved to dt.h - thomas 1999-01-15
/* Set the minimum year to one greater than the year of the first valid day
* to avoid having to check year and day both. - tgl 97/05/08
*/
@ -2294,6 +2315,7 @@ datetkn *deltacache[MAXDATEFIELDS] = {NULL};
#define IS_VALID_JULIAN(y,m,d) ((y > JULIAN_MINYEAR) \
|| ((y == JULIAN_MINYEAR) && ((m > JULIAN_MINMONTH) \
|| ((m == JULIAN_MINMONTH) && (d >= JULIAN_MINDAY)))))
#endif
int
date2j(int y, int m, int d)
@ -2432,7 +2454,7 @@ datetime2tm(DateTime dt, int *tzp, struct tm * tm, double *fsec, char **tzn)
tm->tm_mday = tx->tm_mday;
tm->tm_hour = tx->tm_hour;
tm->tm_min = tx->tm_min;
#if FALSE
#if NOT_USED
/* XXX HACK
* Argh! My Linux box puts in a 1 second offset for dates less than 1970
* but only if the seconds field was non-zero. So, don't copy the seconds
@ -2792,6 +2814,8 @@ DecodeDateTime(char **field, int *ftype, int nf,
int flen,
val;
int mer = HR24;
int haveTextMonth = FALSE;
int is2digits = FALSE;
int bc = FALSE;
*dtype = DTK_DATE;
@ -2839,15 +2863,18 @@ DecodeDateTime(char **field, int *ftype, int nf,
case DTK_NUMBER:
flen = strlen(field[i]);
if (flen > 4)
/* long numeric string and either no date or no time read yet?
* then interpret as a concatenated date or time... */
if ((flen > 4) && !((fmask & DTK_DATE_M) && (fmask & DTK_TIME_M)))
{
if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec) != 0)
if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0)
return -1;
}
/* otherwise it is a single date/time field... */
else
{
if (DecodeNumber(flen, field[i], fmask, &tmask, tm, fsec) != 0)
if (DecodeNumber(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0)
return -1;
}
break;
@ -2929,14 +2956,23 @@ DecodeDateTime(char **field, int *ftype, int nf,
#ifdef DATEDEBUG
printf("DecodeDateTime- month field %s value is %d\n", field[i], val);
#endif
/* already have a (numeric) month? then see if we can substitute... */
if ((fmask & DTK_M(MONTH)) && (! haveTextMonth)
&& (!(fmask & DTK_M(DAY)))
&& ((tm->tm_mon >= 1) && (tm->tm_mon <= 31)))
{
tm->tm_mday = tm->tm_mon;
tmask = DTK_M(DAY);
#ifdef DATEDEBUG
printf("DecodeNumber- misidentified month previously; assign as day %d\n", tm->tm_mday);
#endif
}
haveTextMonth = TRUE;
tm->tm_mon = val;
break;
/*
* daylight savings time modifier (solves "MET
* DST" syntax)
*/
case DTZMOD:
/* daylight savings time modifier (solves "MET DST" syntax) */
tmask |= DTK_M(DTZ);
tm->tm_isdst = 1;
if (tzp == NULL)
@ -3000,7 +3036,19 @@ DecodeDateTime(char **field, int *ftype, int nf,
/* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
if (bc)
tm->tm_year = -(tm->tm_year - 1);
{
if (tm->tm_year > 0)
tm->tm_year = -(tm->tm_year - 1);
else
elog(ERROR,"Inconsistant 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;
}
if ((mer != HR24) && (tm->tm_hour > 12))
return -1;
@ -3075,6 +3123,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct tm * tm, dou
int i;
int flen,
val;
int is2digits = FALSE;
int mer = HR24;
*dtype = DTK_TIME;
@ -3102,7 +3151,7 @@ DecodeTimeOnly(char **field, int *ftype, int nf, int *dtype, struct tm * tm, dou
case DTK_NUMBER:
flen = strlen(field[i]);
if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec) != 0)
if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0)
return -1;
break;
@ -3201,6 +3250,8 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
int nf = 0;
int i,
len;
int bc = FALSE;
int is2digits = FALSE;
int type,
val,
dmask = 0;
@ -3230,9 +3281,11 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
nf++;
}
#if 0
/* don't allow too many fields */
if (nf > 3)
return -1;
#endif
*tmask = 0;
@ -3255,6 +3308,10 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
tm->tm_mon = val;
break;
case ADBC:
bc = (val == BC);
break;
default:
#ifdef DATEDEBUG
printf("DecodeDate- illegal field %s value is %d\n", field[i], val);
@ -3281,7 +3338,7 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
if ((len = strlen(field[i])) <= 0)
return -1;
if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec) != 0)
if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec, &is2digits) != 0)
return -1;
if (fmask & dmask)
@ -3291,6 +3348,25 @@ DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
*tmask |= dmask;
}
if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
return -1;
/* 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
elog(ERROR,"Inconsistant 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;
}
return 0;
} /* DecodeDate() */
@ -3354,7 +3430,8 @@ DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
* Interpret numeric field as a date value in context.
*/
static int
DecodeNumber(int flen, char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
DecodeNumber(int flen, char *str, int fmask,
int *tmask, struct tm * tm, double *fsec, int *is2digits)
{
int val;
char *cp;
@ -3375,8 +3452,23 @@ DecodeNumber(int flen, char *str, int fmask, int *tmask, struct tm * tm, double
printf("DecodeNumber- %s is %d fmask=%08x tmask=%08x\n", str, val, fmask, *tmask);
#endif
/* enough digits to be unequivocal year? */
if (flen == 4)
/* Special case day of year? */
if ((flen == 3) && (fmask & DTK_M(YEAR))
&& ((val >= 1) && (val <= 366)))
{
*tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
tm->tm_yday = val;
j2date((date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1),
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
}
/* Enough digits to be unequivocal year?
* Used to test for 4 digits or more,
* but we now test first for a three-digit doy
* so anything bigger than two digits had better be
* an explicit year. - thomas 1999-01-09
*/
else if (flen > 2)
{
#ifdef DATEDEBUG
printf("DecodeNumber- match %d (%s) as year\n", val, str);
@ -3384,33 +3476,20 @@ DecodeNumber(int flen, char *str, int fmask, int *tmask, struct tm * tm, double
*tmask = DTK_M(YEAR);
/* already have a year? then see if we can substitute... */
if (fmask & DTK_M(YEAR))
if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(DAY)))
&& ((tm->tm_year >= 1) && (tm->tm_year <= 31)))
{
if ((!(fmask & DTK_M(DAY)))
&& ((tm->tm_year >= 1) && (tm->tm_year <= 31)))
{
tm->tm_mday = tm->tm_year;
*tmask = DTK_M(DAY);
#ifdef DATEDEBUG
printf("DecodeNumber- misidentified year previously; swap with day %d\n", tm->tm_mday);
printf("DecodeNumber- misidentified year previously; assign as day %d\n", tm->tm_mday);
#endif
tm->tm_mday = tm->tm_year;
*tmask = DTK_M(DAY);
}
}
tm->tm_year = val;
/* special case day of year? */
}
else if ((flen == 3) && (fmask & DTK_M(YEAR))
&& ((val >= 1) && (val <= 366)))
{
*tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
tm->tm_yday = val;
j2date((date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1),
&tm->tm_year, &tm->tm_mon, &tm->tm_mday);
/* already have year? then could be month */
}
/* already have year? then could be month */
else if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(MONTH)))
&& ((val >= 1) && (val <= 12)))
{
@ -3460,10 +3539,18 @@ DecodeNumber(int flen, char *str, int fmask, int *tmask, struct tm * tm, double
#endif
*tmask = DTK_M(YEAR);
tm->tm_year = val;
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
/* adjust ONLY if exactly two digits... */
#if 0
if (flen == 2)
{
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
}
#endif
*is2digits = (flen == 2);
}
else
@ -3477,7 +3564,8 @@ DecodeNumber(int flen, char *str, int fmask, int *tmask, struct tm * tm, double
* Interpret numeric string as a concatenated date field.
*/
static int
DecodeNumberField(int len, char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
DecodeNumberField(int len, char *str, int fmask,
int *tmask, struct tm * tm, double *fsec, int *is2digits)
{
char *cp;
@ -3527,9 +3615,36 @@ DecodeNumberField(int len, char *str, int fmask, int *tmask, struct tm * tm, dou
tm->tm_mon = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_year = atoi(str + 0);
#if 0
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
#endif
*is2digits = TRUE;
}
}
else if ((len == 5) && !(fmask & DTK_DATE_M))
{
#ifdef DATEDEBUG
printf("DecodeNumberField- %s is 5 characters fmask=%08x tmask=%08x\n", str, fmask, *tmask);
#endif
*tmask = DTK_DATE_M;
tm->tm_mday = atoi(str + 2);
*(str + 2) = '\0';
tm->tm_mon = 1;
tm->tm_year = atoi(str + 0);
#if 0
if (tm->tm_year < 70)
tm->tm_year += 2000;
else if (tm->tm_year < 100)
tm->tm_year += 1900;
#endif
*is2digits = TRUE;
}
else if (strchr(str, '.') != NULL)
{
#ifdef DATEDEBUG

Loading…
Cancel
Save