Below are proposed asctime-related changes to the time zone package; in particular, documentation and Makefile changes are now included. I hope to roll out new versions of tzcode and tzdata incorporating these changes in about a week; I'll then tackle other suggested changes that have been circulated on the time zone mailing list of late. --ado diff -c -r old/Makefile new/Makefile *** old/Makefile Mon Jul 19 14:33:20 2004 --- new/Makefile Thu Aug 5 10:59:53 2004 *************** *** 1,4 **** ! # @(#)Makefile 7.92 # 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.94 # 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,108 **** --- 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" *************** *** 244,251 **** TZCSRCS= zic.c localtime.c asctime.c scheck.c ialloc.c TZCOBJS= zic.o localtime.o asctime.o scheck.o ialloc.o ! TZDSRCS= zdump.c localtime.c asctime.c ialloc.c ! TZDOBJS= zdump.o localtime.o asctime.o ialloc.o DATESRCS= date.c localtime.c logwtmp.c strftime.c asctime.c DATEOBJS= date.o localtime.o logwtmp.o strftime.o asctime.o LIBSRCS= localtime.c asctime.c difftime.c --- 246,253 ---- TZCSRCS= zic.c localtime.c asctime.c scheck.c ialloc.c TZCOBJS= zic.o localtime.o asctime.o scheck.o ialloc.o ! TZDSRCS= zdump.c localtime.c ialloc.c ! TZDOBJS= zdump.o localtime.o ialloc.o DATESRCS= date.c localtime.c logwtmp.c strftime.c asctime.c DATEOBJS= date.o localtime.o logwtmp.o strftime.o asctime.o LIBSRCS= localtime.c asctime.c difftime.c diff -c -r old/asctime.c new/asctime.c *** old/asctime.c Mon Jul 19 14:33:22 2004 --- new/asctime.c Thu Aug 5 10:21:02 2004 *************** *** 5,11 **** #ifndef lint #ifndef NOID ! static char elsieid[] = "@(#)asctime.c 7.9"; #endif /* !defined NOID */ #endif /* !defined lint */ --- 5,11 ---- #ifndef lint #ifndef NOID ! static char elsieid[] = "@(#)asctime.c 7.19"; #endif /* !defined NOID */ #endif /* !defined lint */ *************** *** 14,23 **** #include "private.h" #include "tzfile.h" /* ! ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, Second Edition, 1996-07-12. */ char * asctime_r(timeptr, buf) register const struct tm * timeptr; --- 14,65 ---- #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. ! ** At least some versions of gcc warn about the %02.2d; ignore the warning. */ + /* + ** All years associated with 32-bit time_t values are exactly four digits long; + ** some years associated with 64-bit time_t values are not. + ** 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 + ** 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 + /* + ** Big enough for something such as + ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n + ** (two three-character abbreviations, five strings denoting integers, + ** seven explicit spaces, two explicit colons, a newline, + ** and a trailing ASCII nul). + ** The values above are for systems where an int is 32 bits and are provided + ** as an example; the define below calculates the maximum for the system at + ** hand. + */ + #define MAX_ASCTIME_BUF_SIZE (2*3+5*INT_STRLEN_MAXIMUM(int)+7+2+1+1) + + static char buf_asctime[MAX_ASCTIME_BUF_SIZE]; + + /* + ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition. + */ + char * asctime_r(timeptr, buf) register const struct tm * timeptr; *************** *** 32,37 **** --- 74,81 ---- }; 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) wn = "???"; *************** *** 39,59 **** if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) mn = "???"; else mn = mon_name[timeptr->tm_mon]; /* ! ** The X3J11-suggested format is ! ** "%.3s %.3s%3d %02.2d:%02.2d:%02.2d %d\n" ! ** Since the .2 in 02.2d is ignored, we drop it. */ ! (void) sprintf(buf, "%.3s %.3s%3d %02d:%02d:%02d %d\n", wn, mn, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, ! TM_YEAR_BASE + timeptr->tm_year); ! return buf; } /* ! ** A la X3J11, with core dump avoidance. */ char * --- 83,113 ---- 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, ! year); ! if (strlen(result) < STD_ASCTIME_BUF_SIZE || buf == buf_asctime) { ! (void) strcpy(buf, result); ! return buf; ! } else { ! #ifdef EOVERFLOW ! errno = EOVERFLOW; ! #else /* !defined EOVERFLOW */ ! errno = EINVAL; ! #endif /* !defined EOVERFLOW */ ! return NULL; ! } } /* ! ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, 2004 Edition. */ char * *************** *** 60,74 **** asctime(timeptr) register const struct tm * timeptr; { ! /* ! ** Big enough for something such as ! ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n ! ** (two three-character abbreviations, five strings denoting integers, ! ** three explicit spaces, two explicit colons, a newline, ! ** and a trailing ASCII nul). ! */ ! static char result[3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) + ! 3 + 2 + 1 + 1]; ! ! return asctime_r(timeptr, result); } --- 114,118 ---- asctime(timeptr) register const struct tm * timeptr; { ! return asctime_r(timeptr, buf_asctime); } diff -c -r old/newctime.3 new/newctime.3 *** old/newctime.3 Mon Jul 19 14:33:21 2004 --- new/newctime.3 Thu Aug 5 11:49:18 2004 *************** *** 39,53 **** representing the time in seconds since 00:00:00 UTC, 1970-01-01, and returns a pointer to a ! 26-character string ! of the form .br .ce .eo Thu Nov 24 18:22:48 1986\n\0 .ec .br ! All the fields have constant width. .PP .IR Localtime\^ and --- 39,59 ---- representing the time in seconds since 00:00:00 UTC, 1970-01-01, and returns a pointer to a ! string of the form .br .ce .eo Thu Nov 24 18:22:48 1986\n\0 + .br + .ce .ec + For years longer than four characters, the string is of the form .br ! .ce ! .eo ! Thu Nov 24 18:22:48 81986\n\0 ! .ec ! .br .PP .IR Localtime\^ and *************** *** 72,81 **** .PP .I Asctime\^ converts a time value contained in a ! ``tm'' structure to a 26-character string, as shown in the above example, ! and returns a pointer ! to the string. .PP .I Mktime\^ converts the broken-down time, --- 78,86 ---- .PP .I Asctime\^ converts a time value contained in a ! ``tm'' structure to a string, as shown in the above example, ! and returns a pointer to the string. .PP .I Mktime\^ converts the broken-down time, *************** *** 211,214 **** Avoid using out-of-range values with .I mktime when setting up lunch with promptness sticklers in Riyadh. ! .\" @(#)newctime.3 7.14 --- 216,219 ---- Avoid using out-of-range values with .I mktime when setting up lunch with promptness sticklers in Riyadh. ! .\" @(#)newctime.3 7.15 diff -c -r old/newctime.3.txt new/newctime.3.txt *** old/newctime.3.txt Mon Jul 19 14:33:34 2004 --- new/newctime.3.txt Thu Aug 5 11:49:27 2004 *************** *** 36,45 **** DESCRIPTION Ctime converts a long integer, pointed to by clock, representing the time in seconds since 00:00:00 UTC, 1970- ! 01-01, and returns a pointer to a 26-character string of the ! form Thu Nov 24 18:22:48 1986\n\0 ! All the fields have constant width. Localtime and gmtime return pointers to ``tm'' structures, described below. Localtime corrects for the time zone and --- 36,45 ---- DESCRIPTION Ctime converts a long integer, pointed to by clock, representing the time in seconds since 00:00:00 UTC, 1970- ! 01-01, and returns a pointer to a string of the form Thu Nov 24 18:22:48 1986\n\0 ! For years longer than four characters, the string is of the form ! Thu Nov 24 18:22:48 81986\n\0 Localtime and gmtime return pointers to ``tm'' structures, described below. Localtime corrects for the time zone and *************** *** 52,59 **** Gmtime converts to Coordinated Universal Time. Asctime converts a time value contained in a ``tm'' ! structure to a 26-character string, as shown in the above ! example, and returns a pointer to the string. Mktime converts the broken-down time, expressed as local time, in the structure pointed to by tm into a calendar time --- 52,59 ---- Gmtime converts to Coordinated Universal Time. Asctime converts a time value contained in a ``tm'' ! structure to a string, as shown in the above example, and ! returns a pointer to the string. Mktime converts the broken-down time, expressed as local time, in the structure pointed to by tm into a calendar time diff -c -r old/zdump.c new/zdump.c *** old/zdump.c Mon Jul 19 14:33:22 2004 --- new/zdump.c Thu Aug 5 10:48:37 2004 *************** *** 1,4 **** ! static char elsieid[] = "@(#)zdump.c 7.33"; /* ** This code has been made independent of the rest of the time --- 1,4 ---- ! static char elsieid[] = "@(#)zdump.c 7.34"; /* ** This code has been made independent of the rest of the time *************** *** 129,134 **** --- 129,135 ---- static size_t longest; static char * progname; static void show P((char * zone, time_t t, int v)); + static void dumptime P((const struct tm * tmp)); int main(argc, argv) *************** *** 343,352 **** struct tm * tmp; (void) printf("%-*s ", (int) longest, zone); ! if (v) ! (void) printf("%.24s UTC = ", asctime(gmtime(&t))); tmp = localtime(&t); ! (void) printf("%.24s", asctime(tmp)); if (*abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); if (v) { --- 344,355 ---- struct tm * tmp; (void) printf("%-*s ", (int) longest, zone); ! if (v) { ! dumptime(gmtime(&t)); ! (void) printf(" UTC = "); ! } tmp = localtime(&t); ! dumptime(tmp); if (*abbr(tmp) != '\0') (void) printf(" %s", abbr(tmp)); if (v) { *************** *** 369,372 **** --- 372,404 ---- return &nada; result = tzname[tmp->tm_isdst]; return (result == NULL) ? &nada : result; + } + + static void + dumptime(timeptr) + register const struct tm * timeptr; + { + static const char wday_name[][3] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char mon_name[][3] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + register const char * wn; + register const char * mn; + + if (timeptr->tm_wday < 0 || + timeptr->tm_wday >= sizeof wday_name / sizeof wday_name[0]) + wn = "???"; + else wn = wday_name[timeptr->tm_wday]; + if (timeptr->tm_mon < 0 || + timeptr->tm_mon >= 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); }
"Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> writes:
Below are proposed asctime-related changes to the time zone package
Thanks. I compiled and tested them and have a couple of thoughts. First, GCC warns about zdump.c's new signed-versus-unsigned comparisons "timeptr->tm_wday >= sizeof ...." and similarly for tm_mon. The comparisons aren't needed here, since the values are guaranteed to be in range, so the easiest fix is to remove them. Second, it'd be nice if the asctime/ctime documentation explains more of the stuff we've discussed recently. Here are proposed further patches along these lines: they assume the patches you just sent. =================================================================== RCS file: RCS/newctime.3,v retrieving revision 2004.2.1.1 retrieving revision 2004.2.1.1.0.1 diff -pu -r2004.2.1.1 -r2004.2.1.1.0.1 --- newctime.3 2004/08/05 16:04:29 2004.2.1.1 +++ newctime.3 2004/08/05 19:20:33 2004.2.1.1.0.1 @@ -45,8 +45,12 @@ string of the form .eo Thu Nov 24 18:22:48 1986\n\0 .br -.ce .ec +where \en and \e0 represent a newline and null character, respectively. +Years requiring fewer than four characters are padded with +trailing spaces. +The year before the year 1 is the year 0, the year before that is +the year \-1, and so forth. For years longer than four characters, the string is of the form .br .ce @@ -54,8 +58,11 @@ For years longer than four characters, t Thu Nov 24 18:22:48 81986\n\0 .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 -.IR Localtime\^ +.I Localtime\^ and .I gmtime\^ return pointers to ``tm'' structures, described below. @@ -213,6 +220,20 @@ will also be overwritten at the next cal (and by calls to .IR tzset ). .PP +.I Asctime\^ +and +.I ctime\^ +behave strangely for years before 1000 or after 9999. +The 1989 and 1999 editions of the C Standard say +that years from \-99 through 999 are converted without +extra spaces, but this conflicts with longstanding +tradition and with this implementation. +Traditional implementations of these two functions are +restricted to years in the range 1900 through 2099. +To avoid this portability mess, new programs should use +.I strftime\^ +instead. +.PP Avoid using out-of-range values with .I mktime when setting up lunch with promptness sticklers in Riyadh. =================================================================== RCS file: RCS/zdump.c,v retrieving revision 2004.2.1.1 retrieving revision 2004.2.1.1.0.1 diff -pu -r2004.2.1.1 -r2004.2.1.1.0.1 --- zdump.c 2004/08/05 16:04:29 2004.2.1.1 +++ zdump.c 2004/08/05 19:19:36 2004.2.1.1.0.1 @@ -378,26 +378,9 @@ static void dumptime(timeptr) register const struct tm * timeptr; { - static const char wday_name[][3] = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - static const char mon_name[][3] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - register const char * wn; - register const char * mn; - - if (timeptr->tm_wday < 0 || - timeptr->tm_wday >= sizeof wday_name / sizeof wday_name[0]) - wn = "???"; - else wn = wday_name[timeptr->tm_wday]; - if (timeptr->tm_mon < 0 || - timeptr->tm_mon >= 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, + "SunMonTueWedThuFriSat" + 3 * timeptr->tm_wday, + "JanFebMarAprMayJunJulAugSepOctNovDec" + 3 * timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec, timeptr->tm_year + (long) TM_YEAR_BASE);
participants (2)
-
Olson, Arthur David (NIH/NCI) -
Paul Eggert