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

Olson, Arthur David (NIH/NCI) olsona at dc37a.nci.nih.gov
Tue Oct 5 15:17:45 UTC 2004


Here's the latest try at coping with wide-ranging tm_year values.
This is based on the proposals circulated by Paul Eggert.
I've tried to deal with the challenges of systems where the compiler's %
operator doesn't do things the C99 way.
I've also tried to deal with systems where sizeof (int) == sizeof (long) and
the "long long" type is unavailable;
Improvidently, this means doing double math in some cases.

Note that these changes are relative to the stuff that's currently in
ftp://elsie.nci.nih.gov

				--ado

diff -c -r old/code/tzfile.h new/code/tzfile.h
*** old/code/tzfile.h	Wed Aug 11 11:59:05 2004
--- new/code/tzfile.h	Tue Oct  5 10:17:24 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.15";
  #endif /* !defined NOID */
  #endif /* !defined lint */
  
***************
*** 157,168 ****
  #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,184 ----
  #define EPOCH_WDAY	TM_THURSDAY
  
  /*
! ** Given an integral argument (a) and a positive integral argument (b),
! ** return a % b per C99.
  */
  
+ #define C99IPMOD(a, b)	((-1 % 2 < 0 || (a) >= 0) ? \
+ 				((a) % (b)) : ((a) % (b) - (b)))
+ 
  #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)
+ ** (at least under the C99 definition of %).
+ ** We use this to avoid addition overflow problems.
+ */
+ 
+ #define isleap_sum(a, b)	isleap(C99IPMOD((a), 400) + C99IPMOD((b),
400))
+ 
  #ifndef USG
  
  /*
diff -c -r old/code/asctime.c new/code/asctime.c
*** old/code/asctime.c	Wed Aug 11 11:59:06 2004
--- new/code/asctime.c	Tue Oct  5 10:27:18 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.23";
  #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 %.0lf\n"
  #define ASCTIME_FMT_B	ASCTIME_FMT
  #else /* !STRICTLY_STANDARD_ASCTIME */
  /*
***************
*** 31,37 ****
  ** 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
--- 31,37 ----
  ** 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 %-4.0lf\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     %.0lf\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;
! 	double			year;
  	char			result[MAX_ASCTIME_BUF_SIZE];
  
  	if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
***************
*** 83,89 ****
  	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.
  	*/
--- 83,89 ----
  	if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
  		mn = "???";
  	else	mn = mon_name[timeptr->tm_mon];
! 	year = (double) timeptr->tm_year + (double) TM_YEAR_BASE;
  	/*
  	** We avoid using snprintf since it's not available on all systems.
  	*/
diff -c -r old/code/strftime.c new/code/strftime.c
*** old/code/strftime.c	Thu Sep  9 11:48:53 2004
--- new/code/strftime.c	Tue Oct  5 10:20:02 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.70";
  /*
  ** 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;
  					}
***************
*** 444,455 ****
  					if (*format == 'V')
  						pt = _conv(w, "%02d",
  							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':
--- 435,446 ----
  					if (*format == 'V')
  						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;
  			case 'v':
***************
*** 484,499 ****
  					*warnp = warn2;
  				}
  				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
  				if (t->TM_ZONE != NULL)
--- 475,489 ----
  					*warnp = warn2;
  				}
  				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
  				if (t->TM_ZONE != NULL)
***************
*** 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,632 ----
  	return pt;
  }
  
+ 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;
+ {
+ 	char	buf[INT_STRLEN_MAXIMUM(int) + 2];
+ 	int	i;
+ 	char *	cp;
+ 
+ 	if (!convert_top && !convert_yy)
+ 		return pt;
+ 	cp = buf;
+ 	i = a + b;
+ 	if ((i > a) == (b > 0))
+ 		(void) sprintf(cp, "%04d", i);
+ 	else if (sizeof (long) > sizeof (int))
+ 		(void) sprintf(cp, "%04ld", (long) a + (long) b);
+ 	else 	(void) sprintf(cp, "%04.0lf", (double) a + (double) b);
+ 	i = strlen(cp) - 2;
+ 	if (!convert_top)
+ 		cp += i;
+ 	else	if (!convert_yy)
+ 			cp[i] = '\0';
+ 	return _add(cp, pt, ptlim);
+ }
+ 
  #ifdef LOCALE_HOME
  static struct lc_time_T *
  _loc P((void))
diff -c -r old/code/zdump.c new/code/zdump.c
*** old/code/zdump.c	Mon Sep  6 16:00:46 2004
--- new/code/zdump.c	Tue Oct  5 10:17:24 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.41";
  
  /*
  ** This code has been made independent of the rest of the time
***************
*** 60,69 ****
  #define DAYSPERNYEAR	365
  #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"
--- 60,94 ----
  #define DAYSPERNYEAR	365
  #endif /* !defined DAYSPERNYEAR */
  
+ #ifndef C99IPMOD
+ /*
+ ** Given an integral argument (a) and a positive integral argument (b),
+ ** return a % b per C99.
+ */
+ 
+ #define C99IPMOD(a, b)	((-1 % 2 < 0 || (a) >= 0) ? \
+ 				((a) % (b)) : ((a) % (b) - (b)))
+ #endif /* !defined C99IPMOD */
+ 
  #ifndef isleap
! #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) ==
0))
  #endif /* !defined isleap */
  
+ #ifndef isleap_sum
+ /*
+ ** 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)
+ ** (at least under the C99 definition of %).
+ ** We use this to avoid addition overflow problems.
+ */
+ 
+ #define isleap_sum(a, b)	isleap(C99IPMOD((a), 400) + C99IPMOD((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;
--- 346,352 ----
  		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;
***************
*** 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);
  }
--- 423,431 ----
  		(int) (sizeof mon_name / sizeof mon_name[0]))
  			mn = "???";
  	else		mn = mon_name[timeptr->tm_mon];
! 	(void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d %.0lf",
  		wn, mn,
  		timeptr->tm_mday, timeptr->tm_hour,
  		timeptr->tm_min, timeptr->tm_sec,
! 		(double) timeptr->tm_year + (double) TM_YEAR_BASE);
  }



More information about the tz mailing list