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