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

Olson, Arthur David (NIH/NCI) olsona at dc37a.nci.nih.gov
Mon Oct 18 16:24:42 UTC 2004


Yet another iteration on wide-ranging tm_year values; as usual, the diffs
below are against the stuff in ftp://elsie.nci.nih.gov/pub at the moment.

To avoid putting work into getting STRICTLY_STANDARD_ASCTIME code right,
I've removed the code (and the associated Makefile commentary).

My preference is to have the zdump handling of years parallel the strftime
handling as closely as possible, even though we "know" that TM_YEAR_BASE %
DIVISOR is zero and that some code could be removed. Note that right after
the year calculations in zdump there appears...
	if (lead == 0)
			(void) printf("%d", trail);	
	else	(void) printf("%d%d", lead, ((trail < 0) ? -trail : trail));
...meaning that any cycles eaten up by extra mod operators are going to be
swamped by the cycles devoted to generating output.

				--ado

diff -r -c old/Makefile new/Makefile
*** old/Makefile	Mon Oct 11 14:44:49 2004
--- new/Makefile	Mon Oct 18 11:43:16 2004
***************
*** 1,4 ****
! # @(#)Makefile	7.95
  
  # Change the line below for your time zone (after finding the zone you
want in
  # the time zone files, or adding it to a time zone file).
--- 1,4 ----
! # @(#)Makefile	7.98
  
  # Change the line below for your time zone (after finding the zone you
want in
  # the time zone files, or adding it to a time zone file).
***************
*** 103,110 ****
  #  -DLOCALE_HOME=\"path\" if locales are in "path", not "/usr/lib/locale"
  #  -DHAVE_UNISTD_H=0 if your compiler lacks a "unistd.h" (Microsoft C++
7?)
  #  -DHAVE_UTMPX_H=1 if your compiler has a "utmpx.h"
- #  -DSTRICTLY_STANDARD_ASCTIME=1 if you want a strictly standard (and
arguably
- #	broken) version of asctime (see asctime.c for details)	
  #  -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified
  #	DST transitions if the time zone files cannot be accessed
  #  -DTZ_DOMAIN=\"foo\" to use "foo" for gettext domain name; default is
"tz"
--- 103,108 ----
diff -r -c old/asctime.c new/asctime.c
*** old/asctime.c	Mon Oct 11 14:47:03 2004
--- new/asctime.c	Mon Oct 18 11:37:53 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.28";
  #endif /* !defined NOID */
  #endif /* !defined lint */
  
***************
*** 14,23 ****
  #include "private.h"
  #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 */
  /*
  ** Some systems only handle "%.2d"; others only handle "%02d";
  ** "%02.2d" makes (most) everybody happy.
--- 14,19 ----
***************
*** 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
--- 25,35 ----
  ** 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.
! ** The -4 ensures that we get four characters of output even if
! ** we call a strftime variant that produces fewer characters for some
years.
  */
! #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,45 ****
  ** 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
  /*
--- 36,42 ----
  ** 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"
  
  #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)
--- 71,77 ----
  	};
  	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,
--- 80,97 ----
  	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 -r -c 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 -r -c old/strftime.c new/strftime.c
*** old/strftime.c	Mon Oct 11 14:46:51 2004
--- new/strftime.c	Mon Oct 18 11:37:53 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.73";
  /*
  ** 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,642 ----
  	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
+ 	trail = a % DIVISOR + b % DIVISOR;
+ 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
+ 	trail %= DIVISOR;
+ 	if (trail < 0 && lead > 0) {
+ 		trail += DIVISOR;
+ 		--lead;
+ 	} else 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 -r -c 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 -r -c old/zdump.c new/zdump.c
*** old/zdump.c	Mon Oct 11 14:46:47 2004
--- new/zdump.c	Mon Oct 18 11:37:53 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.47";
  
  /*
  ** 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;
+ 	register int		lead;
+ 	register 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,429 ----
  		(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
! 	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
! 	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
! 		trail / DIVISOR;
! 	trail %= DIVISOR;
! 	if (trail < 0 && lead > 0) {
! 		trail += DIVISOR;
! 		--lead;
! 	} else 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