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

Paul Eggert eggert at CS.UCLA.EDU
Thu Oct 14 20:22:23 UTC 2004


"Olson, Arthur David (NIH/NCI)" <olsona at dc37a.nci.nih.gov> writes:

> 1. The asctime function punts to strftime to generate output representing
> the year

Unfortunately on further thought I noticed that the proposed asctime
mishandles the STRICTLY_STANDARD_ASCTIME case.  The standard requires
a behavior as if %d were used, which means the year 999 should
generate "999", not "0999".

For strftime, the new _yconv is returns char *, but there's a missing
"return pt;" before the end.  The logic for "lead" and "trail" isn't
right; for example, if a and b are both 99, then "trail" will be 198.
These days my impression is that a single integer division is
typically faster than looping once or twice, so I'll propose a fix
based on division below.  There's an "if (...) if (...) ... else ..."
that some compilers (e.g., GCC) will complain about and will suggest
adding brackets.  Finally, if "top" and "yy" are renamed to "lead" and
"trail", then "convert_top" and "convert_yy" should be renamed
similarly.

zdump.c's logic is a bit odd, e.g., it adds TM_YEAR_BASE % DIVISOR
(which is zero) I guess for documentation, but it doesn't really
handle the case of arbitrary TM_YEAR_BASE values correctly.  As things
stand the resulting "while" could be just an "if".  Anyway, the patch
proposed below makes zdump like strftime.

The following proposed patch assumes the patch you just sent.

===================================================================
RCS file: RCS/asctime.c,v
retrieving revision 2004.3.1.2
retrieving revision 2004.3.0.4
diff -pc -r2004.3.1.2 -r2004.3.0.4
*** asctime.c	2004/10/14 18:03:04	2004.3.1.2
--- asctime.c	2004/10/14 20:14:54	2004.3.0.4
*************** static char	elsieid[] = "@(#)asctime.c	7
*** 15,22 ****
  #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 */
  /*
  ** Some systems only handle "%.2d"; others only handle "%02d";
--- 15,22 ----
  #include "tzfile.h"
  
  #if STRICTLY_STANDARD_ASCTIME
! #define ASCTIME_FMT	"%.3s %.3s%3d %.2d:%.2d:%.2d %d\n"
! #define ASCTIME_FMT_B	"%.3s %.3s%3d %.2d:%.2d:%.2d %s\n"
  #else /* !STRICTLY_STANDARD_ASCTIME */
  /*
  ** Some systems only handle "%.2d"; others only handle "%02d";
*************** static char	elsieid[] = "@(#)asctime.c	7
*** 31,37 ****
  ** 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
--- 31,37 ----
  ** 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 %-4d\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
*************** char *				buf;
*** 84,104 ****
  		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,
! 		year);
  	if (strlen(result) < STD_ASCTIME_BUF_SIZE || buf == buf_asctime) {
  		(void) strcpy(buf, result);
  		return buf;
--- 84,114 ----
  		mn = "???";
  	else	mn = mon_name[timeptr->tm_mon];
  	/*
  	** We avoid using snprintf since it's not available on all systems.
  	*/
! 	if (-999 - TM_YEAR_BASE <= timeptr->tm_year
! 	    && timeptr->tm_year <= 9999 - TM_YEAR_BASE) {
! 		(void) sprintf(result,
! 			ASCTIME_FMT,
! 			wn, mn,
! 			timeptr->tm_mday, timeptr->tm_hour,
! 			timeptr->tm_min, timeptr->tm_sec,
! 			timeptr->tm_year + TM_YEAR_BASE);
! 	} else {
! 		/*
! 		** 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);
! 		(void) sprintf(result,
! 			ASCTIME_FMT_B,
! 			wn, mn,
! 			timeptr->tm_mday, timeptr->tm_hour,
! 			timeptr->tm_min, timeptr->tm_sec,
! 			year);
! 	}
  	if (strlen(result) < STD_ASCTIME_BUF_SIZE || buf == buf_asctime) {
  		(void) strcpy(buf, result);
  		return buf;
===================================================================
RCS file: RCS/strftime.c,v
retrieving revision 2004.4.1.2
retrieving revision 2004.4.0.5
diff -pc -r2004.4.1.2 -r2004.4.0.5
*** strftime.c	2004/10/14 18:03:04	2004.4.1.2
--- strftime.c	2004/10/14 20:14:54	2004.4.0.5
*************** const char * const	ptlim;
*** 606,616 ****
  */
  
  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;
  {
--- 606,616 ----
  */
  
  static char *
! _yconv(a, b, convert_lead, convert_trail, pt, ptlim)
  const int		a;
  const int		b;
! const int		convert_lead;
! const int		convert_trail;
  char *			pt;
  const char * const	ptlim;
  {
*************** const char * const	ptlim;
*** 618,639 ****
  	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
--- 618,643 ----
  	register int	trail;
  
  #define DIVISOR	100
  	trail = a % DIVISOR + b % DIVISOR;
! 	lead = a / DIVISOR + b / DIVISOR;
! 	lead += trail / DIVISOR;
! 	trail %= DIVISOR;
! 	if (trail < 0 && 0 < lead) {
  		trail += DIVISOR;
  		--lead;
! 	} else if (lead < 0 && 0 < trail) {
  		trail -= DIVISOR;
  		++lead;
  	}
! 	if (convert_lead) {
  		if (lead == 0 && trail < 0)
  			pt = _add("-0", pt, ptlim);
  		else	pt = _conv(lead, "%02d", pt, ptlim);
! 	}
! 	if (convert_trail)
  		pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
+ 
+ 	return pt;
  }
  
  #ifdef LOCALE_HOME
===================================================================
RCS file: RCS/zdump.c,v
retrieving revision 2004.4.1.2
retrieving revision 2004.4.0.5
diff -pc -r2004.4.1.2 -r2004.4.0.5
*** zdump.c	2004/10/14 18:03:04	2004.4.1.2
--- zdump.c	2004/10/14 20:14:54	2004.4.0.5
*************** register const struct tm *	timeptr;
*** 413,424 ****
  		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;
  	}
--- 413,423 ----
  		timeptr->tm_min, timeptr->tm_sec);
  #define DIVISOR	10
  	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR;
! 	trail = timeptr->tm_year % DIVISOR;
! 	if (trail < 0 && 0 < lead) {
  		trail += DIVISOR;
  		--lead;
! 	} else if (lead < 0 && 0 < trail) {
  		trail -= DIVISOR;
  		++lead;
  	}



More information about the tz mailing list