From: Thomas G. Lockhart Date: Sat, 13 Feb 1999 05:59:34 +0000 (+0000) Subject: This patch fixes some problems in date handling for atypical dates. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2a6faa5368f927e0cc22334ad35393351fec8ede;p=thirdparty%2Fpostgresql.git 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. --- diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index ac6552f4a47..a8cbdadf539 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.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); diff --git a/src/backend/utils/adt/dt.c b/src/backend/utils/adt/dt.c index 79f658c5947..3eb3463089d 100644 --- a/src/backend/utils/adt/dt.c +++ b/src/backend/utils/adt/dt.c @@ -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