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