Proposed strftime.c changes for long years

Olson, Arthur David (NIH/NCI) olsona at dc37a.nci.nih.gov
Thu Sep 9 15:56:38 UTC 2004


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);
  }
  


More information about the tz mailing list