* 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