Strftime's %C and %y formats versus wide-ranging tm_year values

Paul Eggert eggert at CS.UCLA.EDU
Mon Sep 27 05:16:46 UTC 2004


Here's a patch to fix the mishandling of large tm_year values that I
noted in my previous email.  It assumes that you've already applied
the strftime.c patch (in email from ado dated 2004-09-23 19:39:37 UTC)
that increments strftime.c's elsieid from 7.67 to 7.69.

*** tzfile.h	1997/12/29 14:31:51	1997.9
--- tzfile.h	2004/09/27 04:35:02	1997.9.0.2
***************
*** 157,167 ****
  #define EPOCH_WDAY	TM_THURSDAY
  
  /*
! ** Accurate only for the past couple of centuries;
  ** that will probably do.
  */
  
  #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
  
  #ifndef USG
  
--- 157,176 ----
  #define EPOCH_WDAY	TM_THURSDAY
  
  /*
! ** Accurate only for the proleptic Gregorian calendar;
  ** that will probably do.
+ ** isleap(y) is 1 if y is a leap year.
+ ** ismult_sum(y1, y2, d) is 1 if y1 + y2 is a multiple of d (d > 1), and
+ **	returns the correct answer even if the addition would overflow.
+ ** isleap_sum(y1, y2) equals isleap(y1 + y2) except that it also
+ **	returns the correct answer even if the addition would overflow.
  */
  
  #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+ #define ismult_sum(y1, y2, d) ((((y1) % (d) + (y2) % (d))) % (d) == 0)
+ #define isleap_sum(y1, y2) \
+   (ismult_sum(y1, y2, 4) \
+    && (!ismult_sum(y1, y2, 100) || ismult_sum(y1, y2, 400)))
  
  #ifndef USG
  
*** strftime.c	2004/09/23 19:39:37	2004.4.0.1
--- strftime.c	2004/09/27 04:35:02	2004.4.0.2
***************
*** 108,114 ****
  
  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 *));
--- 108,114 ----
  
  static char *	_add P((const char *, char *, const char *));
  static char *	_conv P((int, const char *, char *, const char *));
! static char *	_yconv P((int, int, int, int, 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 *));
***************
*** 211,230 ****
  				** something completely different.
  				** (ado, 1993-05-24)
  				*/
! 				{
! 					long	year;
! 					int	top;
! 
! 					year = t->tm_year;
! 					year += TM_YEAR_BASE;
! 					top = year / 100;
! 					if (top == 0 && year < 0) {
! 						pt = _add("-0", pt, ptlim);
! 					} else {
! 						pt = _conv(top, "%02d",
! 							pt, ptlim);
! 					}
! 				}
  				continue;
  			case 'c':
  				{
--- 211,218 ----
  				** something completely different.
  				** (ado, 1993-05-24)
  				*/
! 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
! 					    pt, ptlim);
  				continue;
  			case 'c':
  				{
***************
*** 392,404 ****
  ** (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 ( ; ; ) {
--- 380,393 ----
  ** (ado, 1996-01-02)
  */
  				{
! 					int	year;
! 					int	base;
  					int	yday;
  					int	wday;
  					int	w;
  
  					year = t->tm_year;
! 					base = TM_YEAR_BASE;
  					yday = t->tm_yday;
  					wday = t->tm_wday;
  					for ( ; ; ) {
***************
*** 406,412 ****
  						int	bot;
  						int	top;
  
! 						len = isleap(year) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  						/*
--- 395,401 ----
  						int	bot;
  						int	top;
  
! 						len = isleap_sum(year, base) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  						/*
***************
*** 425,431 ****
  							top += DAYSPERWEEK;
  						top += len;
  						if (yday >= top) {
! 							++year;
  							w = 1;
  							break;
  						}
--- 414,420 ----
  							top += DAYSPERWEEK;
  						top += len;
  						if (yday >= top) {
! 							++base;
  							w = 1;
  							break;
  						}
***************
*** 434,441 ****
  								DAYSPERWEEK);
  							break;
  						}
! 						--year;
! 						yday += isleap(year) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  					}
--- 423,430 ----
  								DAYSPERWEEK);
  							break;
  						}
! 						--base;
! 						yday += isleap_sum(year,base) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  					}
***************
*** 450,465 ****
  						pt = _conv(w, "%02d",
  							pt, ptlim);
  					else if (*format == 'g') {
- 						int	i;
- 
  						*warnp = IN_ALL;
! 						i = year % 100;
! 						if (i < 0) {
! 							i = -i;
! 						}
! 						pt = _conv(i, "%02d",
  							pt, ptlim);
! 					} else	pt = _lconv(year, "%04ld",
  							pt, ptlim);
  				}
  				continue;
--- 439,448 ----
  						pt = _conv(w, "%02d",
  							pt, ptlim);
  					else if (*format == 'g') {
  						*warnp = IN_ALL;
! 						pt = _yconv(year, base, 0, 1,
  							pt, ptlim);
! 					} else	pt = _yconv(year, base, 1, 1,
  							pt, ptlim);
  				}
  				continue;
***************
*** 497,516 ****
  				continue;
  			case 'y':
  				*warnp = IN_ALL;
! 				{
! 					int	i;
! 
! 					i = (t->tm_year +
! 						(long) TM_YEAR_BASE) % 100;
! 					if (i < 0) {
! 						i = -i;
! 					}
! 					pt = _conv(i, "%02d", pt, ptlim);
! 				}
  				continue;
  			case 'Y':
! 				pt = _lconv(t->tm_year + (long) TM_YEAR_BASE,
! 					"%04ld", pt, ptlim);
  				continue;
  			case 'Z':
  #ifdef TM_ZONE
--- 480,491 ----
  				continue;
  			case 'y':
  				*warnp = IN_ALL;
! 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
! 					    pt, ptlim);
  				continue;
  			case 'Y':
! 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
! 					    pt, ptlim);
  				continue;
  			case 'Z':
  #ifdef TM_ZONE
***************
*** 615,630 ****
  }
  
  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 *
--- 590,638 ----
  }
  
  static char *
! _yconv(y1, y2, convert_top, convert_yy, pt, ptlim)
! const int		y1;
! const int		y2;
! const int		convert_top;
! const int		convert_yy;
! char *			pt;
  const char * const	ptlim;
  {
! 	/*  
! 	** POSIX and the C Standard are unclear or inconsistent about
! 	** what %C and %y do if the year is negative or exceeds 9999.
! 	** Use the convention that %C concatenated with %y yields the
! 	** same output as %Y, and that %Y contains at least 4 bytes,
! 	** with more only if necessary.
! 	*/
  
! 	int	top, yy;
! 
! 	yy = y1 % 100 + y2 % 100;
! 	top = y1 / 100 + y2 / 100 + yy / 100;
! 	yy %= 100;
! 
! 	if (0 < top && yy < 0) {
! 		yy += 100;
! 		top--;
! 	} else if (top < 0 && 0 < yy) {
! 		yy -= 100;
! 		top++;
! 	}
! 
! 	if (convert_top) {
! 		if (top == 0 && yy < 0) {
! 			pt = _add("-0", pt, ptlim);
! 		} else {
! 			pt = _conv(top, "%02d", pt, ptlim);
! 		}
! 	}
! 
! 	if (convert_yy) {
! 		pt = _conv(yy < 0 ? -yy : yy, "%02d", pt, ptlim);
! 	}
! 
! 	return pt;
  }
  
  static char *



More information about the tz mailing list