proposed fixes to date.c, localtime.c, strftime.c for 64-bit hosts
Paul Eggert
eggert at CS.UCLA.EDU
Tue Jun 15 06:21:30 UTC 2004
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;
More information about the tz
mailing list