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