RE: zdump -v crashes with 64-bit time_t
Here's a proposed change to zdump to avoid crashes when NULL is returned by asctime. --ado ------- zdump.c ------- *** /tmp/geta13866 Mon Jun 14 11:47:06 2004 --- /tmp/getb13866 Mon Jun 14 11:47:06 2004 *************** *** 1,4 **** ! static char elsieid[] = "@(#)zdump.c 7.31"; /* ** This code has been made independent of the rest of the time --- 1,4 ---- ! static char elsieid[] = "@(#)zdump.c 7.32"; /* ** This code has been made independent of the rest of the time *************** *** 334,339 **** --- 334,346 ---- return result; } + static char * + nonnull(s) + const char * const s; + { + return (s == NULL) ? "NULL" : s; + } + static void show(zone, t, v) char * zone; *************** *** 344,352 **** (void) printf("%-*s ", (int) longest, zone); if (v) ! (void) printf("%.24s UTC = ", asctime(gmtime(&t))); tmp = localtime(&t); ! (void) printf("%.24s", asctime(tmp)); if (*abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); if (v) { --- 351,359 ---- (void) printf("%-*s ", (int) longest, zone); if (v) ! (void) printf("%.24s UTC = ", nonnull(asctime(gmtime(&t)))); tmp = localtime(&t); ! (void) printf("%.24s", nonnull(asctime(tmp))); if (*abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); if (v) {
On Mon, Jun 14, 2004 at 11:51:48AM -0400, Olson, Arthur David (NIH/NCI) wrote:
Here's a proposed change to zdump to avoid crashes when NULL is returned by asctime. [...] if (v) ! (void) printf("%.24s UTC = ", asctime(gmtime(&t))); tmp = localtime(&t); ! (void) printf("%.24s", asctime(tmp)); ... if (v) ! (void) printf("%.24s UTC = ", nonnull(asctime(gmtime(&t)))); tmp = localtime(&t); ! (void) printf("%.24s", nonnull(asctime(tmp)));
That might work, depending on the local implementation of asctime(), but I'd feel more comfortable with a patch that takes care to avoid passing a NULL to asctime() in the first place. Perhaps this patch instead? --Ken Pizzini --- zdump.c-orig 2004-03-02 06:52:38.000000000 -0800 +++ zdump.c 2004-06-14 20:05:19.453250080 -0700 @@ -1,4 +1,4 @@ -static char elsieid[] = "@(#)zdump.c 7.31"; +static char elsieid[] = "@(#)zdump.c 7.32"; /* ** This code has been made independent of the rest of the time @@ -334,6 +334,14 @@ return result; } +static char * +null_safe_asctime(t) +const struct tm * t; +{ + static char null[] = "NULL"; + return (t == NULL) ? null : asctime(t); +} + static void show(zone, t, v) char * zone; @@ -344,9 +352,9 @@ (void) printf("%-*s ", (int) longest, zone); if (v) - (void) printf("%.24s UTC = ", asctime(gmtime(&t))); + (void) printf("%.24s UTC = ", null_safe_asctime(gmtime(&t))); tmp = localtime(&t); - (void) printf("%.24s", asctime(tmp)); + (void) printf("%.24s", null_safe_asctime(tmp)); if (*abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); if (v) {
On Mon, Jun 14, 2004 at 08:11:26PM -0700, Ken Pizzini wrote:
That might work, depending on the local implementation of asctime(), but I'd feel more comfortable with a patch that takes care to avoid passing a NULL to asctime() in the first place. Perhaps this patch instead?
No, that patch wasn't right either: it did protect the asctime() calls fine, but it still left zdump.c with many potential dereferences of NULL localtime() result pointers. I'm not completely satisfied with how the code resulting from the patch below handles things, but at least it now completely avoids dereferencing these NULLs... (The attached patch is against a fresh copy of zdump.c from tzcode2004a; i.e., it is *not* incremental to either of the previous two patches circulated on the list.) --Ken Pizzini --- zdump.c-orig 2004-03-02 06:52:38.000000000 -0800 +++ zdump.c 2004-06-14 21:24:11.735549152 -0700 @@ -1,4 +1,4 @@ -static char elsieid[] = "@(#)zdump.c 7.31"; +static char elsieid[] = "@(#)zdump.c 7.32"; /* ** This code has been made independent of the rest of the time @@ -129,6 +129,7 @@ static size_t longest; static char * progname; static void show P((char * zone, time_t t, int v)); +static void complain P((time_t t)); int main(argc, argv) @@ -148,6 +149,7 @@ time_t hibit; struct tm tm; struct tm newtm; + struct tm *tmp; INITIALIZE(cuttime); #if HAVE_GETTEXT - 0 @@ -230,7 +232,12 @@ show(argv[i], t, TRUE); t += SECSPERHOUR * HOURSPERDAY; show(argv[i], t, TRUE); - tm = *localtime(&t); + tmp = localtime(&t); + if (tmp == NULL) { + complain(t); + continue; + } + tm = *tmp; (void) strncpy(buf, abbr(&tm), (sizeof buf) - 1); for ( ; ; ) { if (cutoff != NULL && t >= cuttime) @@ -240,12 +247,22 @@ break; if (newt <= t) break; - newtm = *localtime(&newt); + tmp = localtime(&newt); + if (tmp == NULL) { + complain(newt); + break; + } + newtm = *tmp; if (delta(&newtm, &tm) != (newt - t) || newtm.tm_isdst != tm.tm_isdst || strcmp(abbr(&newtm), buf) != 0) { newt = hunt(argv[i], t, newt); - newtm = *localtime(&newt); + tmp = localtime(&newt); + if (tmp == NULL) { + complain(newt); + break; + } + newtm = *tmp; (void) strncpy(buf, abbr(&newtm), (sizeof buf) - 1); } @@ -284,9 +301,15 @@ time_t t; struct tm lotm; struct tm tm; + struct tm *tmp; static char loab[MAX_STRING_LENGTH]; - lotm = *localtime(&lot); + tmp = localtime(&lot); + if (tmp == NULL) { + complain(lot); + return lot; + } + lotm = *tmp; (void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1); while ((hit - lot) >= 2) { t = lot / 2 + hit / 2; @@ -294,7 +317,12 @@ ++t; else if (t >= hit) --t; - tm = *localtime(&t); + tmp = localtime(&t); + if (tmp == NULL) { + complain(t); + return t; + } + tm = *tmp; if (delta(&tm, &lotm) == (t - lot) && tm.tm_isdst == lotm.tm_isdst && strcmp(abbr(&tm), loab) == 0) { @@ -334,6 +362,14 @@ return result; } +static char * +nonnull_asctime(t) +const struct tm * t; +{ + static char null[] = "NULL"; + return (t == NULL) ? null : asctime(t); +} + static void show(zone, t, v) char * zone; @@ -344,12 +380,12 @@ (void) printf("%-*s ", (int) longest, zone); if (v) - (void) printf("%.24s UTC = ", asctime(gmtime(&t))); + (void) printf("%.24s UTC = ", nonnull_asctime(gmtime(&t))); tmp = localtime(&t); - (void) printf("%.24s", asctime(tmp)); - if (*abbr(tmp) != '\0') + (void) printf("%.24s", nonnull_asctime(tmp)); + if (tmp != NULL && *abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); - if (v) { + if (tmp != NULL && v) { (void) printf(" isdst=%d", tmp->tm_isdst); #ifdef TM_GMTOFF (void) printf(" gmtoff=%ld", tmp->TM_GMTOFF); @@ -358,6 +394,13 @@ (void) printf("\n"); } +static void +complain(t) +time_t t; +{ + (void) printf("localtime() failed on input %lld\n", (long long)t); /*XXX*/ +} + static char * abbr(tmp) struct tm * tmp;
Ken Pizzini <"tz."@explicate.org> writes:
No, that patch wasn't right either: it did protect the asctime() calls fine, but it still left zdump.c with many potential dereferences of NULL localtime() result pointers.
The patch that I submitted about an hour after your message fixes those null-dereferencing problems _if_ one assumes that localtime succeeds in a contiguous region, i.e., that if localtime succeeds on both T1 and T2, and T1 <= T <= T2, then localtime succeeds on T. Hmm, I just now realized that this assumption isn't quite valid in general, because a daylight-saving transition could occur at the end if the year (INT_MAX - 1900), which means that localtime might succeed in a discontiguous region. I think the "right way" to fix this is to redo zdump so that a discontinuity is reported if localtime succeeds at time T but fails at time T+1, or vice versa. If Arthur likes this idea I can code up a solution along those lines.
Ken Pizzini <"tz."@explicate.org> writes:
I'd feel more comfortable with a patch that takes care to avoid passing a NULL to asctime() in the first place.
Yes, asctime has undefined behavior if you give it null pointer. But your patch doesn't suffice either, since asctime also has undefined behavior if you give it a struct tm whose tm_year member is less than -999 - 1900 or greater than 9999 - 1900. (This is because it can overrun its 26-byte buffer in that case.) Worse, HP-UX asctime rejects any tm_year that is negative; this violates the C standard, but HP won't take my bug reports since I'm not a HP-UX customer. I looked and found several other problems with 64-bit time_t in zdump.c. Here is a proposed patch. The basic ideas are as follows. * Use 'long' when computing years, not 'int', to avoid spurious overflow on 64-bit hosts. * Use our (more generous) asctime implementation than relying on the system asctime, so that we can print years before 1900 and after 9999 (even on HP-UX). * When localtime/gmtime fails, print the raw time_t value as an integer than the less-informative "NULL". * Don't attempt to print out all the 64-bit time_t space, as that will take nearly forever; instead, default to printing about 50 years into the future, and don't bother to print times before about 1833. This suffices for all the current tz data and is good enough for POSIX tz values until the year 2106. We can revisit this when we add true 64-bit support for tz data. =================================================================== RCS file: RCS/asctime.c,v retrieving revision 2004.1 retrieving revision 2004.1.0.1 diff -pu -r2004.1 -r2004.1.0.1 --- asctime.c 1998/05/28 13:56:06 2004.1 +++ asctime.c 2004/06/15 04:23:43 2004.1.0.1 @@ -41,14 +41,14 @@ char * buf; else mn = mon_name[timeptr->tm_mon]; /* ** The X3J11-suggested format is - ** "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %d\n" - ** Since the .2 in 02.2d is ignored, we drop it. + ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n" + ** Use "%02d", as it is a bit more portable than "%.2d". */ - (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %d\n", + (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, - TM_YEAR_BASE + timeptr->tm_year); + timeptr->tm_year + (long) TM_YEAR_BASE); return buf; } =================================================================== RCS file: RCS/zdump.c,v retrieving revision 2003.3 retrieving revision 2003.3.0.2 diff -pu -r2003.3 -r2003.3.0.2 --- zdump.c 2003/09/16 11:12:41 2003.3 +++ zdump.c 2004/06/15 05:24:21 2003.3.0.2 @@ -6,6 +6,7 @@ static char elsieid[] = "@(#)zdump.c 7.3 ** You can use this code to help in verifying other implementations. */ +#include "limits.h" /* for CHAR_BIT */ #include "stdio.h" /* for stdout, stderr, perror */ #include "string.h" /* for strcpy */ #include "sys/types.h" /* for time_t */ @@ -24,6 +25,25 @@ static char elsieid[] = "@(#)zdump.c 7.3 #define FALSE 0 #endif /* !defined FALSE */ +#ifndef TYPE_BIT +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif /* !defined TYPE_BIT */ + +#ifndef TYPE_SIGNED +#define TYPE_SIGNED(type) (((type) -1) < 0) +#endif /* !defined TYPE_SIGNED */ + +#ifndef INT_STRLEN_MAXIMUM +/* +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +*/ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + 1 + TYPE_SIGNED(type)) +#endif /* !defined INT_STRLEN_MAXIMUM */ + #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #endif /* !defined EXIT_SUCCESS */ @@ -56,10 +76,18 @@ static char elsieid[] = "@(#)zdump.c 7.3 #define TM_YEAR_BASE 1900 #endif /* !defined TM_YEAR_BASE */ +#ifndef DAYSPERWEEK +#define DAYSPERWEEK 7 +#endif /* !defined DAYSPERWEEK */ + #ifndef DAYSPERNYEAR #define DAYSPERNYEAR 365 #endif /* !defined DAYSPERNYEAR */ +#ifndef MONSPERYEAR +#define MONSPERYEAR 12 +#endif /* !defined MONSPERYEAR */ + #ifndef isleap #define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) #endif /* !defined isleap */ @@ -128,6 +156,9 @@ static long delta P((struct tm * newp, s static time_t hunt P((char * name, time_t lot, time_t hit)); static size_t longest; static char * progname; +static char * my_asctime_r P((const struct tm *, char *)); +static char * my_asctime P((const struct tm *)); +static void showtime P((time_t, const struct tm *)); static void show P((char * zone, time_t t, int v)); int @@ -140,16 +171,17 @@ char * argv[]; register int vflag; register char * cutoff; register int cutyear; - register long cuttime; + register time_t lasttime; char ** fakeenv; time_t now; time_t t; time_t newt; time_t hibit; + time_t time_t_min; + time_t time_t_max; struct tm tm; - struct tm newtm; + struct tm * newtm; - INITIALIZE(cuttime); #if HAVE_GETTEXT - 0 (void) setlocale(LC_MESSAGES, ""); #ifdef TZ_DOMAINDIR @@ -176,22 +208,39 @@ _("%s: usage is %s [ --version ] [ -v ] argv[0], argv[0]); (void) exit(EXIT_FAILURE); } + for (hibit = 1; (hibit << 1) != 0; hibit <<= 1) + continue; + time_t_max = ~((time_t) 0); + if (TYPE_SIGNED(time_t)) + time_t_max &= ~hibit; + time_t_min = TYPE_SIGNED(time_t) ? hibit : 0; + (void) time(&now); if (cutoff != NULL) { int y; cutyear = atoi(cutoff); - cuttime = 0; + lasttime = 0; for (y = EPOCH_YEAR; y < cutyear; ++y) - cuttime += DAYSPERNYEAR + isleap(y); - cuttime *= SECSPERHOUR * HOURSPERDAY; + lasttime += DAYSPERNYEAR + isleap(y); + lasttime *= SECSPERHOUR * HOURSPERDAY; + lasttime -= TYPE_SIGNED(time_t) || lasttime != 0; + } else { + /* + ** By default, the cutoff is the maximum time value, + ** or about 50 years from now, whichever comes first. + ** This prevents us from generating too much output if + ** time_t is 64 bits. 31556952 is 365.2425 * 24 * 60 + ** * 60, but don't trust the C compiler to calculate + ** it as there may be bugs or rounding problems. + */ + long seconds_per_mean_gregorian_year = 31556952; + time_t now50 = now + 50 * seconds_per_mean_gregorian_year - 1; + lasttime = now < now50 ? now50 : time_t_max; } - (void) time(&now); longest = 0; for (i = optind; i < argc; ++i) if (strlen(argv[i]) > longest) longest = strlen(argv[i]); - for (hibit = 1; (hibit << 1) != 0; hibit <<= 1) - continue; { register int from; register int to; @@ -224,40 +273,47 @@ _("%s: usage is %s [ --version ] [ -v ] /* ** Get lowest value of t. */ - t = hibit; - if (t > 0) /* time_t is unsigned */ - t = 0; + t = time_t_min; show(argv[i], t, TRUE); t += SECSPERHOUR * HOURSPERDAY; show(argv[i], t, TRUE); + /* + ** But don't go below - 2**32 in the following loop, + ** as that predates standard time, would cause us to + ** generate too much output, and could dump core if + ** localtime returns NULL. The earliest known use of + ** standard time is 1837 (Iceland) and - 2**32 is a + ** bit before that. + */ + if ((t + 1) / 2 < -2147483647) { + t = -1 - 2147483647; + t *= 2; + } tm = *localtime(&t); (void) strncpy(buf, abbr(&tm), (sizeof buf) - 1); for ( ; ; ) { - if (cutoff != NULL && t >= cuttime) + if (lasttime < t) break; newt = t + SECSPERHOUR * 12; - if (cutoff != NULL && newt >= cuttime) + if (lasttime < newt) break; if (newt <= t) break; - newtm = *localtime(&newt); - if (delta(&newtm, &tm) != (newt - t) || - newtm.tm_isdst != tm.tm_isdst || - strcmp(abbr(&newtm), buf) != 0) { + newtm = localtime(&newt); + if (newtm == NULL) + break; + if (delta(newtm, &tm) != (newt - t) || + newtm->tm_isdst != tm.tm_isdst || + strcmp(abbr(newtm), buf) != 0) { newt = hunt(argv[i], t, newt); - newtm = *localtime(&newt); - (void) strncpy(buf, abbr(&newtm), + newtm = localtime(&newt); + (void) strncpy(buf, abbr(newtm), (sizeof buf) - 1); } t = newt; - tm = newtm; + tm = *newtm; } - /* - ** Get highest value of t. - */ - t = ~((time_t) 0); - if (t < 0) /* time_t is signed */ - t &= ~hibit; + t = time_t_max; t -= SECSPERHOUR * HOURSPERDAY; show(argv[i], t, TRUE); t += SECSPERHOUR * HOURSPERDAY; @@ -323,7 +379,7 @@ struct tm * oldp; return -delta(oldp, newp); result = 0; for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy) - result += DAYSPERNYEAR + isleap(tmy + TM_YEAR_BASE); + result += DAYSPERNYEAR + isleap(tmy + (long) TM_YEAR_BASE); result += newp->tm_yday - oldp->tm_yday; result *= HOURSPERDAY; result += newp->tm_hour - oldp->tm_hour; @@ -334,6 +390,80 @@ struct tm * oldp; return result; } +/* +** my_asctime_r and my_asctime are stolen from asctime.c. Unlike +** Standard C asctime, my_asctime has well-defined behavior even if +** the year is less than -999 or greater than 9999. +*/ + +static char * +my_asctime_r(timeptr, buf) +register const struct tm * timeptr; +char * buf; +{ + static const char wday_name[][3] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char mon_name[][3] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + register const char * wn; + register const char * mn; + + if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK) + wn = "???"; + else wn = wday_name[timeptr->tm_wday]; + if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) + mn = "???"; + else mn = mon_name[timeptr->tm_mon]; + /* + ** The X3J11-suggested format is + ** "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n" + ** Use "%02d", as it is a bit more portable than "%.2d". + */ + (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %ld\n", + wn, mn, + timeptr->tm_mday, timeptr->tm_hour, + timeptr->tm_min, timeptr->tm_sec, + timeptr->tm_year + (long) TM_YEAR_BASE); + return buf; +} + +static char * +my_asctime(timeptr) +register const struct tm * timeptr; +{ + /* + ** Big enough for something such as + ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n + ** (two three-character abbreviations, five strings denoting integers, + ** three explicit spaces, two explicit colons, a newline, + ** and a trailing ASCII nul). + */ + static char result[3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) + + 3 + 2 + 1 + 1]; + + return my_asctime_r(timeptr, result); +} + +static void +showtime(t, timeptr) +time_t t; +const struct tm * timeptr; +{ + if (timeptr != NULL) { + const char *p; + for (p = my_asctime (timeptr); *p != '\n'; p++) + putchar (*p); + } else { + if (TYPE_SIGNED(time_t)) + printf ("%ld\n", (long) t); + else + printf ("%lu\n", (unsigned long) t); + } +} + static void show(zone, t, v) char * zone; @@ -343,13 +473,15 @@ int v; struct tm * tmp; (void) printf("%-*s ", (int) longest, zone); - if (v) - (void) printf("%.24s UTC = ", asctime(gmtime(&t))); + if (v) { + showtime(t, gmtime(&t)); + (void) printf(" UTC = "); + } tmp = localtime(&t); - (void) printf("%.24s", asctime(tmp)); + showtime(t, tmp); if (*abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); - if (v) { + if (v && tmp != NULL) { (void) printf(" isdst=%d", tmp->tm_isdst); #ifdef TM_GMTOFF (void) printf(" gmtoff=%ld", tmp->TM_GMTOFF); @@ -365,7 +497,7 @@ struct tm * tmp; register char * result; static char nada; - if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1) + if (!tmp || (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)) return &nada; result = tzname[tmp->tm_isdst]; return (result == NULL) ? &nada : result; =================================================================== RCS file: RCS/zdump.8,v retrieving revision 2004.1 retrieving revision 2004.1.0.1 diff -pu -r2004.1 -r2004.1.0.1 --- zdump.8 2003/09/16 11:12:41 2004.1 +++ zdump.8 2004/06/15 05:24:21 2004.1.0.1 @@ -40,6 +40,12 @@ otherwise. .TP .BI "\-c " cutoffyear Cut off the verbose output near the start of the given year. +By default, verbose output is cut off approximately 50 years after the +current date and time. .SH "SEE ALSO" newctime(3), tzfile(5), zic(8) +.SH "BUGS" +Time discontinuities are detected via a heuristic that suffices for +all historically valid discontinuities, but one can construct +artificial time zones for which the heuristic fails. .\" @(#)zdump.8 7.4
Following up on my previous patch to zdump.c and asctime.c, here is a patch to similar problems that I discovered with 64-bit time_t and 32-bit int in date.c, localtime.c and strftime.c. I have the nagging suspicion that there are more problems but I thought I'd publish what I found. The basic idea is to avoid overflow in year computations, either by using 'long' rather than 'int', or by reformulating the computations so that they can't possibly overflow. This is an issue on 64-bit time_t hosts (typically int is 32-bits) because tm_year can be any value in the range INT_MIN through INT_MAX. I also fixed a few related problems with negative year numbers. =================================================================== RCS file: RCS/date.c,v retrieving revision 2001.4 retrieving revision 2001.4.0.1 diff -pu -r2001.4 -r2001.4.0.1 --- date.c 2001/10/09 17:31:31 2001.4 +++ date.c 2004/06/15 05:53:49 2001.4.0.1 @@ -630,8 +630,12 @@ const time_t t; time_t outt; tm = *localtime(&t); - cent = (tm.tm_year + TM_YEAR_BASE) / 100; - year_in_cent = (tm.tm_year + TM_YEAR_BASE) - cent * 100; + cent = tm.tm_year / 100 + TM_YEAR_BASE / 100; + year_in_cent = tm.tm_year % 100; + if (year_int_cent < 0) { + cent--; + year_in_cent += 100; + } month = tm.tm_mon + 1; day = tm.tm_mday; hour = tm.tm_hour; =================================================================== RCS file: RCS/localtime.c,v retrieving revision 2003.5 retrieving revision 2003.5.0.2 diff -pu -r2003.5 -r2003.5.0.2 --- localtime.c 2003/12/15 14:36:35 2003.5 +++ localtime.c 2004/06/15 06:13:01 2003.5.0.2 @@ -134,8 +134,11 @@ static void gmtsub P((const time_t * ti 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 normalize_overflow P((int * tensptr, int * unitsptr, int base)); +static int long_normalize_overflow P((long * tensptr, + int * unitsptr, int base)); static void settzname P((void)); static time_t time1 P((struct tm * tmp, void(*funcp) P((const time_t *, @@ -1149,7 +1152,7 @@ register struct tm * const tmp; register const struct lsinfo * lp; register long days; register long rem; - register int y; + register long y; register int yleap; register const int * ip; register long corr; @@ -1218,7 +1221,7 @@ register struct tm * const tmp; 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; + register long newy; newy = y + days / DAYSPERNYEAR; if (days < 0) @@ -1294,6 +1297,18 @@ int delta; } 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,6 +1324,21 @@ const int base; } 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; @@ -1336,6 +1366,8 @@ const int do_norm_secs; register int dir; register int bits; register int i, j ; + register long li; + long y; register int saved_seconds; time_t newt; time_t t; @@ -1352,42 +1384,46 @@ const int do_norm_secs; return WRONG; if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) return WRONG; - if (normalize_overflow(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR)) + y = yourtm.tm_year; + if (long_normalize_overflow(&y, &yourtm.tm_mon, MONSPERYEAR)) return WRONG; /* - ** Turn yourtm.tm_year into an actual year number for now. + ** Turn y 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)) + if (long_increment_overflow(&y, TM_YEAR_BASE)) return WRONG; while (yourtm.tm_mday <= 0) { - if (increment_overflow(&yourtm.tm_year, -1)) + if (long_increment_overflow(&y, -1)) return WRONG; - i = yourtm.tm_year + (1 < yourtm.tm_mon); - yourtm.tm_mday += year_lengths[isleap(i)]; + li = y + (1 < yourtm.tm_mon); + yourtm.tm_mday += year_lengths[isleap(li)]; } 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)) + 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(yourtm.tm_year)][yourtm.tm_mon]; + 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 (increment_overflow(&yourtm.tm_year, 1)) + if (long_increment_overflow(&y, 1)) return WRONG; } } - if (increment_overflow(&yourtm.tm_year, -TM_YEAR_BASE)) + 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 (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) { + else if (y < EPOCH_YEAR - TM_YEAR_BASE) { /* ** We can't set tm_sec to 0, because that might push the ** time below the minimum representable time. =================================================================== RCS file: RCS/strftime.c,v retrieving revision 2001.4 retrieving revision 2001.4.0.1 diff -pu -r2001.4 -r2001.4.0.1 --- strftime.c 2001/10/04 21:01:17 2001.4 +++ strftime.c 2004/06/15 05:53:49 2001.4.0.1 @@ -108,6 +108,7 @@ static const struct lc_time_T C_time_loc 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,8 +211,12 @@ label: ** something completely different. ** (ado, 1993-05-24) */ - pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, - "%02d", pt, ptlim); + { + int c = (TM_YEAR_BASE / 100 + + t->tm_year / 100 + - (t->tm_year % 100 < 0)); + pt = _conv(c, "%02d", pt, ptlim); + } continue; case 'c': { @@ -379,12 +384,13 @@ label: ** (ado, 1996-01-02) */ { - int year; + long year; int yday; int wday; int w; - year = t->tm_year + TM_YEAR_BASE; + year = t->tm_year; + year += TM_YEAR_BASE; yday = t->tm_yday; wday = t->tm_wday; for ( ; ; ) { @@ -436,10 +442,13 @@ label: pt = _conv(w, "%02d", pt, ptlim); else if (*format == 'g') { + int g = year % 100; + if (g < 0) + g += 100; *warnp = IN_ALL; - pt = _conv(year % 100, "%02d", + pt = _conv(g, "%02d", pt, ptlim); - } else pt = _conv(year, "%04d", + } else pt = _lconv(year, "%04ld", pt, ptlim); } continue; @@ -476,13 +485,17 @@ label: } continue; case 'y': + { + int y = t->tm_year % 100; + if (y < 0) + y += 100; *warnp = IN_ALL; - pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, - "%02d", pt, ptlim); + pt = _conv(y, "%02d", pt, ptlim); + } continue; case 'Y': - pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", - pt, ptlim); + pt = _lconv(t->tm_year + (long) TM_YEAR_BASE, + "%04ld", pt, ptlim); continue; case 'Z': #ifdef TM_ZONE @@ -586,6 +599,19 @@ const char * const 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); +} + +static char * _add(str, pt, ptlim) const char * str; char * pt;
participants (3)
-
Ken Pizzini -
Olson, Arthur David (NIH/NCI) -
Paul Eggert