RE: Strftime's %C and %y formats versus wide-ranging tm_year valu es
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); }
"Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> writes:
+ #define C99IPMOD(a, b) ((-1 % 2 < 0 || (a) >= 0) ? \ + ((a) % (b)) : ((a) % (b) - (b)))
A quick reaction: this returns the wrong answer if -1%2 is 1 and a%b is zero. But see below.
+ #define isleap_sum(a, b) isleap(C99IPMOD((a), 400) + C99IPMOD((b), 400))
Surely this is overkill. You can simply use this: #define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) as this will work regardless of whether % means Fortran remainder or modulus.
! year = (double) timeptr->tm_year + (double) TM_YEAR_BASE;
This sort of trick won't work in general on hosts that have 64-bit int and 64-bit double (e.g., some Crays), due to rounding problems.
+ i = a + b; + if ((i > a) == (b > 0))
This kind of overflow-checking won't work reliably in general, since the behavior is undefined if signed integer overflow occurs. I have run into compilers where the above code won't detect overflow correctly. I have a draft solution for the above problems but would like to think about it for a day or so before posting.
participants (2)
-
Olson, Arthur David (NIH/NCI) -
Paul Eggert