[PROPOSED PATCH 1/9] zdump: use TM_ZONE if available
* zdump.c (abbr): Return pointer-to-const. Use TM_ZONE if available, as it's simpler, more reliable, and a tad faster. --- zdump.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/zdump.c b/zdump.c index a04ba99..9eb434e 100644 --- a/zdump.c +++ b/zdump.c @@ -225,7 +225,7 @@ static char * progname; static bool warned; static bool errout; -static char *abbr(struct tm const *); +static char const *abbr(struct tm const *); static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE; static void dumptime(struct tm const *); static time_t hunt(char *, time_t, time_t); @@ -738,16 +738,17 @@ show(char *zone, time_t t, bool v) abbrok(abbr(tmp), zone); } -static char * +static char const * abbr(struct tm const *tmp) { - register char * result; - static char nada; - - if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1) - return &nada; - result = tzname[tmp->tm_isdst]; - return (result == NULL) ? &nada : result; +#ifdef TM_ZONE + return tmp->TM_ZONE; +#else + return ((0 <= tmp->tm_isdst && tmp->tm_isdst <= 1 + && tzname[tmp->tm_isdst]) + ? tzname[tmp->tm_isdst] + : ""); +#endif } /* -- 1.9.1
* zdump.c (main): Avoid use of uninitalized locals in rare cases. * NEWS: Mention this. --- NEWS | 4 ++-- zdump.c | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 2994afd..a14929b 100644 --- a/NEWS +++ b/NEWS @@ -41,8 +41,8 @@ Unreleased, experimental changes Changes affecting code - Some crashes have been fixed when the tz library is given a - compiled time zone file containing invalid or outlandish data. + Some crashes have been fixed when zdump or the tz library is given + invalid or outlandish input. The tz library no longer mishandles leap seconds on platforms with unsigned time_t in time zones that lack ordinary transitions after 1970. diff --git a/zdump.c b/zdump.c index 9eb434e..3dde5e8 100644 --- a/zdump.c +++ b/zdump.c @@ -420,8 +420,11 @@ _("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n" int main(int argc, char *argv[]) { + /* These are static so that they're initially zero. */ static char * abbrev; static size_t abbrevsize; + static struct tm newtm; + register int i; register bool vflag; register bool Vflag; @@ -433,7 +436,6 @@ main(int argc, char *argv[]) time_t t; time_t newt; struct tm tm; - struct tm newtm; register struct tm * tmp; register struct tm * newtmp; -- 1.9.1
This is a bit cleaner and faster. * zdump.c (HAVE_LOCALTIME_R, HAVE_TZSET): Default to 1. (tzset) [!HAVE_TZSET]: (localtime_r) [!HAVE_LOCALTIME_R]: Provide a replacement. (settimezone): Call tzset after changing environ. (my_localtime_r): Rename from my_localtime, and change API to be compatible with localtime_r, not localtime. All uses changed. * Makefile, NEWS: Document this. --- Makefile | 2 ++ NEWS | 4 ++++ zdump.c | 70 ++++++++++++++++++++++++++++++++++++++++------------------------ 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 09f614c..26e8ab7 100644 --- a/Makefile +++ b/Makefile @@ -109,6 +109,7 @@ LDLIBS= # ctime_r and asctime_r incompatibly with the POSIX standard (Solaris 8). # -DHAVE_INTTYPES_H=1 if you have a pre-C99 compiler with "inttypes.h" # -DHAVE_LINK=0 if your system lacks a link function +# -DHAVE_LOCALTIME_R=0 if your system lacks a localtime_r function # -DHAVE_SETTIMEOFDAY=0 if settimeofday does not exist (SVR0?) # -DHAVE_SETTIMEOFDAY=1 if settimeofday has just 1 arg (SVR4) # -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD) @@ -117,6 +118,7 @@ LDLIBS= # -DHAVE_SYMLINK=0 if your system lacks the symlink function # -DHAVE_SYS_STAT_H=0 if your compiler lacks a "sys/stat.h" # -DHAVE_SYS_WAIT_H=0 if your compiler lacks a "sys/wait.h" +# -DHAVE_TZSET=0 if your system lacks a tzset function # -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" # -DLOCALE_HOME=\"path\" if locales are in "path", not "/usr/lib/locale" diff --git a/NEWS b/NEWS index a14929b..9912856 100644 --- a/NEWS +++ b/NEWS @@ -51,6 +51,10 @@ Unreleased, experimental changes Although not needed for tz's own applications, which are single-threaded, this supports POSIX better if the tz library is used in multithreaded apps. + zdump now uses localtime_r and tzset if available, as this is a + bit cleaner and faster than plain localtime. Compile with + -DHAVE_LOCALTIME_R=0 and/or -DHAVE_TZSET=0 if your system lacks them. + The tz code now attempts to infer TM_GMTOFF and TM_ZONE if not already defined, to make it easier to configure on common platforms. Define NO_TM_GMTOFF and NO_TM_ZONE to suppress this. diff --git a/zdump.c b/zdump.c index 3dde5e8..c417b20 100644 --- a/zdump.c +++ b/zdump.c @@ -89,6 +89,13 @@ typedef long intmax_t; # endif #endif +#ifndef HAVE_LOCALTIME_R +# define HAVE_LOCALTIME_R 1 +#endif + +#ifndef HAVE_TZSET +# define HAVE_TZSET 1 +#endif #ifndef ZDUMP_LO_YEAR #define ZDUMP_LO_YEAR (-500) @@ -264,6 +271,12 @@ sumsize(size_t a, size_t b) return sum; } +#if ! HAVE_TZSET +# undef tzset +# define tzset zdump_tzset +static void tzset(void) { } +#endif + /* Set the global time zone to VAL, exiting on memory allocation failure. */ static void settimezone(char const *val) @@ -296,18 +309,32 @@ settimezone(char const *val) env[0] = strcat(strcpy(env0, "TZ="), val); environ = fakeenv = env; free(oldstorage); + tzset(); } +#if ! HAVE_LOCALTIME_R || ! HAVE_TZSET +# undef localtime_r +# define localtime_r zdump_localtime_r +static struct tm * +localtime_r(time_t *tp, struct tm *tmp) +{ + struct tm *r = localtime(tp); + if (r) { + *tmp = *r; + r = tmp; + } + return r; +} +#endif + #ifndef TYPECHECK -#define my_localtime localtime +# define my_localtime_r localtime_r #else /* !defined TYPECHECK */ static struct tm * -my_localtime(time_t *tp) +my_localtime_r(time_t *tp, struct tm *tmp) { - register struct tm * tmp; - - tmp = localtime(tp); - if (tp != NULL && tmp != NULL) { + tmp = localtime_r(tp, tmp); + if (tmp) { struct tm tm; register time_t t; @@ -552,31 +579,25 @@ main(int argc, char *argv[]) } if (t < cutlotime) t = cutlotime; - tmp = my_localtime(&t); - if (tmp != NULL) { - tm = *tmp; + tmp = my_localtime_r(&t, &tm); + if (tmp) saveabbr(&abbrev, &abbrevsize, &tm); - } for ( ; ; ) { newt = (t < absolute_max_time - SECSPERDAY / 2 ? t + SECSPERDAY / 2 : absolute_max_time); if (cuthitime <= newt) break; - newtmp = localtime(&newt); - if (newtmp != NULL) - newtm = *newtmp; + newtmp = localtime_r(&newt, &newtm); if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) : (delta(&newtm, &tm) != (newt - t) || newtm.tm_isdst != tm.tm_isdst || strcmp(abbr(&newtm), abbrev) != 0)) { newt = hunt(argv[i], t, newt); - newtmp = localtime(&newt); - if (newtmp != NULL) { - newtm = *newtmp; + newtmp = localtime_r(&newt, &newtm); + if (newtmp) saveabbr(&abbrev, &abbrevsize, &newtm); - } } t = newt; tm = newtm; @@ -650,11 +671,9 @@ hunt(char *name, time_t lot, time_t hit) struct tm tm; register struct tm * tmp; - lotmp = my_localtime(&lot); - if (lotmp != NULL) { - lotm = *lotmp; + lotmp = my_localtime_r(&lot, &lotm); + if (lotmp) saveabbr(&loab, &loabsize, &lotm); - } for ( ; ; ) { time_t diff = hit - lot; if (diff < 2) @@ -665,9 +684,7 @@ hunt(char *name, time_t lot, time_t hit) ++t; else if (t >= hit) --t; - tmp = my_localtime(&t); - if (tmp != NULL) - tm = *tmp; + tmp = my_localtime_r(&t, &tm); if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) : (delta(&tm, &lotm) == (t - lot) && tm.tm_isdst == lotm.tm_isdst && @@ -711,6 +728,7 @@ static void show(char *zone, time_t t, bool v) { register struct tm * tmp; + struct tm tm; printf("%-*s ", longest, zone); if (v) { @@ -723,7 +741,7 @@ show(char *zone, time_t t, bool v) } printf(" = "); } - tmp = my_localtime(&t); + tmp = my_localtime_r(&t, &tm); dumptime(tmp); if (tmp != NULL) { if (*abbr(tmp) != '\0') @@ -801,7 +819,7 @@ dumptime(register const struct tm *timeptr) return; } /* - ** The packaged versions of localtime and gmtime never put out-of-range + ** The packaged localtime_r and gmtime never put out-of-range ** values in tm_wday or tm_mon, but since this code might be compiled ** with other (perhaps experimental) versions, paranoia is in order. */ -- 1.9.1
* localtime.c (SMALLEST): New macro. (time2sub) [TM_GMTOFF && !UNINIT_TRAP]: If the UTC offset doesn't match the request, try the requested offset. This catches a problem caught by -DTYPECHECK with a time stamp near a transition from LMT to standard time, where both sides of the transition have tm_isdst == 0. If !defined TM_GMTOFF || UNINIT_TRAP you're out of luck: mktime will still conform to its spec but it'll be more likely to guess wrong on these ambiguous inputs. * private.h (UNINIT_TRAP): New macro that defaults to 0. * Makefile, NEWS: Document this. --- Makefile | 2 ++ NEWS | 8 ++++++++ localtime.c | 30 ++++++++++++++++++++++++++++++ private.h | 4 ++++ 4 files changed, 44 insertions(+) diff --git a/Makefile b/Makefile index 26e8ab7..968773f 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,8 @@ LDLIBS= # the default is system-supplied, typically "/usr/lib/locale" # -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified # DST transitions if the time zone files cannot be accessed +# -DUNINIT_TRAP=1 if reading uninitialized storage can cause problems +# other than simply getting garbage data # -DZIC_MAX_ABBR_LEN_WO_WARN=3 # (or some other number) to set the maximum time zone abbreviation length # that zic will accept without a warning (the default is 6) diff --git a/NEWS b/NEWS index 9912856..3d69b1f 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,14 @@ Unreleased, experimental changes already defined, to make it easier to configure on common platforms. Define NO_TM_GMTOFF and NO_TM_ZONE to suppress this. + Unless the new macro UNINIT_TRAP is defined to 0, the tz code now + assumes that reading uninitialized memory yields garbage values + but does not cause other problems such as traps. + + If TM_GMTOFF is defined and UNINIT_TRAP is not 0, mktime is now + more likely to guess right for ambiguous time stamps near + transitions where tm_isdst does not change. + tzselect -c now uses a hybrid distance measure that works better in Africa. (Thanks to Alan Barrett for noting the problem.) diff --git a/localtime.c b/localtime.c index 7b22f72..e60842e 100644 --- a/localtime.c +++ b/localtime.c @@ -103,6 +103,7 @@ struct lsinfo { /* leap second information */ int_fast64_t ls_corr; /* correction to apply */ }; +#define SMALLEST(a, b) (((a) < (b)) ? (a) : (b)) #define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) #ifdef TZNAME_MAX @@ -1817,6 +1818,35 @@ time2sub(struct tm *const tmp, else lo = t; continue; } +#if defined TM_GMTOFF && ! UNINIT_TRAP + if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF + && (yourtm.TM_GMTOFF < 0 + ? (-SECSPERDAY <= yourtm.TM_GMTOFF + && (mytm.TM_GMTOFF <= + (SMALLEST (INT_FAST32_MAX, LONG_MAX) + + yourtm.TM_GMTOFF))) + : (yourtm.TM_GMTOFF <= SECSPERDAY + && ((BIGGEST (INT_FAST32_MIN, LONG_MIN) + + yourtm.TM_GMTOFF) + <= mytm.TM_GMTOFF)))) { + /* MYTM matches YOURTM except with the wrong UTC offset. + YOURTM.TM_GMTOFF is plausible, so try it instead. + It's OK if YOURTM.TM_GMTOFF contains uninitialized data, + since the guess gets checked. */ + time_t altt = t; + int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; + if (!increment_overflow_time(&altt, diff)) { + struct tm alttm; + if (funcp(&altt, offset, &alttm) + && alttm.tm_isdst == mytm.tm_isdst + && alttm.TM_GMTOFF == yourtm.TM_GMTOFF + && tmcomp(&alttm, &yourtm) == 0) { + t = altt; + mytm = alttm; + } + } + } +#endif if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) break; /* diff --git a/private.h b/private.h index 14a9d53..31239e1 100644 --- a/private.h +++ b/private.h @@ -413,6 +413,10 @@ static time_t const time_t_max = # define INITIALIZE(x) #endif +#ifndef UNINIT_TRAP +# define UNINIT_TRAP 0 +#endif + /* ** For the benefit of GNU folk... ** '_(MSGID)' uses the current locale's message library string for MSGID. -- 1.9.1
This fixes some schizophrenia in the build, which linked zdump to the tz library code but did not compile zdump with the tz library API. * zdump.c (USE_LTZ): New macro. Use it, not time_tz, to decide whether to include private.h. * Makefile, NEWS: Document this. * Makefile (CHECK_TIME_T_ALTERNATIVES): New macro. (public): Use it. --- Makefile | 8 +++++++- NEWS | 5 +++++ zdump.c | 10 ++++++---- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 968773f..0a8696c 100644 --- a/Makefile +++ b/Makefile @@ -136,6 +136,8 @@ LDLIBS= # DST transitions if the time zone files cannot be accessed # -DUNINIT_TRAP=1 if reading uninitialized storage can cause problems # other than simply getting garbage data +# -DUSE_LTZ=0 to build zdump with the system time zone library +# Also set TZDOBJS=zdump.o and CHECK_TIME_T_ALTERNATIVES= below. # -DZIC_MAX_ABBR_LEN_WO_WARN=3 # (or some other number) to set the maximum time zone abbreviation length # that zic will accept without a warning (the default is 6) @@ -258,6 +260,10 @@ VALIDATE_ENV = \ SP_CHARSET_FIXED=YES \ SP_ENCODING=UTF-8 +# This expensive test requires USE_LTZ. +# To suppress it, define this macro to be empty. +CHECK_TIME_T_ALTERNATIVES = check_time_t_alternatives + # SAFE_CHAR is a regular expression that matches a safe character. # Some parts of this distribution are limited to safe characters; # others can use any UTF-8 character. @@ -488,7 +494,7 @@ maintainer-clean: clean names: @echo $(ENCHILADA) -public: check check_public check_time_t_alternatives \ +public: check check_public $(CHECK_TIME_T_ALTERNATIVES) \ tarballs signatures date.1.txt: date.1 diff --git a/NEWS b/NEWS index 3d69b1f..9cf4b04 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,11 @@ Unreleased, experimental changes more likely to guess right for ambiguous time stamps near transitions where tm_isdst does not change. + zdump now builds with the tz library unless USE_LTZ is defined to 0, + This lets zdump use tz features even if the system library lacks them. + To build zdump with the system library, use 'make CFLAGS=-DUSE_LTZ=0 + TZDOBJS=zdump.o CHECK_TIME_T_ALTERNATIVES='. + tzselect -c now uses a hybrid distance measure that works better in Africa. (Thanks to Alan Barrett for noting the problem.) diff --git a/zdump.c b/zdump.c index c417b20..594e5fd 100644 --- a/zdump.c +++ b/zdump.c @@ -9,12 +9,14 @@ ** This code has been made independent of the rest of the time ** conversion package to increase confidence in the verification it provides. ** You can use this code to help in verifying other implementations. -** -** However, include private.h when debugging, so that it overrides -** time_t consistently with the rest of the package. +** To do this, compile with -DUSE_LTZ=0 and link without the tz library. */ -#ifdef time_tz +#ifndef USE_LTZ +# define USE_LTZ 1 +#endif + +#if USE_LTZ # include "private.h" #endif -- 1.9.1
These files were removed from Solaris long ago, and are not present on any current platforms. * Makefile, NEWS: Document this. * private.h (LOCALE_HOME): Remove. * strftime.c: Assume LOCALE_HOME is not defined. Do not include sys/stat.h. (localebuf, _loc) [LOCALE_HOME]: Remove. All uses removed. --- Makefile | 1 - NEWS | 2 + private.h | 4 -- strftime.c | 132 ------------------------------------------------------------- 4 files changed, 2 insertions(+), 137 deletions(-) diff --git a/Makefile b/Makefile index 0a8696c..3a52875 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,6 @@ LDLIBS= # -DHAVE_TZSET=0 if your system lacks a tzset function # -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" -# -DLOCALE_HOME=\"path\" if locales are in "path", not "/usr/lib/locale" # -DNO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU=1 # if you do not want run time warnings about formats that may cause # year 2000 grief diff --git a/NEWS b/NEWS index 9cf4b04..9432dd4 100644 --- a/NEWS +++ b/NEWS @@ -86,6 +86,8 @@ Unreleased, experimental changes For easier maintenance later, some C code has been simplified and some lint has been removed. + The long-obsolete LOCALE_HOME code has been removed. + The long-obsolete 'gtime' function has been removed. Changes affecting build procedure diff --git a/private.h b/private.h index 31239e1..5d44919 100644 --- a/private.h +++ b/private.h @@ -62,10 +62,6 @@ #define HAVE_UTMPX_H 0 #endif /* !defined HAVE_UTMPX_H */ -#ifndef LOCALE_HOME -#define LOCALE_HOME "/usr/lib/locale" -#endif /* !defined LOCALE_HOME */ - #if HAVE_INCOMPATIBLE_CTIME_R #define asctime_r _incompatible_asctime_r #define ctime_r _incompatible_ctime_r diff --git a/strftime.c b/strftime.c index 8f75499..ef2471b 100644 --- a/strftime.c +++ b/strftime.c @@ -41,15 +41,7 @@ struct lc_time_T { const char * date_fmt; }; -#ifdef LOCALE_HOME -#include "sys/stat.h" -static struct lc_time_T localebuf; -static struct lc_time_T * _loc(void); -#define Locale _loc() -#endif /* defined LOCALE_HOME */ -#ifndef LOCALE_HOME #define Locale (&C_time_locale) -#endif /* !defined LOCALE_HOME */ static const struct lc_time_T C_time_locale = { { @@ -122,9 +114,6 @@ strftime(char * const s, const size_t maxsize, const char *const format, int warn; tzset(); -#ifdef LOCALE_HOME - localebuf.mon[0] = 0; -#endif /* defined LOCALE_HOME */ warn = IN_NONE; p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn); #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU @@ -615,124 +604,3 @@ _yconv(int a, int b, bool convert_top, bool convert_yy, pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); return pt; } - -#ifdef LOCALE_HOME -static struct lc_time_T * -_loc(void) -{ - static const char locale_home[] = LOCALE_HOME; - static const char lc_time[] = "LC_TIME"; - static char * locale_buf; - - int fd; - bool oldsun; /* "...ain't got nothin' to do..." */ - char * lbuf; - char * name; - char * p; - const char ** ap; - const char * plim; - char filename[FILENAME_MAX]; - struct stat st; - size_t namesize; - size_t bufsize; - - /* - ** Use localebuf.mon[0] to signal whether locale is already set up. - */ - if (localebuf.mon[0]) - return &localebuf; - name = setlocale(LC_TIME, NULL); - if (name == NULL || *name == '\0') - goto no_locale; - /* - ** If the locale name is the same as our cache, use the cache. - */ - lbuf = locale_buf; - if (lbuf != NULL && strcmp(name, lbuf) == 0) { - p = lbuf; - for (ap = (const char **) &localebuf; - ap < (const char **) (&localebuf + 1); - ++ap) - *ap = p += strlen(p) + 1; - return &localebuf; - } - /* - ** Slurp the locale file into the cache. - */ - namesize = strlen(name) + 1; - if (sizeof filename < - ((sizeof locale_home) + namesize + (sizeof lc_time))) - goto no_locale; - oldsun = false; - sprintf(filename, "%s/%s/%s", locale_home, name, lc_time); - fd = open(filename, O_RDONLY); - if (fd < 0) { - /* - ** Old Sun systems have a different naming and data convention. - */ - oldsun = true; - sprintf(filename, "%s/%s/%s", locale_home, - lc_time, name); - fd = open(filename, O_RDONLY); - if (fd < 0) - goto no_locale; - } - if (fstat(fd, &st) != 0) - goto bad_locale; - if (st.st_size <= 0) - goto bad_locale; - bufsize = namesize + st.st_size; - locale_buf = NULL; - lbuf = (lbuf == NULL) ? malloc(bufsize) : realloc(lbuf, bufsize); - if (lbuf == NULL) - goto bad_locale; - strcpy(lbuf, name); - p = lbuf + namesize; - plim = p + st.st_size; - if (read(fd, p, st.st_size) != st.st_size) - goto bad_lbuf; - if (close(fd) != 0) - goto bad_lbuf; - /* - ** Parse the locale file into localebuf. - */ - if (plim[-1] != '\n') - goto bad_lbuf; - for (ap = (const char **) &localebuf; - ap < (const char **) (&localebuf + 1); - ++ap) { - if (p == plim) - goto bad_lbuf; - *ap = p; - while (*p != '\n') - ++p; - *p++ = '\0'; - } - if (oldsun) { - /* - ** SunOS 4 used an obsolescent format; see localdtconv(3). - ** c_fmt had the "short format for dates and times together" - ** (SunOS 4 date, "%a %b %e %T %Z %Y" in the C locale); - ** date_fmt had the "long format for dates" - ** (SunOS 4 strftime %C, "%A, %B %e, %Y" in the C locale). - ** Discard the latter in favor of the former. - */ - localebuf.date_fmt = localebuf.c_fmt; - } - /* - ** Record the successful parse in the cache. - */ - locale_buf = lbuf; - - return &localebuf; - -bad_lbuf: - free(lbuf); -bad_locale: - close(fd); -no_locale: - localebuf = C_time_locale; - locale_buf = NULL; - return &localebuf; -} -#endif /* defined LOCALE_HOME */ -- 1.9.1
* Makefile, NEWS: Document this. * private.h (HAVE_STRFTIME_L): New macro. * strftime.c (strftime_l) [HAVE_STRFTIME_L]: New function. --- Makefile | 2 ++ NEWS | 5 +++++ private.h | 8 ++++++++ strftime.c | 10 ++++++++++ 4 files changed, 25 insertions(+) diff --git a/Makefile b/Makefile index 3a52875..a4a74e6 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,8 @@ LDLIBS= # -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD) # -DHAVE_SETTIMEOFDAY=3 if settimeofday ignores 2nd arg (4.4BSD) # -DHAVE_STDINT_H=1 if you have a pre-C99 compiler with "stdint.h" +# -DHAVE_STRFTIME_L=1 if <time.h> declares locale_t and strftime_l +# This defaults to 0 if _POSIX_VERSION < 200809, 1 otherwise. # -DHAVE_SYMLINK=0 if your system lacks the symlink function # -DHAVE_SYS_STAT_H=0 if your compiler lacks a "sys/stat.h" # -DHAVE_SYS_WAIT_H=0 if your compiler lacks a "sys/wait.h" diff --git a/NEWS b/NEWS index 9432dd4..8e4be29 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,11 @@ Unreleased, experimental changes more likely to guess right for ambiguous time stamps near transitions where tm_isdst does not change. + If HAVE_STRFTIME_L is defined to 1, the tz library now defines + strftime_l for compatibility with recent versions of POSIX. + Only the C locale is supported, though. HAVE_STRFTIME_L defaults + to 1 on recent POSIX versions, and to 0 otherwise. + zdump now builds with the tz library unless USE_LTZ is defined to 0, This lets zdump use tz features even if the system library lacks them. To build zdump with the system library, use 'make CFLAGS=-DUSE_LTZ=0 diff --git a/private.h b/private.h index 5d44919..1858bae 100644 --- a/private.h +++ b/private.h @@ -101,6 +101,14 @@ #include "unistd.h" /* for F_OK, R_OK, and other POSIX goodness */ #endif /* HAVE_UNISTD_H */ +#ifndef HAVE_STRFTIME_L +# if _POSIX_VERSION < 200809 +# define HAVE_STRFTIME_L 0 +# else +# define HAVE_STRFTIME_L 1 +# endif +#endif + #ifndef F_OK #define F_OK 0 #endif /* !defined F_OK */ diff --git a/strftime.c b/strftime.c index ef2471b..632f395 100644 --- a/strftime.c +++ b/strftime.c @@ -106,6 +106,16 @@ extern char * tzname[]; #define IN_THIS 2 #define IN_ALL 3 +#if HAVE_STRFTIME_L +size_t +strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, + locale_t locale) +{ + /* Just call strftime, as only the C locale is supported. */ + return strftime(s, maxsize, format, t); +} +#endif + size_t strftime(char * const s, const size_t maxsize, const char *const format, const struct tm *const t) -- 1.9.1
* Makefile, NEWS: Document this. * localtime.c (NETBSD_INSPIRED_EXTERN): New macro. (zoneinit): New function, with tzset_unlocked's internals. (tzset_unlocked): Use it. (tzalloc, tzfree) [NETBSD_INSPIRED]: New functions. (localsub, gmtsub, time2sub, time2, time1, leapcorr): New time zone argument. All uses changed. (localsub, gmtsub): Cast to char *, since the time zone argument is a pointer-to-const. (localtime_rz): New function, with localtime_tzset's internals. (localtime_tzset): Use it. (mktime_z): New function, with mktime's internals. (mktime): Use it. (leapcorr): Pass time_t by value, not by reference. (time2posix_z): New function, with time2posix's internals. (time2posix): Use it. Omit unnecessary local. (posix2time_z): New function, with posix2time's internals. (posix2time): Use it. * private.h (NETBSD_INSPIRED): Default to 1. (localtime_rz, mktime_z, timezone_t, strftime, tzalloc, tzfree): Define if NETBSD_INSPIRED is defined. Use macros to avoid any clashes with <time.h>. (posiztime_z, time2posix_z): Likewise, but only if STD_INSPIRED is also defined. --- Makefile | 11 ++++ NEWS | 8 +++ localtime.c | 208 +++++++++++++++++++++++++++++++++++++++--------------------- private.h | 47 +++++++++++++- 4 files changed, 201 insertions(+), 73 deletions(-) diff --git a/Makefile b/Makefile index a4a74e6..f4fb8cd 100644 --- a/Makefile +++ b/Makefile @@ -197,6 +197,17 @@ GCC_DEBUG_FLAGS = -Dlint -g3 -O3 -fno-common -fstrict-aliasing \ # These functions may well disappear in future releases of the time # conversion package. # +# If you don't want functions that were inspired by NetBSD, add +# -DNETBSD_INSPIRED=0 +# to the end of the "CFLAGS=" line. Otherwise, the functions +# "localtime_rz", "mktime_z", "tzalloc", and "tzfree" are added to the +# time library, and if STD_INSPIRED is also defined the functions +# "posix2time_z" and "time2posix_z" are added as well. +# The functions ending in "_z" (or "_rz") are like their unsuffixed +# (or suffixed-by-"_r") counterparts, except with an extra first +# argument of opaque type timezone_t that specifies the time zone. +# "tzalloc" allocates a timezone_t value, and "tzfree" frees it. +# # If you want to allocate state structures in localtime, add # -DALL_STATE # to the end of the "CFLAGS=" line. Storage is obtained by calling malloc. diff --git a/NEWS b/NEWS index 8e4be29..ce49fd2 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,14 @@ Unreleased, experimental changes bit cleaner and faster than plain localtime. Compile with -DHAVE_LOCALTIME_R=0 and/or -DHAVE_TZSET=0 if your system lacks them. + Unless NETBSD_INSPIRED is defined to 0, the tz library now supplies + functions that let callers create and use objects representing time zones. + This is intended for applications that need to deal with many time + zones simultaneously, e.g., an application where each thread may be + in a different time zone. The new functions are tzalloc, tzfree, + localtime_rz, mktime_z, and (if STD_INSPIRED is also defined) + posix2time_z and time2posix_z. + The tz code now attempts to infer TM_GMTOFF and TM_ZONE if not already defined, to make it easier to configure on common platforms. Define NO_TM_GMTOFF and NO_TM_ZONE to suppress this. diff --git a/localtime.c b/localtime.c index e60842e..c68b575 100644 --- a/localtime.c +++ b/localtime.c @@ -28,6 +28,14 @@ static int lock(void) { return 0; } static void unlock(void) { } #endif +/* NETBSD_INSPIRED_EXTERN functions are exported to callers if + NETBSD_INSPIRED is defined, and are private otherwise. */ +#if NETBSD_INSPIRED +# define NETBSD_INSPIRED_EXTERN +#else +# define NETBSD_INSPIRED_EXTERN static +#endif + #ifndef TZ_ABBR_MAX_LEN #define TZ_ABBR_MAX_LEN 16 #endif /* !defined TZ_ABBR_MAX_LEN */ @@ -141,7 +149,8 @@ struct rule { #define DAY_OF_YEAR 1 /* n = day of year */ #define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d = month, week, day of week */ -static struct tm *gmtsub(time_t const *, int_fast32_t, struct tm *); +static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, + struct tm *); static bool increment_overflow(int *, int); static bool increment_overflow_time(time_t *, int_fast32_t); static bool normalize_overflow32(int_fast32_t *, int *, int); @@ -1192,6 +1201,28 @@ tzsetwall(void) } #endif +static struct state * +zoneinit(struct state *sp, char const *name) +{ + if (sp) { + if (*name == '\0') { + /* + ** User wants it fast rather than right. + */ + sp->leapcnt = 0; /* so, we're off a little */ + sp->timecnt = 0; + sp->typecnt = 0; + sp->ttis[0].tt_isdst = 0; + sp->ttis[0].tt_gmtoff = 0; + sp->ttis[0].tt_abbrind = 0; + strcpy(sp->chars, gmt); + } else if (! tzload(name, sp, true)) + if (name[0] == ':' || ! tzparse(name, sp, false)) + gmtload(sp); + } + return sp; +} + static void tzset_unlocked(void) { @@ -1211,22 +1242,7 @@ tzset_unlocked(void) if (! lclptr) lclptr = malloc(sizeof *lclptr); #endif /* defined ALL_STATE */ - if (lclptr) { - if (*name == '\0') { - /* - ** User wants it fast rather than right. - */ - lclptr->leapcnt = 0; /* so, we're off a little */ - lclptr->timecnt = 0; - lclptr->typecnt = 0; - lclptr->ttis[0].tt_isdst = false; - lclptr->ttis[0].tt_gmtoff = 0; - lclptr->ttis[0].tt_abbrind = 0; - strcpy(lclptr->chars, gmt); - } else if (! tzload(name, lclptr, true)) - if (name[0] == ':' || ! tzparse(name, lclptr, false)) - gmtload(lclptr); - } + zoneinit(lclptr, name); settzname(); lcl_is_set = shortname; } @@ -1256,6 +1272,33 @@ gmtcheck(void) unlock(); } +#if NETBSD_INSPIRED + +timezone_t +tzalloc(char const *name) +{ + timezone_t sp = malloc(sizeof *sp); + return zoneinit(sp, name); +} + +void +tzfree(timezone_t sp) +{ + free(sp); +} + +/* +** NetBSD 6.1.4 has ctime_rz, but omit it because POSIX says ctime and +** ctime_r are obsolescent and have potential security problems that +** ctime_rz would share. Callers can instead use localtime_rz + strftime. +** +** NetBSD 6.1.4 has tzgetname, but omit it because it doesn't work +** in zones with three or more time zone abbreviations. +** Callers can instead use localtime_rz + strftime. +*/ + +#endif + /* ** The easy way to behave "as if no library function calls" localtime ** is to not call it, so we drop its guts into "localsub", which can be @@ -1267,18 +1310,16 @@ gmtcheck(void) /*ARGSUSED*/ static struct tm * -localsub(const time_t *const timep, const int_fast32_t offset, +localsub(struct state const *sp, time_t const *timep, int_fast32_t offset, struct tm *const tmp) { - register struct state * sp; register const struct ttinfo * ttisp; register int i; register struct tm * result; const time_t t = *timep; - sp = lclptr; if (sp == NULL) - return gmtsub(timep, offset, tmp); + return gmtsub(gmtptr, timep, offset, tmp); if ((sp->goback && t < sp->ats[0]) || (sp->goahead && t > sp->ats[sp->timecnt - 1])) { time_t newt = t; @@ -1297,7 +1338,7 @@ localsub(const time_t *const timep, const int_fast32_t offset, if (newt < sp->ats[0] || newt > sp->ats[sp->timecnt - 1]) return NULL; /* "cannot happen" */ - result = localsub(&newt, offset, tmp); + result = localsub(sp, &newt, offset, tmp); if (result == tmp) { register int_fast64_t newy; @@ -1336,11 +1377,17 @@ localsub(const time_t *const timep, const int_fast32_t offset, result = timesub(&t, ttisp->tt_gmtoff, sp, tmp); tmp->tm_isdst = ttisp->tt_isdst; #ifdef TM_ZONE - tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind]; + tmp->TM_ZONE = (char *) &sp->chars[ttisp->tt_abbrind]; #endif /* defined TM_ZONE */ return result; } +NETBSD_INSPIRED_EXTERN struct tm * +localtime_rz(struct state *sp, time_t const *timep, struct tm *tmp) +{ + return localsub(sp, timep, 0, tmp); +} + static struct tm * localtime_tzset(time_t const *timep, struct tm *tmp, bool skip_tzset) { @@ -1351,7 +1398,7 @@ localtime_tzset(time_t const *timep, struct tm *tmp, bool skip_tzset) } if (!skip_tzset) tzset_unlocked(); - tmp = localsub(timep, 0, tmp); + tmp = localtime_rz(lclptr, timep, tmp); unlock(); return tmp; } @@ -1362,10 +1409,6 @@ localtime(const time_t *const timep) return localtime_tzset(timep, &tm, 0); } -/* -** Re-entrant version of localtime. -*/ - struct tm * localtime_r(const time_t *const timep, struct tm *tmp) { @@ -1377,8 +1420,8 @@ localtime_r(const time_t *const timep, struct tm *tmp) */ static struct tm * -gmtsub(const time_t *const timep, const int_fast32_t offset, - struct tm *const tmp) +gmtsub(struct state const *sp, time_t const *timep, int_fast32_t offset, + struct tm *tmp) { register struct tm * result; @@ -1389,7 +1432,8 @@ gmtsub(const time_t *const timep, const int_fast32_t offset, ** "UT+xxxx" or "UT-xxxx" if offset is non-zero, ** but this is no time for a treasure hunt. */ - tmp->TM_ZONE = offset ? wildabbr : gmtptr ? gmtptr->chars : gmt; + tmp->TM_ZONE = ((char *) + (offset ? wildabbr : gmtptr ? gmtptr->chars : gmt)); #endif /* defined TM_ZONE */ return result; } @@ -1408,7 +1452,7 @@ struct tm * gmtime_r(const time_t *const timep, struct tm *tmp) { gmtcheck(); - tmp = gmtsub(timep, 0, tmp); + tmp = gmtsub(gmtptr, timep, 0, tmp); return tmp; } @@ -1419,7 +1463,7 @@ offtime(const time_t *const timep, const long offset) { struct tm *tmp; gmtcheck(); - tmp = gmtsub(timep, offset, &tm); + tmp = gmtsub(gmtptr, timep, offset, &tm); return tmp; } @@ -1689,12 +1733,13 @@ tmcomp(register const struct tm *const atmp, static time_t time2sub(struct tm *const tmp, - struct tm *(*const funcp)(const time_t *, int_fast32_t, struct tm *), + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, const int_fast32_t offset, bool *okayp, bool do_norm_secs) { - register const struct state * sp; register int dir; register int i, j; register int saved_seconds; @@ -1791,7 +1836,7 @@ time2sub(struct tm *const tmp, t = lo; else if (t > hi) t = hi; - if ((*funcp)(&t, offset, &mytm) == NULL) { + if (! funcp(sp, &t, offset, &mytm)) { /* ** Assume that t is too extreme to be represented in ** a struct tm; arrange things so that it is less @@ -1837,7 +1882,7 @@ time2sub(struct tm *const tmp, int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; if (!increment_overflow_time(&altt, diff)) { struct tm alttm; - if (funcp(&altt, offset, &alttm) + if (funcp(sp, &altt, offset, &alttm) && alttm.tm_isdst == mytm.tm_isdst && alttm.TM_GMTOFF == yourtm.TM_GMTOFF && tmcomp(&alttm, &yourtm) == 0) { @@ -1855,8 +1900,6 @@ time2sub(struct tm *const tmp, ** It's okay to guess wrong since the guess ** gets checked. */ - sp = (const struct state *) - ((funcp == localsub) ? lclptr : gmtptr); if (sp == NULL) return WRONG; for (i = sp->typecnt - 1; i >= 0; --i) { @@ -1867,7 +1910,7 @@ time2sub(struct tm *const tmp, continue; newt = t + sp->ttis[j].tt_gmtoff - sp->ttis[i].tt_gmtoff; - if ((*funcp)(&newt, offset, &mytm) == NULL) + if (! funcp(sp, &newt, offset, &mytm)) continue; if (tmcomp(&mytm, &yourtm) != 0) continue; @@ -1887,14 +1930,16 @@ label: if ((newt < t) != (saved_seconds < 0)) return WRONG; t = newt; - if ((*funcp)(&t, offset, tmp)) + if (funcp(sp, &t, offset, tmp)) *okayp = true; return t; } static time_t time2(struct tm * const tmp, - struct tm * (*const funcp)(const time_t *, int_fast32_t, struct tm *), + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, const int_fast32_t offset, bool *okayp) { @@ -1905,17 +1950,18 @@ time2(struct tm * const tmp, ** (in case tm_sec contains a value associated with a leap second). ** If that fails, try with normalization of seconds. */ - t = time2sub(tmp, funcp, offset, okayp, false); - return *okayp ? t : time2sub(tmp, funcp, offset, okayp, true); + t = time2sub(tmp, funcp, sp, offset, okayp, false); + return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true); } static time_t time1(struct tm *const tmp, - struct tm *(*const funcp) (const time_t *, int_fast32_t, struct tm *), + struct tm *(*funcp) (struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, const int_fast32_t offset) { register time_t t; - register const struct state * sp; register int samei, otheri; register int sameind, otherind; register int i; @@ -1930,7 +1976,7 @@ time1(struct tm *const tmp, } if (tmp->tm_isdst > 1) tmp->tm_isdst = 1; - t = time2(tmp, funcp, offset, &okay); + t = time2(tmp, funcp, sp, offset, &okay); if (okay) return t; if (tmp->tm_isdst < 0) @@ -1948,7 +1994,6 @@ time1(struct tm *const tmp, ** We try to divine the type they started from and adjust to the ** type they need. */ - sp = (const struct state *) ((funcp == localsub) ? lclptr : gmtptr); if (sp == NULL) return WRONG; for (i = 0; i < sp->typecnt; ++i) @@ -1970,7 +2015,7 @@ time1(struct tm *const tmp, tmp->tm_sec += sp->ttis[otheri].tt_gmtoff - sp->ttis[samei].tt_gmtoff; tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, offset, &okay); + t = time2(tmp, funcp, sp, offset, &okay); if (okay) return t; tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff - @@ -1981,6 +2026,12 @@ time1(struct tm *const tmp, return WRONG; } +NETBSD_INSPIRED_EXTERN time_t +mktime_z(struct state *sp, struct tm *tmp) +{ + return time1(tmp, localsub, sp, 0); +} + time_t mktime(struct tm *const tmp) { @@ -1991,7 +2042,7 @@ mktime(struct tm *const tmp) return -1; } tzset_unlocked(); - t = time1(tmp, localsub, 0); + t = mktime_z(lclptr, tmp); unlock(); return t; } @@ -2019,7 +2070,7 @@ timeoff(struct tm *const tmp, const long offset) if (tmp) tmp->tm_isdst = 0; gmtcheck(); - t = time1(tmp, gmtsub, offset); + t = time1(tmp, gmtsub, gmtptr, offset); return t; } @@ -2040,26 +2091,30 @@ timeoff(struct tm *const tmp, const long offset) */ static int_fast64_t -leapcorr(time_t *timep) +leapcorr(struct state const *sp, time_t t) { - register struct state * sp; - register struct lsinfo * lp; + register struct lsinfo const * lp; register int i; sp = lclptr; i = sp->leapcnt; while (--i >= 0) { lp = &sp->lsis[i]; - if (*timep >= lp->ls_trans) + if (t >= lp->ls_trans) return lp->ls_corr; } return 0; } +NETBSD_INSPIRED_EXTERN time_t ATTRIBUTE_PURE +time2posix_z(struct state *sp, time_t t) +{ + return t - leapcorr(sp, t); +} + time_t time2posix(time_t t) { - time_t p; int err = lock(); if (err) { errno = err; @@ -2067,48 +2122,57 @@ time2posix(time_t t) } if (!lcl_is_set) tzset_unlocked(); - p = t - leapcorr(&t); + if (lclptr) + t = time2posix_z(lclptr, t); unlock(); - return p; + return t; } -time_t -posix2time(time_t t) +NETBSD_INSPIRED_EXTERN time_t ATTRIBUTE_PURE +posix2time_z(struct state *sp, time_t t) { time_t x; time_t y; - int err = lock(); - if (err) { - errno = err; - return -1; - } - if (!lcl_is_set) - tzset_unlocked(); /* ** For a positive leap second hit, the result ** is not unique. For a negative leap second ** hit, the corresponding time doesn't exist, ** so we return an adjacent second. */ - x = t + leapcorr(&t); - y = x - leapcorr(&x); + x = t + leapcorr(sp, t); + y = x - leapcorr(sp, x); if (y < t) { do { x++; - y = x - leapcorr(&x); + y = x - leapcorr(sp, x); } while (y < t); x -= y != t; } else if (y > t) { do { --x; - y = x - leapcorr(&x); + y = x - leapcorr(sp, x); } while (y > t); x += y != t; } - unlock(); return x; } +time_t +posix2time(time_t t) +{ + int err = lock(); + if (err) { + errno = err; + return -1; + } + if (!lcl_is_set) + tzset_unlocked(); + if (lclptr) + t = posix2time_z(lclptr, t); + unlock(); + return t; +} + #endif /* defined STD_INSPIRED */ #ifdef time_tz diff --git a/private.h b/private.h index 1858bae..00a2469 100644 --- a/private.h +++ b/private.h @@ -62,6 +62,10 @@ #define HAVE_UTMPX_H 0 #endif /* !defined HAVE_UTMPX_H */ +#ifndef NETBSD_INSPIRED +# define NETBSD_INSPIRED 1 +#endif + #if HAVE_INCOMPATIBLE_CTIME_R #define asctime_r _incompatible_asctime_r #define ctime_r _incompatible_ctime_r @@ -74,12 +78,28 @@ ** Nested includes */ +/* Avoid clashes with NetBSD by renaming NetBSD's declarations. */ +#define localtime_rz sys_localtime_rz +#define mktime_z sys_mktime_z +#define posix2time_z sys_posix2time_z +#define time2posix_z sys_time2posix_z +#define timezone_t sys_timezone_t +#define tzalloc sys_tzalloc +#define tzfree sys_tzfree +#include <time.h> +#undef localtime_rz +#undef mktime_z +#undef posix2time_z +#undef time2posix_z +#undef timezone_t +#undef tzalloc +#undef tzfree + #include "sys/types.h" /* for time_t */ #include "stdio.h" #include "errno.h" #include "string.h" #include "limits.h" /* for CHAR_BIT et al. */ -#include "time.h" #include "stdlib.h" #if HAVE_GETTEXT @@ -358,6 +378,31 @@ time_t posix2time(time_t); #endif /* +** Define functions that are ABI compatible with NetBSD but have +** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t +** and labors under the misconception that 'const timezone_t' is a +** pointer to a constant. This use of 'const' is ineffective, so it +** is not done here. What we call 'struct state' NetBSD calls +** 'struct __state', but this is a private name so it doesn't matter. +*/ +#if NETBSD_INSPIRED +typedef struct state *timezone_t; +struct tm *localtime_rz(timezone_t restrict, time_t const *restrict, + struct tm *restrict); +time_t mktime_z(timezone_t restrict, struct tm *restrict); +timezone_t tzalloc(char const *); +void tzfree(timezone_t); +# ifdef STD_INSPIRED +# if !defined posix2time_z || defined time_tz +time_t posix2time_z(timezone_t, time_t) ATTRIBUTE_PURE; +# endif +# if !defined time2posix_z || defined time_tz +time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE; +# endif +# endif +#endif + +/* ** Private function declarations. */ -- 1.9.1
Thank you for this. I haven't yet compared the code with what's currently in NetBSD, but I immediately noticed that there's no mention that a null pointer may be passed instead of a timezone_t, and this is a shorthand for UTC. Here are some extract's from NetBSD's ctime(3) man page to illustrate what I mean: FUNCTIONS [...] ctime_rz(tz, clock, buf) The ctime_rz() function is similar to ctime_r(), but it also takes a const timezone_t argument, as returned by a previous call to tzalloc(), or a null pointer denoting Coordinated Universal Time (UTC). localtime_rz(tz, clock, result) The localtime_rz() function is similar to localtime_r(), but it also takes a const timezone_t argument, returned by a previous call to tzalloc(), or a null pointer denoting Coordinated Universal Time (UTC). mktime_z(tz, tm) The mktime_z() function is similar to mktime() but it also takes a const timezone_t argument, returned by a previous call to tzalloc(), or a null pointer denoting Coordinated Universal Time (UTC). tzalloc(zone) The tzalloc() function takes as an argument a timezone name and returns a timezone_t object suitable to be used in the ctime_rz(), localtime_rz(), and mktime_z() functions. A null pointer may be passed to tzalloc() instead of a timezone name, to refer to Coordinated Universal Time (UTC). Note that instead of setting the environment variable TZ, and globally changing the behavior of the calling program, one can use multiple timezones at the same time by using separate timezone_t objects allocated by tzalloc() and calling the ``z'' variants of the functions. RETURN VALUES [...] o The tzalloc() function returns a pointer to a timezone_t object or NULL on failure, setting errno to indicate the error. It may also return NULL when the name argument is NULL, and this is not an error, but a way of referring to Coordinated Universal Time (UTC). --apb (Alan Barrett)
Alan Barrett wrote:
a null pointer may be passed instead of a timezone_t, and this is a shorthand for UTC.
Thanks, I hadn't noticed this. localtime_rz already does this but mktime_z and tzalloc don't. Proposed further patch attached.
On Sunday, August 24 2014, "Paul Eggert" wrote to "tz@iana.org" saying:
(tzalloc, tzfree) [NETBSD_INSPIRED]: New functions.
These functions aren't documented yet, as far as I can tell? So I'm uncertain about their exact intended semantics. But it seems there's no way to get a timezone_t corresponding to the system wall clock time zone, i.e. the one that would be used by the non _z versions of the functions? If the TZ environment variable is set, tzalloc(getenv("TZ")) returns the correct time zone, but if it isn't, you get GMT not localtime. (I suppose you could do readlink("/etc/localtime") in that case, but that's error-prone and not very portable.) Since NetBSD doesn't define what a NULL argument to tzalloc does, could I suggest that it indeed mean /etc/localtime? That way, tzalloc(getenv("TZ")) always does the right thing. (UTC can be obtained by passing "" to tzalloc, just as you can for the TZ variable.) I'd also suggest that tzalloc should fail for unknown time zone names, rather than silently returning GMT. This was the defined behavior of the similar proposal I made on (the predecessor of) this list back in 2001: <http://mm.icann.org/pipermail/tz/2001-June/011692.html>. It might be worth reading over some of the other discussion there as well. -- Jonathan Lennox lennox@cs.columbia.edu
On Tue, 26 Aug 2014, lennox@cs.columbia.edu wrote:
Since NetBSD doesn't define what a NULL argument to tzalloc does, could I suggest that it indeed mean /etc/localtime? That way, tzalloc(getenv("TZ")) always does the right thing. (UTC can be obtained by passing "" to tzalloc, just as you can for the TZ variable.)
NetBSD-6.x did not define what a NULL argument to tzalloc meant, but NetBSD-current defines it to mean UTC. NetBSD-7 is not yet released, so if we want to change that, now would be a good time to do so, before the "NULL means UTC" behaviour appears in any release. I like your suggestion of letting NULL refer to the system's default timezone, and "" refer to UTC.
I'd also suggest that tzalloc should fail for unknown time zone names, rather than silently returning GMT.
In NetBSD, it returns NULL for error. At least, that's the intent.
This was the defined behavior of the similar proposal I made on (the predecessor of) this list back in 2001: <http://mm.icann.org/pipermail/tz/2001-June/011692.html>. It might be worth reading over some of the other discussion there as well.
Thanks. I had forgotten that proposal. --apb (Alan Barrett)
Thanks very much for that review. lennox@cs.columbia.edu wrote:
These functions aren't documented yet, as far as I can tell?
I had been lazy, and had taken the lead from the STD_INSPIRED functions, which aren't documented.... But you're right, the new functions should be, or at least the ones that aren't STD_INSPIRED.
Since NetBSD doesn't define what a NULL argument to tzalloc does, could I suggest that it indeed mean /etc/localtime?
As Alan mentioned NetBSD-current says NULL means UTC, but your idea is better and Alan likes it too so let's do that.
I'd also suggest that tzalloc should fail for unknown time zone names, rather than silently returning GMT.
Yes, that sounds good too. Proposed further patches attached for all the above.
This is significantly faster and is cleaner internally. * Makefile, NEWS: Document this. * zdump.c (NETBSD_INSPIRED): Default to 1. (HAVE_LOCALTIME_RZ): New macro; defaults to NETBSD_INSPIRED && USE_LTZ. (timezone_t) [!HAVE_LOCALTIME_RZ]: New macro, as a substitute. (localtime_r) [!HAVE_LOCALTIME_RZ && (!HAVE_LOCALTIME_R||!HAVE_TZSET)]: (localtime_rz, tzalloc, tzfree) [!HAVE_LOCALTIME_RZ]: (mktime_rz) [!HAVE_LOCALTIME_RZ && TYPECHECK]: New substitute function and macro, compatible with NetBSD. All other uses of localtime_r changed to use localtime_rz. (settimezone): Remove; all uses replaced by tzalloc. (tzalloc): Use most of the code of the old settimezone function, but don't free the old storage. (tzfree): Free it here instead. (my_localtime_rz): Rename from my_localtime_r, and make it compatible with localtime_rz. All uses changed. (saveabbr): Return the abbreviation. If HAVE_LOCALTIME_RZ simply return the output of abbr; that's faster. (main): Diagnose any tzalloc failure. tzfree after use. (hunt, show): New timezone_t arg. All uses changed. --- Makefile | 3 ++ NEWS | 4 ++ zdump.c | 183 +++++++++++++++++++++++++++++++++++++++++---------------------- 3 files changed, 128 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index f4fb8cd..0fc8e2c 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,9 @@ LDLIBS= # -DHAVE_INTTYPES_H=1 if you have a pre-C99 compiler with "inttypes.h" # -DHAVE_LINK=0 if your system lacks a link function # -DHAVE_LOCALTIME_R=0 if your system lacks a localtime_r function +# -DHAVE_LOCALTIME_RZ=0 if you do not want zdump to use localtime_rz +# This defaults to 1 if localtime_rz is known to be available. +# localtime_rz can make zdump significantly faster, but is nonstandard. # -DHAVE_SETTIMEOFDAY=0 if settimeofday does not exist (SVR0?) # -DHAVE_SETTIMEOFDAY=1 if settimeofday has just 1 arg (SVR4) # -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD) diff --git a/NEWS b/NEWS index ce49fd2..18b6aec 100644 --- a/NEWS +++ b/NEWS @@ -85,6 +85,10 @@ Unreleased, experimental changes To build zdump with the system library, use 'make CFLAGS=-DUSE_LTZ=0 TZDOBJS=zdump.o CHECK_TIME_T_ALTERNATIVES='. + zdump now uses localtime_rz if available, as it's significantly faster. + Define HAVE_LOCALTIME_RZ to 0 to suppress this. HAVE_LOCALTIME_TZ + defaults to 1 if NETBSD_INSPIRED && USE_LTZ. + tzselect -c now uses a hybrid distance measure that works better in Africa. (Thanks to Alan Barrett for noting the problem.) diff --git a/zdump.c b/zdump.c index 594e5fd..7326c63 100644 --- a/zdump.c +++ b/zdump.c @@ -12,6 +12,9 @@ ** To do this, compile with -DUSE_LTZ=0 and link without the tz library. */ +#ifndef NETBSD_INSPIRED +# define NETBSD_INSPIRED 1 +#endif #ifndef USE_LTZ # define USE_LTZ 1 #endif @@ -95,6 +98,10 @@ typedef long intmax_t; # define HAVE_LOCALTIME_R 1 #endif +#ifndef HAVE_LOCALTIME_RZ +# define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ) +#endif + #ifndef HAVE_TZSET # define HAVE_TZSET 1 #endif @@ -213,6 +220,11 @@ enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 }; # define TZ_DOMAIN "tz" #endif +#if ! HAVE_LOCALTIME_RZ +# undef timezone_t +# define timezone_t char ** +#endif + extern char ** environ; extern int getopt(int argc, char * const argv[], const char * options); @@ -237,8 +249,8 @@ static bool errout; static char const *abbr(struct tm const *); static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_PURE; static void dumptime(struct tm const *); -static time_t hunt(char *, time_t, time_t); -static void show(char *, time_t, bool); +static time_t hunt(timezone_t, char *, time_t, time_t); +static void show(timezone_t, char *, time_t, bool); static const char *tformat(void); static time_t yeartot(intmax_t) ATTRIBUTE_PURE; @@ -279,13 +291,51 @@ sumsize(size_t a, size_t b) static void tzset(void) { } #endif -/* Set the global time zone to VAL, exiting on memory allocation failure. */ -static void -settimezone(char const *val) +#if ! HAVE_LOCALTIME_RZ + +# if ! HAVE_LOCALTIME_R || ! HAVE_TZSET +# undef localtime_r +# define localtime_r zdump_localtime_r +static struct tm * +localtime_r(time_t *tp, struct tm *tmp) +{ + struct tm *r = localtime(tp); + if (r) { + *tmp = *r; + r = tmp; + } + return r; +} +# endif + +# undef localtime_rz +# define localtime_rz zdump_localtime_rz +static struct tm * +localtime_rz(timezone_t rz, time_t *tp, struct tm *tmp) +{ + return localtime_r(tp, tmp); +} + +# ifdef TYPECHECK +# undef mktime_z +# define mktime_z zdump_mktime_z +static time_t +mktime_z(timezone_t tz, struct tm *tmp) +{ + return mktime(tmp); +} +# endif + +# undef tzalloc +# undef tzfree +# define tzalloc zdump_tzalloc +# define tzfree zdump_tzfree + +static timezone_t +tzalloc(char const *val) { static char **fakeenv; char **env = fakeenv; - char *oldstorage = env ? env[0] : 0; char *env0; if (! env) { char **e = environ; @@ -310,38 +360,32 @@ settimezone(char const *val) } env[0] = strcat(strcpy(env0, "TZ="), val); environ = fakeenv = env; - free(oldstorage); tzset(); + return env; } -#if ! HAVE_LOCALTIME_R || ! HAVE_TZSET -# undef localtime_r -# define localtime_r zdump_localtime_r -static struct tm * -localtime_r(time_t *tp, struct tm *tmp) +static void +tzfree(timezone_t env) { - struct tm *r = localtime(tp); - if (r) { - *tmp = *r; - r = tmp; - } - return r; + environ = env + 1; + free(env[0]); } -#endif +#endif /* ! HAVE_LOCALTIME_RZ */ #ifndef TYPECHECK -# define my_localtime_r localtime_r +# define my_localtime_rz localtime_rz #else /* !defined TYPECHECK */ + static struct tm * -my_localtime_r(time_t *tp, struct tm *tmp) +my_localtime_rz(timezone_t tz, time_t *tp, struct tm *tmp) { - tmp = localtime_r(tp, tmp); + tmp = localtime_rz(tz, tp, tmp); if (tmp) { struct tm tm; register time_t t; tm = *tmp; - t = mktime(&tm); + t = mktime_z(tz, &tm); if (t != *tp) { fflush(stdout); fprintf(stderr, "\n%s: ", progname); @@ -399,27 +443,34 @@ abbrok(const char *const abbrp, const char *const zone) warned = errout = true; } -/* Save into *BUF (of size *BUFALLOC) the time zone abbreviation of TMP. +/* Return a time zone abbreviation. If the abbreviation needs to be + saved, use *BUF (of size *BUFALLOC) to save it, and return the + abbreviation in the possibly-reallocated *BUF. Otherwise, just + return the abbreviation. Get the abbreviation from TMP. Exit on memory allocation failure. */ -static void +static char const * saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp) { char const *ab = abbr(tmp); - size_t ablen = strlen(ab); - if (*bufalloc <= ablen) { - free(*buf); - - /* Make the new buffer at least twice as long as the old, - to avoid O(N**2) behavior on repeated calls. */ - *bufalloc = sumsize(*bufalloc, ablen + 1); - - *buf = malloc(*bufalloc); - if (! *buf) { - perror(progname); - exit(EXIT_FAILURE); + if (HAVE_LOCALTIME_RZ) + return ab; + else { + size_t ablen = strlen(ab); + if (*bufalloc <= ablen) { + free(*buf); + + /* Make the new buffer at least twice as long as the old, + to avoid O(N**2) behavior on repeated calls. */ + *bufalloc = sumsize(*bufalloc, ablen + 1); + + *buf = malloc(*bufalloc); + if (! *buf) { + perror(progname); + exit(EXIT_FAILURE); + } } + return strcpy(*buf, ab); } - strcpy(*buf, ab); } static void @@ -567,39 +618,45 @@ main(int argc, char *argv[]) } for (i = optind; i < argc; ++i) { - settimezone(argv[i]); + timezone_t tz = tzalloc(argv[i]); + char const *ab; + if (!tz) { + perror("tzalloc"); + return EXIT_FAILURE; + } if (! (vflag | Vflag)) { - show(argv[i], now, false); + show(tz, argv[i], now, false); + tzfree(tz); continue; } warned = false; t = absolute_min_time; if (!Vflag) { - show(argv[i], t, true); + show(tz, argv[i], t, true); t += SECSPERDAY; - show(argv[i], t, true); + show(tz, argv[i], t, true); } if (t < cutlotime) t = cutlotime; - tmp = my_localtime_r(&t, &tm); + tmp = my_localtime_rz(tz, &t, &tm); if (tmp) - saveabbr(&abbrev, &abbrevsize, &tm); + ab = saveabbr(&abbrev, &abbrevsize, &tm); for ( ; ; ) { newt = (t < absolute_max_time - SECSPERDAY / 2 ? t + SECSPERDAY / 2 : absolute_max_time); if (cuthitime <= newt) break; - newtmp = localtime_r(&newt, &newtm); + newtmp = localtime_rz(tz, &newt, &newtm); if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) : (delta(&newtm, &tm) != (newt - t) || newtm.tm_isdst != tm.tm_isdst || - strcmp(abbr(&newtm), abbrev) != 0)) { - newt = hunt(argv[i], t, newt); - newtmp = localtime_r(&newt, &newtm); + strcmp(abbr(&newtm), ab) != 0)) { + newt = hunt(tz, argv[i], t, newt); + newtmp = localtime_rz(tz, &newt, &newtm); if (newtmp) - saveabbr(&abbrev, &abbrevsize, - &newtm); + ab = saveabbr(&abbrev, &abbrevsize, + &newtm); } t = newt; tm = newtm; @@ -608,10 +665,11 @@ main(int argc, char *argv[]) if (!Vflag) { t = absolute_max_time; t -= SECSPERDAY; - show(argv[i], t, true); + show(tz, argv[i], t, true); t += SECSPERDAY; - show(argv[i], t, true); + show(tz, argv[i], t, true); } + tzfree(tz); } close_file(stdout); if (errout && (ferror(stderr) || fclose(stderr) != 0)) @@ -663,19 +721,20 @@ yeartot(const intmax_t y) } static time_t -hunt(char *name, time_t lot, time_t hit) +hunt(timezone_t tz, char *name, time_t lot, time_t hit) { static char * loab; static size_t loabsize; + char const * ab; time_t t; struct tm lotm; register struct tm * lotmp; struct tm tm; register struct tm * tmp; - lotmp = my_localtime_r(&lot, &lotm); + lotmp = my_localtime_rz(tz, &lot, &lotm); if (lotmp) - saveabbr(&loab, &loabsize, &lotm); + ab = saveabbr(&loab, &loabsize, &lotm); for ( ; ; ) { time_t diff = hit - lot; if (diff < 2) @@ -686,18 +745,18 @@ hunt(char *name, time_t lot, time_t hit) ++t; else if (t >= hit) --t; - tmp = my_localtime_r(&t, &tm); + tmp = my_localtime_rz(tz, &t, &tm); if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) : (delta(&tm, &lotm) == (t - lot) && tm.tm_isdst == lotm.tm_isdst && - strcmp(abbr(&tm), loab) == 0)) { + strcmp(abbr(&tm), ab) == 0)) { lot = t; lotm = tm; lotmp = tmp; } else hit = t; } - show(name, lot, true); - show(name, hit, true); + show(tz, name, lot, true); + show(tz, name, hit, true); return hit; } @@ -727,7 +786,7 @@ delta(struct tm * newp, struct tm *oldp) } static void -show(char *zone, time_t t, bool v) +show(timezone_t tz, char *zone, time_t t, bool v) { register struct tm * tmp; struct tm tm; @@ -743,7 +802,7 @@ show(char *zone, time_t t, bool v) } printf(" = "); } - tmp = my_localtime_r(&t, &tm); + tmp = my_localtime_rz(tz, &t, &tm); dumptime(tmp); if (tmp != NULL) { if (*abbr(tmp) != '\0') @@ -821,7 +880,7 @@ dumptime(register const struct tm *timeptr) return; } /* - ** The packaged localtime_r and gmtime never put out-of-range + ** The packaged localtime_rz and gmtime never put out-of-range ** values in tm_wday or tm_mon, but since this code might be compiled ** with other (perhaps experimental) versions, paranoia is in order. */ -- 1.9.1
On 2014-08-25 00:59, Paul Eggert wrote:
diff --git a/NEWS b/NEWS index ce49fd2..18b6aec 100644 --- a/NEWS +++ b/NEWS @@ -85,6 +85,10 @@ Unreleased, experimental changes
+ Define HAVE_LOCALTIME_RZ to 0 to suppress this. HAVE_LOCALTIME_TZ - s/b Define HAVE_LOCALTIME_RZ to 0 to suppress this. HAVE_LOCALTIME_RZ + + defaults to 1 if NETBSD_INSPIRED && USE_LTZ.
-- Take care. Thanks, Brian Inglis
Thanks, I fixed that misspelling. No doubt I have "tz" on the brain....
participants (4)
-
Alan Barrett -
Brian Inglis -
lennox@cs.columbia.edu -
Paul Eggert