RE: Proposed strftime.c changes for long years
Based on Paul Eggert's feedback, below find an updated set of changes to avoid problems with overflow when TM_YEAR_BASE is added to tm_year. I eliminated the tm_year #define that was designed to catch coding problems; I moved the asctime_r define to private.h (and it is now conditionalized); I changed strftime's handling of the 'C' format to use _conv rather than _lconv; and I added a note to strftime about the need to figure out what to do when a format asks for the last two digits of a year (or the century of a year) and the year is negative (or less than 100). --ado ------- private.h ------- *** /tmp/geta29972 Thu Sep 9 11:48:56 2004 --- /tmp/getb29972 Thu Sep 9 11:48:56 2004 *************** *** 21,27 **** #ifndef lint #ifndef NOID ! static char privatehid[] = "@(#)private.h 7.53"; #endif /* !defined NOID */ #endif /* !defined lint */ --- 21,27 ---- #ifndef lint #ifndef NOID ! static char privatehid[] = "@(#)private.h 7.54"; #endif /* !defined NOID */ #endif /* !defined lint */ *************** *** 190,200 **** --- 190,211 ---- ** But some newer errno.h implementations define it as a macro. ** Fix the former without affecting the latter. */ + #ifndef errno extern int errno; #endif /* !defined errno */ /* + ** Some time.h implementations don't declare asctime_r. + ** Others might define it as a macro. + ** Fix the former without affecting the latter. + */ + + #ifndef asctime_r + extern char * asctime_r(); + #endif + + /* ** Private function declarations. */ char * icalloc P((int nelem, int elsize)); ------- localtime.c ------- *** /tmp/geta29991 Thu Sep 9 11:48:56 2004 --- /tmp/getb29991 Thu Sep 9 11:48:56 2004 *************** *** 5,11 **** #ifndef lint #ifndef NOID ! static char elsieid[] = "@(#)localtime.c 7.78"; #endif /* !defined NOID */ #endif /* !defined lint */ --- 5,11 ---- #ifndef lint #ifndef NOID ! static char elsieid[] = "@(#)localtime.c 7.80"; #endif /* !defined NOID */ #endif /* !defined lint */ *************** *** 134,139 **** --- 134,142 ---- static void localsub P((const time_t * timep, long offset, struct tm * tmp)); static int increment_overflow P((int * number, int delta)); + static int long_increment_overflow P((long * number, int delta)); + static int long_normalize_overflow P((long * tensptr, + int * unitsptr, int base)); static int normalize_overflow P((int * tensptr, int * unitsptr, int base)); static void settzname P((void)); *************** *** 1149,1155 **** register const struct lsinfo * lp; register long days; register long rem; ! register int y; register int yleap; register const int * ip; register long corr; --- 1152,1158 ---- register const struct lsinfo * lp; register long days; register long rem; ! register long y; register int yleap; register const int * ip; register long corr; *************** *** 1218,1224 **** y = EPOCH_YEAR; #define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400) while (days < 0 || days >= (long) year_lengths[yleap = isleap(y)]) { ! register int newy; newy = y + days / DAYSPERNYEAR; if (days < 0) --- 1221,1227 ---- y = EPOCH_YEAR; #define LEAPS_THRU_END_OF(y) ((y) / 4 - (y) / 100 + (y) / 400) while (days < 0 || days >= (long) year_lengths[yleap = isleap(y)]) { ! register long newy; newy = y + days / DAYSPERNYEAR; if (days < 0) *************** *** 1294,1299 **** --- 1297,1314 ---- } static int + long_increment_overflow(number, delta) + long * number; + int delta; + { + long number0; + + number0 = *number; + *number += delta; + return (*number < number0) != (delta < 0); + } + + static int normalize_overflow(tensptr, unitsptr, base) int * const tensptr; int * const unitsptr; *************** *** 1309,1314 **** --- 1324,1344 ---- } static int + long_normalize_overflow(tensptr, unitsptr, base) + long * const tensptr; + int * const unitsptr; + const int base; + { + register int tensdelta; + + tensdelta = (*unitsptr >= 0) ? + (*unitsptr / base) : + (-1 - (-1 - *unitsptr) / base); + *unitsptr -= tensdelta * base; + return long_increment_overflow(tensptr, tensdelta); + } + + static int tmcomp(atmp, btmp) register const struct tm * const atmp; register const struct tm * const btmp; *************** *** 1335,1342 **** register const struct state * sp; register int dir; register int bits; ! register int i, j ; register int saved_seconds; time_t newt; time_t t; struct tm yourtm, mytm; --- 1365,1374 ---- register const struct state * sp; register int dir; register int bits; ! register int i, j; register int saved_seconds; + register long li; + long y; time_t newt; time_t t; struct tm yourtm, mytm; *************** *** 1352,1393 **** return WRONG; if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) return WRONG; ! if (normalize_overflow(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR)) return WRONG; /* ! ** Turn yourtm.tm_year into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ ! if (increment_overflow(&yourtm.tm_year, TM_YEAR_BASE)) return WRONG; while (yourtm.tm_mday <= 0) { ! if (increment_overflow(&yourtm.tm_year, -1)) return WRONG; ! i = yourtm.tm_year + (1 < yourtm.tm_mon); ! yourtm.tm_mday += year_lengths[isleap(i)]; } while (yourtm.tm_mday > DAYSPERLYEAR) { ! i = yourtm.tm_year + (1 < yourtm.tm_mon); ! yourtm.tm_mday -= year_lengths[isleap(i)]; ! if (increment_overflow(&yourtm.tm_year, 1)) return WRONG; } for ( ; ; ) { ! i = mon_lengths[isleap(yourtm.tm_year)][yourtm.tm_mon]; if (yourtm.tm_mday <= i) break; yourtm.tm_mday -= i; if (++yourtm.tm_mon >= MONSPERYEAR) { yourtm.tm_mon = 0; ! if (increment_overflow(&yourtm.tm_year, 1)) return WRONG; } } ! if (increment_overflow(&yourtm.tm_year, -TM_YEAR_BASE)) return WRONG; if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) saved_seconds = 0; ! else if (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) { /* ** We can't set tm_sec to 0, because that might push the ** time below the minimum representable time. --- 1384,1429 ---- return WRONG; if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) return WRONG; ! y = yourtm.tm_year; ! if (long_normalize_overflow(&y, &yourtm.tm_mon, MONSPERYEAR)) return WRONG; /* ! ** Turn y into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ ! if (long_increment_overflow(&y, TM_YEAR_BASE)) return WRONG; while (yourtm.tm_mday <= 0) { ! if (long_increment_overflow(&y, -1)) return WRONG; ! li = y + (1 < yourtm.tm_mon); ! yourtm.tm_mday += year_lengths[isleap(li)]; } while (yourtm.tm_mday > DAYSPERLYEAR) { ! li = y + (1 < yourtm.tm_mon); ! yourtm.tm_mday -= year_lengths[isleap(li)]; ! if (long_increment_overflow(&y, 1)) return WRONG; } for ( ; ; ) { ! i = mon_lengths[isleap(y)][yourtm.tm_mon]; if (yourtm.tm_mday <= i) break; yourtm.tm_mday -= i; if (++yourtm.tm_mon >= MONSPERYEAR) { yourtm.tm_mon = 0; ! if (long_increment_overflow(&y, 1)) return WRONG; } } ! if (long_increment_overflow(&y, -TM_YEAR_BASE)) return WRONG; + yourtm.tm_year = y; + if (yourtm.tm_year != y) + return WRONG; if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) saved_seconds = 0; ! else if (y + TM_YEAR_BASE < EPOCH_YEAR) { /* ** We can't set tm_sec to 0, because that might push the ** time below the minimum representable time. ------- strftime.c ------- *** /tmp/geta13 Thu Sep 9 11:48:56 2004 --- /tmp/getb13 Thu Sep 9 11:48:56 2004 *************** *** 1,6 **** #ifndef lint #ifndef NOID ! static char elsieid[] = "@(#)strftime.c 7.64"; /* ** Based on the UCB version with the ID appearing below. ** This is ANSIish only when "multibyte character == plain character". --- 1,12 ---- + /* + ** XXX To do: figure out correct (as distinct from standard-mandated) + ** output for "two digits of year" and "century" formats when + ** the year is negative or less than 100. --ado, 2004-09-09 + */ + #ifndef lint #ifndef NOID ! static char elsieid[] = "@(#)strftime.c 7.67"; /* ** Based on the UCB version with the ID appearing below. ** This is ANSIish only when "multibyte character == plain character". *************** *** 108,113 **** --- 114,120 ---- static char * _add P((const char *, char *, const char *)); static char * _conv P((int, const char *, char *, const char *)); + static char * _lconv P((long, const char *, char *, const char *)); static char * _fmt P((const char *, const struct tm *, char *, const char *, int *)); size_t strftime P((char *, size_t, const char *, const struct tm *)); *************** *** 210,216 **** ** something completely different. ** (ado, 1993-05-24) */ ! pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, "%02d", pt, ptlim); continue; case 'c': --- 217,224 ---- ** something completely different. ** (ado, 1993-05-24) */ ! pt = _conv((int) ((t->tm_year + ! (long) TM_YEAR_BASE) / 100), "%02d", pt, ptlim); continue; case 'c': *************** *** 379,390 **** ** (ado, 1996-01-02) */ { ! int year; int yday; int wday; int w; ! year = t->tm_year + TM_YEAR_BASE; yday = t->tm_yday; wday = t->tm_wday; for ( ; ; ) { --- 387,399 ---- ** (ado, 1996-01-02) */ { ! long year; int yday; int wday; int w; ! year = t->tm_year; ! year += TM_YEAR_BASE; yday = t->tm_yday; wday = t->tm_wday; for ( ; ; ) { *************** *** 426,436 **** DAYSPERNYEAR; } #ifdef XPG4_1994_04_09 ! if ((w == 52 ! && t->tm_mon == TM_JANUARY) ! || (w == 1 ! && t->tm_mon == TM_DECEMBER)) ! w = 53; #endif /* defined XPG4_1994_04_09 */ if (*format == 'V') pt = _conv(w, "%02d", --- 435,445 ---- DAYSPERNYEAR; } #ifdef XPG4_1994_04_09 ! if ((w == 52 && ! t->tm_mon == TM_JANUARY) || ! (w == 1 && ! t->tm_mon == TM_DECEMBER)) ! w = 53; #endif /* defined XPG4_1994_04_09 */ if (*format == 'V') pt = _conv(w, "%02d", *************** *** 437,446 **** pt, ptlim); else if (*format == 'g') { *warnp = IN_ALL; ! pt = _conv(year % 100, "%02d", pt, ptlim); - } else pt = _conv(year, "%04d", - pt, ptlim); } continue; case 'v': --- 446,455 ---- pt, ptlim); else if (*format == 'g') { *warnp = IN_ALL; ! pt = _conv(int(year % 100), ! "%02d", pt, ptlim); ! } else pt = _lconv(year, "%04ld", pt, ptlim); } continue; case 'v': *************** *** 477,488 **** continue; case 'y': *warnp = IN_ALL; ! pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, "%02d", pt, ptlim); continue; case 'Y': ! pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", ! pt, ptlim); continue; case 'Z': #ifdef TM_ZONE --- 486,498 ---- continue; case 'y': *warnp = IN_ALL; ! pt = _conv((int) ((t->tm_year + ! (long) TM_YEAR_BASE) % 100), "%02d", pt, ptlim); continue; case 'Y': ! pt = _lconv(t->tm_year + (long) TM_YEAR_BASE, ! "%04ld", pt, ptlim); continue; case 'Z': #ifdef TM_ZONE *************** *** 582,587 **** --- 592,610 ---- char buf[INT_STRLEN_MAXIMUM(int) + 1]; (void) sprintf(buf, format, n); + return _add(buf, pt, ptlim); + } + + static char * + _lconv(n, format, pt, ptlim) + const long n; + const char * const format; + char * const pt; + const char * const ptlim; + { + char buf[INT_STRLEN_MAXIMUM(long) + 1]; + + (void) sprintf(buf, format, n); return _add(buf, pt, ptlim); }
"Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> writes:
+ ** XXX To do: figure out correct (as distinct from standard-mandated) + ** output for "two digits of year" and "century" formats when + ** the year is negative or less than 100. --ado, 2004-09-09
Hmm, what's the problem for years less than 100? %C is clearly zero-origin, since it reports 19 for 1999 and 20 for 2000 (which isn't the same as "19th century" or "20th century"). So even though the year 50 is 1st-century, its %C should be 00. Maybe there's some doubt about the proper value for the year 0 (i.e., the year 1 B.C. according to the Venerable Bede's system) but I don't see any doubt about years 1 through 99. One other point that might be worth mentioning somewhere: this whole set of patches is motivated by the common case where time_t and long are 64 bits and int is 32 bits, but it doesn't suffice for some other cases, e.g., int==long==32 bits and time_t==64 bits (a case that is not allowed by C89 but is allowed by C99). Personally I'm inclined to not worry about these weird cases unless a real portability problem arises, just as we currently don't worry about the case where time_t==float (which the standards have always allowed).
<<On Thu, 09 Sep 2004 14:56:44 -0700, Paul Eggert <eggert@CS.UCLA.EDU> said:
are 64 bits and int is 32 bits, but it doesn't suffice for some other cases, e.g., int==long==32 bits and time_t==64 bits (a case that is not allowed by C89 but is allowed by C99).
We've talked a bit about doing this for FreeBSD/i386 (so that all architectures would use the same width time_t), but such a transition would be so difficult that I doubt it would happen. -GAWollman
participants (3)
-
Garrett Wollman -
Olson, Arthur David (NIH/NCI) -
Paul Eggert