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

Olson, Arthur David (NIH/NCI) olsona at dc37a.nci.nih.gov
Thu Oct 14 18:03:04 UTC 2004


Below find the latest iteration on handling wide-ranging tm_year values.

1. The asctime function punts to strftime to generate output representing
the year; the asctime documentation has been adjusted accordingly; a comment
based on Paul Eggert's suggestion has been added.

2. zdump.c and strftime.c have been changed along the lines proposed by Paul
Eggert; I've tried to make the code as similar as possible in the two
places.

I've stayed with the "% 400" version of isleap_sum since it's correct and
relatively simple (if not efficient).
I'd suggest not worrying about this too much. I don't know of any real-world
applications where code relying on isleap is a bottleneck.
I also foresee that with the extension of the range of time_t to the distant
past, the days of being able to determine leap-year status with a macro are
numbered.

This round of changes does not split tzfile.h into caldefs.h and tzfile.h.

				--ado 

diff -c -r old/asctime.c new/asctime.c
*** old/asctime.c	Mon Oct 11 14:47:03 2004
--- new/asctime.c	Thu Oct 14 13:36:37 2004
***************
*** 5,11 ****
  
  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = "@(#)asctime.c	7.22";
  #endif /* !defined NOID */
  #endif /* !defined lint */
  
--- 5,11 ----
  
  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = "@(#)asctime.c	7.26";
  #endif /* !defined NOID */
  #endif /* !defined lint */
  
***************
*** 15,21 ****
  #include "tzfile.h"
  
  #if STRICTLY_STANDARD_ASCTIME
! #define ASCTIME_FMT	"%.3s %.3s%3d %.2d:%.2d:%.2d %ld\n"
  #define ASCTIME_FMT_B	ASCTIME_FMT
  #else /* !STRICTLY_STANDARD_ASCTIME */
  /*
--- 15,21 ----
  #include "tzfile.h"
  
  #if STRICTLY_STANDARD_ASCTIME
! #define ASCTIME_FMT	"%.3s %.3s%3d %.2d:%.2d:%.2d %s\n"
  #define ASCTIME_FMT_B	ASCTIME_FMT
  #else /* !STRICTLY_STANDARD_ASCTIME */
  /*
***************
*** 29,37 ****
  ** Vintage programs are coded for years that are always four digits long
  ** and may assume that the newline always lands in the same place.
  ** For years that are less than four digits, we pad the output with
! ** spaces before the newline to get the newline in the traditional place.
  */
! #define ASCTIME_FMT	"%.3s %.3s%3d %02.2d:%02.2d:%02.2d %-4ld\n"
  /*
  ** For years that are more than four digits we put extra spaces before the
year
  ** so that code trying to overwrite the newline won't end up overwriting
--- 29,37 ----
  ** Vintage programs are coded for years that are always four digits long
  ** and may assume that the newline always lands in the same place.
  ** For years that are less than four digits, we pad the output with
! ** leading zeroes to get the newline in the traditional place.
  */
! #define ASCTIME_FMT	"%.3s %.3s%3d %02.2d:%02.2d:%02.2d %-4s\n"
  /*
  ** For years that are more than four digits we put extra spaces before the
year
  ** so that code trying to overwrite the newline won't end up overwriting
***************
*** 38,44 ****
  ** a digit within a year and truncating the year (operating on the
assumption
  ** that no output is better than wrong output).
  */
! #define ASCTIME_FMT_B	"%.3s %.3s%3d %02.2d:%02.2d:%02.2d     %ld\n"
  #endif /* !STRICTLY_STANDARD_ASCTIME */
  
  #define STD_ASCTIME_BUF_SIZE	26
--- 38,44 ----
  ** a digit within a year and truncating the year (operating on the
assumption
  ** that no output is better than wrong output).
  */
! #define ASCTIME_FMT_B	"%.3s %.3s%3d %02.2d:%02.2d:%02.2d     %s\n"
  #endif /* !STRICTLY_STANDARD_ASCTIME */
  
  #define STD_ASCTIME_BUF_SIZE	26
***************
*** 74,80 ****
  	};
  	register const char *	wn;
  	register const char *	mn;
! 	long			year;
  	char			result[MAX_ASCTIME_BUF_SIZE];
  
  	if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
--- 74,80 ----
  	};
  	register const char *	wn;
  	register const char *	mn;
! 	char			year[INT_STRLEN_MAXIMUM(int) + 2];
  	char			result[MAX_ASCTIME_BUF_SIZE];
  
  	if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
***************
*** 83,94 ****
  	if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
  		mn = "???";
  	else	mn = mon_name[timeptr->tm_mon];
- 	year = timeptr->tm_year + (long) TM_YEAR_BASE;
  	/*
  	** We avoid using snprintf since it's not available on all systems.
  	*/
! 	(void) sprintf(result,
! 		((year >= -999 && year <= 9999) ? ASCTIME_FMT :
ASCTIME_FMT_B),
  		wn, mn,
  		timeptr->tm_mday, timeptr->tm_hour,
  		timeptr->tm_min, timeptr->tm_sec,
--- 83,100 ----
  	if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
  		mn = "???";
  	else	mn = mon_name[timeptr->tm_mon];
  	/*
+ 	** Use strftime's %Y to generate the year, to avoid overflow
problems
+ 	** when computing timeptr->tm_year + TM_YEAR_BASE.
+ 	** Assume that strftime is unaffected by other out-of-range members
+ 	** (e.g., timeptr->tm_mday) when processing "%Y".
+ 	*/
+ 	(void) strftime(year, sizeof year, "%Y", timeptr);
+ 	/*
  	** We avoid using snprintf since it's not available on all systems.
  	*/
! 	(void) sprintf(result, 
! 		((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B),
  		wn, mn,
  		timeptr->tm_mday, timeptr->tm_hour,
  		timeptr->tm_min, timeptr->tm_sec,
diff -c -r old/newctime.3 new/newctime.3
*** old/newctime.3	Mon Oct 11 14:44:49 2004
--- new/newctime.3	Thu Oct 14 13:00:48 2004
***************
*** 46,52 ****
  Thu Nov 24 18:22:48 1986\n\0
  .br
  .ec
! Years requiring fewer than four characters are padded with trailing
spaces.
  For years longer than four characters, the string is of the form
  .br
  .ce
--- 46,52 ----
  Thu Nov 24 18:22:48 1986\n\0
  .br
  .ec
! Years requiring fewer than four characters are padded with leading zeroes.
  For years longer than four characters, the string is of the form
  .br
  .ce
***************
*** 55,61 ****
  .ec
  .br
  with five spaces before the year.
! This unusual format is designed to make it less likely that older
  software that expects exactly 26 bytes of output will mistakenly output
  misleading values for out-of-range years.
  .PP
--- 55,61 ----
  .ec
  .br
  with five spaces before the year.
! These unusual formats are designed to make it less likely that older
  software that expects exactly 26 bytes of output will mistakenly output
  misleading values for out-of-range years.
  .PP
***************
*** 65,71 ****
  return pointers to ``tm'' structures, described below.
  .I Localtime\^
  corrects for the time zone and any time zone adjustments
! (such as Daylight Saving Time in the U.S.A.).
  After filling in the ``tm'' structure,
  .I localtime
  sets the
--- 65,71 ----
  return pointers to ``tm'' structures, described below.
  .I Localtime\^
  corrects for the time zone and any time zone adjustments
! (such as Daylight Saving Time in the United States).
  After filling in the ``tm'' structure,
  .I localtime
  sets the
***************
*** 234,237 ****
  Avoid using out-of-range values with
  .I mktime
  when setting up lunch with promptness sticklers in Riyadh.
! .\" @(#)newctime.3	7.16
--- 234,237 ----
  Avoid using out-of-range values with
  .I mktime
  when setting up lunch with promptness sticklers in Riyadh.
! .\" @(#)newctime.3	7.17
diff -c -r old/strftime.c new/strftime.c
*** old/strftime.c	Mon Oct 11 14:46:51 2004
--- new/strftime.c	Thu Oct 14 13:36:37 2004
***************
*** 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".
--- 1,6 ----
  #ifndef lint
  #ifndef NOID
! static char	elsieid[] = "@(#)strftime.c	7.71";
  /*
  ** Based on the UCB version with the ID appearing below.
  ** This is ANSIish only when "multibyte character == plain character".
***************
*** 114,124 ****
  
  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 *));
- 
  extern char *	tzname[];
  
  #ifndef YEAR_2000_NAME
--- 108,116 ----
  
  static char *	_add P((const char *, char *, const char *));
  static char *	_conv P((int, const char *, char *, const char *));
  static char *	_fmt P((const char *, const struct tm *, char *, const char
*, int *));
+ static char *	_yconv P((int, int, int, int, char *, const char *));
  
  extern char *	tzname[];
  
  #ifndef YEAR_2000_NAME
***************
*** 125,131 ****
  #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
  #endif /* !defined YEAR_2000_NAME */
  
- 
  #define IN_NONE	0
  #define IN_SOME	1
  #define IN_THIS	2
--- 117,122 ----
***************
*** 217,225 ****
  				** something completely different.
  				** (ado, 1993-05-24)
  				*/
! 				pt = _conv((int) ((t->tm_year +
! 					(long) TM_YEAR_BASE) / 100),
! 					"%02d", pt, ptlim);
  				continue;
  			case 'c':
  				{
--- 208,215 ----
  				** something completely different.
  				** (ado, 1993-05-24)
  				*/
! 				pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
! 					pt, ptlim);
  				continue;
  			case 'c':
  				{
***************
*** 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 ( ; ; ) {
--- 377,390 ----
  ** (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 ( ; ; ) {
***************
*** 401,407 ****
  						int	bot;
  						int	top;
  
! 						len = isleap(year) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  						/*
--- 392,398 ----
  						int	bot;
  						int	top;
  
! 						len = isleap_sum(year, base)
?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  						/*
***************
*** 420,426 ****
  							top += DAYSPERWEEK;
  						top += len;
  						if (yday >= top) {
! 							++year;
  							w = 1;
  							break;
  						}
--- 411,417 ----
  							top += DAYSPERWEEK;
  						top += len;
  						if (yday >= top) {
! 							++base;
  							w = 1;
  							break;
  						}
***************
*** 429,436 ****
 
DAYSPERWEEK);
  							break;
  						}
! 						--year;
! 						yday += isleap(year) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  					}
--- 420,427 ----
 
DAYSPERWEEK);
  							break;
  						}
! 						--base;
! 						yday += isleap_sum(year,
base) ?
  							DAYSPERLYEAR :
  							DAYSPERNYEAR;
  					}
***************
*** 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':
--- 437,446 ----
  							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;
  			case 'v':
***************
*** 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
--- 477,488 ----
  				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
***************
*** 556,564 ****
  					diff = -diff;
  				} else	sign = "+";
  				pt = _add(sign, pt, ptlim);
! 				diff /= 60;
! 				pt = _conv((diff/60)*100 + diff%60,
! 					"%04d", pt, ptlim);
  				}
  				continue;
  			case '+':
--- 546,555 ----
  					diff = -diff;
  				} else	sign = "+";
  				pt = _add(sign, pt, ptlim);
! 				diff /= SECSPERMIN;
! 				diff = (diff / MINSPERHOUR) * 100 +
! 					(diff % MINSPERHOUR);
! 				pt = _conv(diff, "%04d", pt, ptlim);
  				}
  				continue;
  			case '+':
***************
*** 596,614 ****
  }
  
  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;
--- 587,592 ----
***************
*** 619,624 ****
--- 597,641 ----
  	return pt;
  }
  
+ /*
+ ** 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.
+ */
+ 
+ static char *
+ _yconv(a, b, convert_top, convert_yy, pt, ptlim)
+ const int		a;
+ const int		b;
+ const int		convert_top;
+ const int		convert_yy;
+ char *			pt;
+ const char * const	ptlim;
+ {
+ 	register int	lead;
+ 	register int	trail;
+ 
+ #define DIVISOR	100
+ 	lead = a / DIVISOR + b / DIVISOR;
+ 	trail = a % DIVISOR + b % DIVISOR;
+ 	while (trail < 0) {
+ 		trail += DIVISOR;
+ 		--lead;
+ 	}
+ 	if (lead < 0 && trail != 0) {
+ 		trail -= DIVISOR;
+ 		++lead;
+ 	}
+ 	if (convert_top)
+ 		if (lead == 0 && trail < 0)
+ 			pt = _add("-0", pt, ptlim);
+ 		else	pt = _conv(lead, "%02d", pt, ptlim);
+ 	if (convert_yy)
+ 		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt,
ptlim);
+ }
+ 
  #ifdef LOCALE_HOME
  static struct lc_time_T *
  _loc P((void))
diff -c -r old/tzfile.h new/tzfile.h
*** old/tzfile.h	Mon Oct 11 14:46:42 2004
--- new/tzfile.h	Mon Oct 11 15:20:25 2004
***************
*** 21,27 ****
  
  #ifndef lint
  #ifndef NOID
! static char	tzfilehid[] = "@(#)tzfile.h	7.14";
  #endif /* !defined NOID */
  #endif /* !defined lint */
  
--- 21,27 ----
  
  #ifndef lint
  #ifndef NOID
! static char	tzfilehid[] = "@(#)tzfile.h	7.16";
  #endif /* !defined NOID */
  #endif /* !defined lint */
  
***************
*** 156,167 ****
  #define EPOCH_YEAR	1970
  #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
  
--- 156,176 ----
  #define EPOCH_YEAR	1970
  #define EPOCH_WDAY	TM_THURSDAY
  
+ #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) ==
0))
+ 
  /*
! ** Since everything in isleap is modulo 400 (or a factor of 400), we know
that
! **	isleap(y) == isleap(y % 400)
! ** and so
! **	isleap(a + b) == isleap((a + b) % 400)
! ** or
! **	isleap(a + b) == isleap(a % 400 + b % 400)
! ** This is true even if % means modulo rather than Fortran remainder
! ** (which is allowed by C89 but not C99).
! ** We use this to avoid addition overflow problems.
  */
  
! #define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
  
  #ifndef USG
  
diff -c -r old/zdump.c new/zdump.c
*** old/zdump.c	Mon Oct 11 14:46:47 2004
--- new/zdump.c	Thu Oct 14 13:00:49 2004
***************
*** 1,4 ****
! static char	elsieid[] = "@(#)zdump.c	7.40";
  
  /*
  ** This code has been made independent of the rest of the time
--- 1,4 ----
! static char	elsieid[] = "@(#)zdump.c	7.43";
  
  /*
  ** This code has been made independent of the rest of the time
***************
*** 61,69 ****
  #endif /* !defined DAYSPERNYEAR */
  
  #ifndef isleap
! #define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) ==
0)
  #endif /* !defined isleap */
  
  #if HAVE_GETTEXT
  #include "locale.h"	/* for setlocale */
  #include "libintl.h"
--- 61,76 ----
  #endif /* !defined DAYSPERNYEAR */
  
  #ifndef isleap
! #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) ==
0))
  #endif /* !defined isleap */
  
+ #ifndef isleap_sum
+ /*
+ ** See tzfile.h for details on isleap_sum.
+ */
+ #define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
+ #endif /* !defined isleap_sum */
+ 
  #if HAVE_GETTEXT
  #include "locale.h"	/* for setlocale */
  #include "libintl.h"
***************
*** 321,327 ****
  		return -delta(oldp, newp);
  	result = 0;
  	for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
! 		result += DAYSPERNYEAR + isleap(tmy + (long) TM_YEAR_BASE);
  	result += newp->tm_yday - oldp->tm_yday;
  	result *= HOURSPERDAY;
  	result += newp->tm_hour - oldp->tm_hour;
--- 328,334 ----
  		return -delta(oldp, newp);
  	result = 0;
  	for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
! 		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
  	result += newp->tm_yday - oldp->tm_yday;
  	result *= HOURSPERDAY;
  	result += newp->tm_hour - oldp->tm_hour;
***************
*** 384,389 ****
--- 391,398 ----
  	};
  	register const char *	wn;
  	register const char *	mn;
+ 	int			lead;
+ 	int			trail;
  
  	/*
  	** The packaged versions of localtime and gmtime never put
out-of-range
***************
*** 398,406 ****
  		(int) (sizeof mon_name / sizeof mon_name[0]))
  			mn = "???";
  	else		mn = mon_name[timeptr->tm_mon];
! 	(void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d %ld",
  		wn, mn,
  		timeptr->tm_mday, timeptr->tm_hour,
! 		timeptr->tm_min, timeptr->tm_sec,
! 		timeptr->tm_year + (long) TM_YEAR_BASE);
  }
--- 407,428 ----
  		(int) (sizeof mon_name / sizeof mon_name[0]))
  			mn = "???";
  	else		mn = mon_name[timeptr->tm_mon];
! 	(void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
  		wn, mn,
  		timeptr->tm_mday, timeptr->tm_hour,
! 		timeptr->tm_min, timeptr->tm_sec);
! #define DIVISOR	10
! 	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR;
! 	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
! 	while (trail < 0) {
! 		trail += DIVISOR;
! 		--lead;
! 	}
! 	if (lead < 0 && trail != 0) {
! 		trail -= DIVISOR;
! 		++lead;
! 	}
! 	if (lead == 0)
! 		(void) printf("%d", trail);
! 	else	(void) printf("%d%d", lead, ((trail < 0) ? -trail : trail));
  }



More information about the tz mailing list