From cd59570c180e34a6716c2d61a3047ac2f9407795 Mon Sep 17 00:00:00 2001 From: Dag-Erling Smørgrav Date: Mon, 2 Mar 2026 18:21:42 +0100 Subject: Import tzcode 2026a --- Makefile | 48 ++-- NEWS | 99 +++++++- date.1 | 2 +- date.1.txt | 2 +- localtime.c | 750 +++++++++++++++++++++++++++++-------------------------- newctime.3 | 6 +- newctime.3.txt | 9 +- newtzset.3 | 29 +-- newtzset.3.txt | 24 +- private.h | 55 ++-- strftime.c | 5 +- theory.html | 22 +- time2posix.3 | 134 ++++++---- time2posix.3.txt | 87 ++++--- tz-art.html | 16 +- tz-how-to.html | 72 +----- tz-link.html | 139 ++++++----- tzfile.5 | 7 +- tzfile.5.txt | 9 +- tzfile.h | 24 +- version | 2 +- zdump.c | 3 +- zic.8 | 20 +- zic.8.txt | 22 +- zic.c | 229 +++++++++-------- 25 files changed, 995 insertions(+), 820 deletions(-) diff --git a/Makefile b/Makefile index bb7cb1014f73..1e0a5903534d 100644 --- a/Makefile +++ b/Makefile @@ -66,28 +66,6 @@ DATAFORM= main LOCALTIME= Factory -# The POSIXRULES macro controls interpretation of POSIX-like TZ -# settings like TZ='EET-2EEST' that lack DST transition rules. -# If POSIXRULES is '-', no template is installed; this is the default. -# Any other value for POSIXRULES is obsolete and should not be relied on, as: -# * It does not work correctly in popular implementations such as GNU/Linux. -# * It does not work even in tzcode, except for historical timestamps -# that precede the last explicit transition in the POSIXRULES file. -# Hence it typically does not work for current and future timestamps. -# If, despite the above, you want a template for handling these settings, -# you can change the line below (after finding the timezone you want in the -# one of the $(TDATA) source files, or adding it to a source file). -# Alternatively, if you discover you've got the wrong timezone, you can just -# 'zic -p -' to remove it, or 'zic -p rightzone' to change it. -# Use the command -# make zonenames -# to get a list of the values you can use for POSIXRULES. - -POSIXRULES= - - -# Also see TZDEFRULESTRING below, which takes effect only -# if POSIXRULES is '-' or if the template file cannot be accessed. - # Installation locations. # @@ -170,7 +148,7 @@ TIME_T_ALTERNATIVES_TAIL = int_least32_t.ck uint_least32_t.ck \ # applications that are not leap second aware, and is closer to unsmeared # "right" time than unsmeared POSIX time is (e.g., 0.5 vs 1.0 s max error). -REDO= posix_right +REDO= posix_only # Whether to put an "Expires" line in the leapseconds file. # Use EXPIRES_LINE=1 to put the line in, 0 to omit it. @@ -284,7 +262,10 @@ LDLIBS= # -DHAVE_STRDUP=0 if your system lacks the strdup function # -DHAVE_STRNLEN=0 if your system lacks the strnlen function+ # -DHAVE_STRTOLL=0 if your system lacks the strtoll function+ -# -DHAVE_STRUCT_STAT_ST_CTIM=0 if struct stat lacks a member st_ctim+ +# -DHAVE_STRUCT_STAT_ST_CTIM=0 if struct stat lacks a status-change member +# of type struct timespec, so code should use st_ctime instead; +# but if the status-change member name is st_ctimespec, +# use -Dst_ctim=st_ctimespec instead (default is guessed)+ # -DHAVE_STRUCT_TIMESPEC=0 if your system lacks struct timespec+ # -DHAVE_SYMLINK=0 if your system lacks the symlink function # -DHAVE_SYS_STAT_H=0 if does not work* @@ -322,13 +303,13 @@ LDLIBS= # variable, 0 otherwise (default is guessed) # -DHAVE_SYS_SINGLE_THREADED_H=0 if works, # 0 otherwise (default is guessed) -# -DTHREAD_RWLOCK to use read-write locks intead of mutexes. -# This can improve paralellism and thus save real time +# -DTHREAD_RWLOCK to use read-write locks instead of mutexes. +# This can improve parallelism and thus save real time # if many threads call tzcode functions simultaneously. # It also costs CPU time and thus energy. # -DTHREAD_TM_MULTI to have gmtime, localtime, and offtime # return different struct tm * addresses in different threads. -# This supports unportable programs that call +# This supports nonportable programs that call # gmtime/localtime/offtime when they should call # gmtime_r/localtime_r/offtime_r to avoid races. # Because the corresponding storage is freed on thread exit, @@ -347,10 +328,13 @@ LDLIBS= # -DTZ_DOMAIN=\"foo\" to use "foo" for gettext domain name; default is "tz" # -DTZ_DOMAINDIR=\"/path\" to use "/path" for gettext directory; # the default is system-supplied, typically "/usr/lib/locale" +# -DTZ_RUNTIME_LEAPS=0 to disable runtime support for leap seconds. +# This conforms to POSIX, shrinks tzcode's attack surface, +# and is more efficient. However, it fails to support Internet +# RFC 9636's leap seconds. # -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified -# DST transitions for proleptic format TZ strings lacking them, -# in the usual case where POSIXRULES is '-'. If not specified, -# TZDEFRULESTRING defaults to US rules for future DST transitions. +# DST transitions for proleptic format TZ strings lacking them. +# If not specified, it defaults to US rules for future DST transitions. # This mishandles some past timestamps, as US DST rules have changed. # It also mishandles settings like TZ='EET-2EEST' for eastern Europe, # as Europe and US DST rules differ. @@ -400,12 +384,13 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 \ -Wold-style-definition -Woverlength-strings -Wpointer-arith \ -Wshadow -Wshift-overflow=2 -Wstrict-overflow \ -Wstrict-prototypes -Wstringop-overflow=4 \ - -Wstringop-truncation -Wsuggest-attribute=cold \ + -Wsuggest-attribute=cold \ -Wsuggest-attribute=const -Wsuggest-attribute=format \ -Wsuggest-attribute=malloc \ -Wsuggest-attribute=noreturn -Wsuggest-attribute=pure \ -Wtrampolines -Wundef -Wunused-macros -Wuse-after-free=3 \ -Wvariadic-macros -Wvla -Wwrite-strings \ + -Wzero-as-null-pointer-constant \ -Wno-format-nonliteral -Wno-sign-compare -Wno-type-limits # # If your system has a "GMT offset" field in its "struct tm"s @@ -723,7 +708,6 @@ install: all $(DATA) $(REDO) $(MANS) '$(DESTDIR)$(MANDIR)/man3' '$(DESTDIR)$(MANDIR)/man5' \ '$(DESTDIR)$(MANDIR)/man8' $(ZIC_INSTALL) -l $(LOCALTIME) \ - -p $(POSIXRULES) \ -t '$(DESTDIR)$(TZDEFAULT)' cp -f $(TABDATA) '$(DESTDIR)$(TZDIR)/.' cp tzselect '$(DESTDIR)$(BINDIR)/.' diff --git a/NEWS b/NEWS index eef59a2b095b..acd22280cb3a 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,100 @@ News for the tz database +Release 2026a - 2026-03-01 22:59:49 -0800 + + Briefly: + Moldova has used EU transition times since 2022. + The "right" TZif files are no longer installed by default. + -DTZ_RUNTIME_LEAPS=0 disables runtime support for leap seconds. + TZif files are no longer limited to 50 bytes of abbreviations. + zic is no longer limited to 50 leap seconds. + Several integer overflow bugs have been fixed. + + Changes to past and future timestamps + + Since 2022 Moldova has observed EU transition times, that is, it + has sprung forward at 03:00, not 02:00, and has fallen back at + 04:00, not 03:00. (Thanks to Heitor David Pinto.) + + Changes to data + + Remove Europe/Chisinau from zonenow.tab, as it now agrees with + Europe/Athens for future timestamps. + + Changes to build procedure + + The Makefile no longer by default installs an alternate set + of TZif files for system clocks that count leap seconds. + Install with 'make REDO=posix_right' to get the old default, + which is rarely used in major downstream distributions. + If your system clock counts leap seconds (contrary to POSIX), + it is better to install with 'make REDO=right_only'. + This change does not affect the leapseconds file, which is still + installed as before. + + The Makefile's POSIXRULES option, which was declared obsolete in + release 2019b, has been removed. The Makefile's build procedure + thus no longer optionally installs the obsolete posixrules file. + + Changes to code + + Compiling with the new option -DTZ_RUNTIME_LEAPS=0 disables + runtime support for leap seconds. Although this conforms to + POSIX, shrinks tzcode's attack surface, and is more efficient, + it fails to support Internet RFC 9636's leap seconds. + + zic now can generate, and localtime.c can now use, TZif files that + hold up to 256 bytes of abbreviations, counting trailing NULs. + The previous limit was 50 bytes, and some tzdata TZif files were + already consuming 40 bytes. zic -v warns if it generates a file + that exceeds the old 50-byte limit. + + zic -L can now generate TZif files with more than 50 leap seconds. + This helps test TZif readers not limited to 50 leap seconds, as + tzcode's localtime.c is; it has little immediate need for + practical timekeeping as there have been only 27 leap seconds and + possibly there will be no more, due to planned changes to UTC. + zic -v warns if its output exceeds the old 50-second limit. + + localtime.c no longer accesses the posixrules file generated by + zic -p. Hence for obsolete and nonconforming settings like + TZ="AST4ADT" it now typically falls back on US DST rules, rather + than attempting to override this fallback with the contents of the + posixrules file. This removes library support that was declared + obsolete in release 2019b, and fixes some undefined behavior. + (Undefined behavior reported by GitHub user Naveed8951.) + + The posix2time, posix2time_z, time2posix, and time2posix_z + functions now set errno=EOVERFLOW and return ((time_t) -1) if the + result is not representable. Formerly they had undefined behavior + that could in practice result in crashing, looping indefinitely, + or returning an incorrect result. As before, these functions are + defined only when localtime.c is compiled with the -DSTD_INSPIRED + option. + + Some other undefined behavior, triggered by TZif files containing + outlandish but conforming UT offsets or leap second corrections, + has also been fixed. (Some of these bugs reported by Naveed8951.) + + localtime.c no longer rejects TZif files that exactly fit in its + internal structures, fixing off-by-one typos introduced in 2014g. + + zic no longer generates a no-op transition when + simultaneous Rule and Zone changes cancel each other out. + This occurs in tzdata only in Asia/Tbilisi on 1997-03-30. + (Thanks to Renchunhui for a test case showing the bug.) + + zic no longer assumes you can fflush a read-only stream. + (Problem reported by Christos Zoulas.) + + zic no longer generates UT offsets equal to -2**31 and localtime.c + no longer accepts them, as they can cause trouble in both + localtime.c and its callers. RFC 9636 prohibits such offsets. + + zic -p now warns that the -p option is obsolete and likely + ineffective. + + Release 2025c - 2025-12-10 14:42:37 -0800 Briefly: @@ -69,6 +164,8 @@ Release 2025c - 2025-12-10 14:42:37 -0800 The new CFLAGS options -DHAVE_STRUCT_STAT_ST_CTIM=0 and -DHAVE_STRUCT_TIMESPEC=0 port to non-POSIX.1-2008 platforms that lack st_ctim and struct timespec, respectively. + On these platforms, the code falls back on st_ctime to + implement -DTZ_CHANGE_INTERVAL=N. tzset etc. now treat ' ' like '_' in time zone abbreviations, just as they treat other invalid bytes. This continues the @@ -89,7 +186,7 @@ Release 2025c - 2025-12-10 14:42:37 -0800 The new CFLAGS option -TTHREAD_TM_MULTI causes localtime to return a pointer to thread-specific memory, as FreeBSD does, instead of - to the same memory in all threads. This supports unportable + to the same memory in all threads. This supports nonportable programs that incorrectly use localtime instead of localtime_r. This option affects gmtime and offtime similarly to localtime. Because the corresponding storage is freed on thread exit, this diff --git a/date.1 b/date.1 index 5f334358eca6..8f2c212b2cf3 100644 --- a/date.1 +++ b/date.1 @@ -77,6 +77,6 @@ hexadecimal (leading 0x), preceded by an optional sign. .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/Etc/UTC for UTC leap seconds +/usr/share/zoneinfo/Etc/UTC for UTC leap seconds, if supported .SH SEE ALSO .BR strftime (3). diff --git a/date.1.txt b/date.1.txt index 1dbcb18d93a5..a2e52c035214 100644 --- a/date.1.txt +++ b/date.1.txt @@ -37,7 +37,7 @@ FILES /etc/localtime local timezone file /usr/lib/locale/L/LC_TIME description of time locale L /usr/share/zoneinfo timezone directory - /usr/share/zoneinfo/Etc/UTC for UTC leap seconds + /usr/share/zoneinfo/Etc/UTC for UTC leap seconds, if supported SEE ALSO strftime(3). diff --git a/localtime.c b/localtime.c index b80a34138370..0091b81eba3d 100644 --- a/localtime.c +++ b/localtime.c @@ -52,6 +52,10 @@ struct stat { char st_ctime, st_dev, st_ino; }; # define THREAD_TM_MULTI 0 #endif +#ifndef USE_TIMEX_T +# define USE_TIMEX_T false +#endif + #if THREAD_SAFE # include @@ -88,6 +92,10 @@ extern int __isthreaded; # endif # endif # endif +#endif + +#if !defined TM_GMTOFF || !USE_TIMEX_T +# if THREAD_SAFE /* True if the current process might be multi-threaded, false if it is definitely single-threaded. @@ -98,24 +106,25 @@ extern int __isthreaded; static bool is_threaded(void) { -# if THREAD_PREFER_SINGLE && HAVE___ISTHREADED +# if THREAD_PREFER_SINGLE && HAVE___ISTHREADED return !!__isthreaded; -# elif THREAD_PREFER_SINGLE && HAVE_SYS_SINGLE_THREADED_H +# elif THREAD_PREFER_SINGLE && HAVE_SYS_SINGLE_THREADED_H return !__libc_single_threaded; -# else +# else return true; -# endif +# endif } -# if THREAD_RWLOCK +# if THREAD_RWLOCK static pthread_rwlock_t locallock = PTHREAD_RWLOCK_INITIALIZER; static int dolock(void) { return pthread_rwlock_rdlock(&locallock); } static void dounlock(void) { pthread_rwlock_unlock(&locallock); } -# else +# else static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; static int dolock(void) { return pthread_mutex_lock(&locallock); } static void dounlock(void) { pthread_mutex_unlock(&locallock); } -# endif +# endif + /* Get a lock. Return 0 on success, a positive errno value on failure, negative if known to be single-threaded so no lock is needed. */ static int @@ -131,25 +140,12 @@ unlock(bool threaded) if (threaded) dounlock(); } -#else +# else static int lock(void) { return -1; } static void unlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) { } +# endif #endif -/* If THREADED, upgrade a read lock to a write lock. - Return 0 on success, a positive errno value otherwise. */ -static int -rd2wrlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) -{ -#if THREAD_RWLOCK - if (threaded) { - dounlock(); - return pthread_rwlock_wrlock(&locallock); - } -#endif - return 0; -} - #if THREAD_SAFE typedef pthread_once_t once_t; # define ONCE_INIT PTHREAD_ONCE_INIT @@ -187,36 +183,6 @@ tm_multi_key_init(void) #endif -/* Return TMP, or a thread-specific struct tm * selected by WHICH. */ -static struct tm * -tm_multi(struct tm *tmp, ATTRIBUTE_MAYBE_UNUSED enum tm_multi which) -{ -#if THREAD_SAFE && THREAD_TM_MULTI - /* It is OK to check is_threaded() separately here; even if it - returns a different value in other places in the caller, - this function's behavior is still valid. */ - if (is_threaded()) { - /* Try to get a thread-specific struct tm *. - Fall back on TMP if this fails. */ - static pthread_once_t tm_multi_once = PTHREAD_ONCE_INIT; - pthread_once(&tm_multi_once, tm_multi_key_init); - if (!tm_multi_key_err) { - struct tm *p = pthread_getspecific(tm_multi_key); - if (!p) { - p = malloc(N_TM_MULTI * sizeof *p); - if (p && pthread_setspecific(tm_multi_key, p) != 0) { - free(p); - p = NULL; - } - } - if (p) - return &p[which]; - } - } -#endif - return tmp; -} - /* Unless intptr_t is missing, pacify gcc -Wcast-qual on char const * exprs. Use this carefully, as the casts disable type checking. This is a macro so that it can be used in static initializers. */ @@ -293,9 +259,6 @@ typedef time_t monotime_t; to a static function that returns the redefined time_t. It also tells us to define only data and code needed to support the offtime or mktime variant. */ -#ifndef USE_TIMEX_T -# define USE_TIMEX_T false -#endif #if USE_TIMEX_T # undef TIME_T_MIN # undef TIME_T_MAX @@ -446,7 +409,7 @@ static char const *utc = etc_utc + sizeof "Etc/" - 1; #endif /* -** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** The DST rules to use if TZ has no rules. ** Default to US rules as of 2017-05-07. ** POSIX does not specify the default DST rules; ** for historical reasons, US rules are a common default. @@ -496,8 +459,19 @@ typedef ptrdiff_t desigidx_type; # error "TZNAME_MAXIMUM too large" #endif +/* A type that can represent any 32-bit two's complement integer, + i.e., any integer in the range -2**31 .. 2**31 - 1. + Ordinarily this is int_fast32_t, but on non-C23 hosts + that are not two's complement it is int_fast64_t. */ +#if INT_FAST32_MIN < -TWO_31_MINUS_1 +typedef int_fast32_t int_fast32_2s; +#else +typedef int_fast64_t int_fast32_2s; +#endif + struct ttinfo { /* time type information */ - int_least32_t tt_utoff; /* UT offset in seconds */ + int_least32_t tt_utoff; /* UT offset in seconds; in the range + -2**31 + 1 .. 2**31 - 1 */ desigidx_type tt_desigidx; /* abbreviation list index */ bool tt_isdst; /* used to set tm_isdst */ bool tt_ttisstd; /* transition is std time */ @@ -505,8 +479,8 @@ struct ttinfo { /* time type information */ }; struct lsinfo { /* leap second information */ - time_t ls_trans; /* transition time */ - int_fast32_t ls_corr; /* correction to apply */ + time_t ls_trans; /* transition time (positive) */ + int_fast32_2s ls_corr; /* correction to apply */ }; /* This abbreviation means local time is unspecified. */ @@ -524,7 +498,9 @@ enum { CHARS_EXTRA = max(sizeof UNSPEC, 2) - 1 }; are put on the stack and stacks are relatively small on some platforms. See tzfile.h for more about the sizes. */ struct state { +#if TZ_RUNTIME_LEAPS int leapcnt; +#endif int timecnt; int typecnt; int charcnt; @@ -535,9 +511,48 @@ struct state { struct ttinfo ttis[TZ_MAX_TYPES]; char chars[max(max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"), 2 * (TZNAME_MAXIMUM + 1))]; +#if TZ_RUNTIME_LEAPS struct lsinfo lsis[TZ_MAX_LEAPS]; +#endif }; +static int +leapcount(ATTRIBUTE_MAYBE_UNUSED struct state const *sp) +{ +#if TZ_RUNTIME_LEAPS + return sp->leapcnt; +#else + return 0; +#endif +} +static void +set_leapcount(ATTRIBUTE_MAYBE_UNUSED struct state *sp, + ATTRIBUTE_MAYBE_UNUSED int leapcnt) +{ +#if TZ_RUNTIME_LEAPS + sp->leapcnt = leapcnt; +#endif +} +static struct lsinfo +lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, + ATTRIBUTE_MAYBE_UNUSED int i) +{ +#if TZ_RUNTIME_LEAPS + return sp->lsis[i]; +#else + unreachable(); +#endif +} +static void +set_lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state *sp, + ATTRIBUTE_MAYBE_UNUSED int i, + ATTRIBUTE_MAYBE_UNUSED struct lsinfo lsinfo) +{ +#if TZ_RUNTIME_LEAPS + sp->lsis[i] = lsinfo; +#endif +} + enum r_type { JULIAN_DAY, /* Jn = Julian day */ DAY_OF_YEAR, /* n = day of year */ @@ -555,8 +570,8 @@ struct rule { 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 int_fast32_t leapcorr(struct state const *, time_t); +static bool increment_overflow_time(time_t *, int_fast32_2s); +static int_fast32_2s leapcorr(struct state const *, time_t); static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, struct tm *); static bool tzparse(char const *, struct state *, struct state const *); @@ -638,15 +653,14 @@ ttunspecified(struct state const *sp, int i) return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; } -static int_fast32_t +static int_fast32_2s detzcode(const char *const codep) { - register int_fast32_t result; register int i; - int_fast32_t one = 1; - int_fast32_t halfmaxval = one << (32 - 2); - int_fast32_t maxval = halfmaxval - 1 + halfmaxval; - int_fast32_t minval = -1 - maxval; + int_fast32_2s + maxval = TWO_31_MINUS_1, + minval = -1 - maxval, + result; result = codep[0] & 0x7f; for (i = 1; i < 4; ++i) @@ -654,8 +668,7 @@ detzcode(const char *const codep) if (codep[0] & 0x80) { /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; + This cannot overflow, as int_fast32_2s is wide enough. */ result += minval; } return result; @@ -1033,24 +1046,24 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, char version = up->tzhead.tzh_version[0]; bool skip_datablock = stored == 4 && version; int_fast32_t datablock_size; - int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); - int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); - int_fast64_t prevtr = -1; - int_fast32_t prevcorr; - int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); - int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); - int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); - int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); + int_fast32_2s + ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt), + ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt), + leapcnt = detzcode(up->tzhead.tzh_leapcnt), + timecnt = detzcode(up->tzhead.tzh_timecnt), + typecnt = detzcode(up->tzhead.tzh_typecnt), + charcnt = detzcode(up->tzhead.tzh_charcnt); char const *p = up->buf + tzheadsize; /* Although tzfile(5) currently requires typecnt to be nonzero, support future formats that may allow zero typecnt in files that have a TZ string and no transitions. */ - if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS - && 0 <= typecnt && typecnt < TZ_MAX_TYPES - && 0 <= timecnt && timecnt < TZ_MAX_TIMES - && 0 <= charcnt && charcnt < TZ_MAX_CHARS - && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES - && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) + if (! (0 <= leapcnt + && leapcnt <= (TZ_RUNTIME_LEAPS ? TZ_MAX_LEAPS : 0) + && 0 <= typecnt && typecnt <= TZ_MAX_TYPES + && 0 <= timecnt && timecnt <= TZ_MAX_TIMES + && 0 <= charcnt && charcnt <= TZ_MAX_CHARS + && 0 <= ttisstdcnt && ttisstdcnt <= TZ_MAX_TYPES + && 0 <= ttisutcnt && ttisutcnt <= TZ_MAX_TYPES)) return EINVAL; datablock_size = (timecnt * stored /* ats */ @@ -1064,12 +1077,13 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, return EINVAL; if (skip_datablock) p += datablock_size; + else if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisutcnt == typecnt || ttisutcnt == 0))) + return EINVAL; else { - if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) - && (ttisutcnt == typecnt || ttisutcnt == 0))) - return EINVAL; - - sp->leapcnt = leapcnt; + int_fast64_t prevtr = -1; + int_fast32_2s prevcorr; + set_leapcount(sp, leapcnt); sp->timecnt = timecnt; sp->typecnt = typecnt; sp->charcnt = charcnt; @@ -1109,9 +1123,16 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, for (i = 0; i < sp->typecnt; ++i) { register struct ttinfo * ttisp; unsigned char isdst, desigidx; + int_fast32_2s utoff = detzcode(p); + + /* Reject a UT offset equal to -2**31, as it might + cause trouble both in this file and in callers. + Also, it violates RFC 9636 section 3.2. */ + if (utoff < -TWO_31_MINUS_1) + return EINVAL; ttisp = &sp->ttis[i]; - ttisp->tt_utoff = detzcode(p); + ttisp->tt_utoff = utoff; p += 4; isdst = *p++; if (! (isdst < 2)) @@ -1130,9 +1151,9 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, /* Read leap seconds, discarding those out of time_t range. */ leapcnt = 0; - for (i = 0; i < sp->leapcnt; ++i) { + for (i = 0; i < leapcount(sp); i++) { int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); - int_fast32_t corr = detzcode(p + stored); + int_fast32_2s corr = detzcode(p + stored); p += stored + 4; /* Leap seconds cannot occur before the Epoch, @@ -1155,12 +1176,14 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, prevcorr = corr; if (tr <= TIME_T_MAX) { - sp->lsis[leapcnt].ls_trans = tr; - sp->lsis[leapcnt].ls_corr = corr; + struct lsinfo ls; + ls.ls_trans = tr; + ls.ls_corr = corr; + set_lsinfo(sp, leapcnt, ls); leapcnt++; } } - sp->leapcnt = leapcnt; + set_leapcount(sp, leapcnt); for (i = 0; i < sp->typecnt; ++i) { register struct ttinfo * ttisp; @@ -1204,8 +1227,8 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags, if (tzparse(&up->buf[1], ts, sp)) { /* Attempt to reuse existing abbreviations. - Without this, America/Anchorage would be right on - the edge after 2037 when TZ_MAX_CHARS is 50, as + Without this, America/Anchorage would + consume 50 bytes for abbreviations, as sp->charcnt equals 40 (for LMT AST AWT APT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for AKST AKDT). Reusing means sp->charcnt can @@ -1600,7 +1623,6 @@ tzparse(const char *name, struct state *sp, struct state const *basep) int_fast32_t stdoffset; int_fast32_t dstoffset; register char * cp; - register bool load_ok; ptrdiff_t stdlen, dstlen, charcnt; time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; @@ -1626,18 +1648,22 @@ tzparse(const char *name, struct state *sp, struct state const *basep) if (basep) { if (0 < basep->timecnt) atlo = basep->ats[basep->timecnt - 1]; - load_ok = false; - sp->leapcnt = basep->leapcnt; - memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); - } else { - load_ok = tzload(TZDEFRULES, sp, 0) == 0; - if (!load_ok) - sp->leapcnt = 0; /* So, we're off a little. */ - } - if (0 < sp->leapcnt) - leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; + set_leapcount(sp, leapcount(basep)); + if (0 < leapcount(sp)) { + int i; + for (i = 0; i < leapcount(sp); i++) + set_lsinfo(sp, i, lsinfo(basep, i)); + leaplo = lsinfo(sp, leapcount(sp) - 1).ls_trans; + } + } else + set_leapcount(sp, 0); /* So, we're off a little. */ sp->goback = sp->goahead = false; if (*name != '\0') { + struct rule start, end; + int year, yearbeg, yearlim, timecnt; + time_t janfirst; + int_fast32_t janoffset = 0; + if (*name == '<') { dstname = ++name; name = getqzname(name, '>'); @@ -1658,194 +1684,102 @@ tzparse(const char *name, struct state *sp, struct state const *basep) if (name == NULL) return false; } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && !load_ok) + + if (*name == '\0') name = TZDEFRULESTRING; - if (*name == ',' || *name == ';') { - struct rule start; - struct rule end; - register int year; - register int timecnt; - time_t janfirst; - int_fast32_t janoffset = 0; - int yearbeg, yearlim; - - ++name; - if ((name = getrule(name, &start)) == NULL) - return false; - if (*name++ != ',') - return false; - if ((name = getrule(name, &end)) == NULL) - return false; - if (*name != '\0') - return false; - sp->typecnt = 2; /* standard time and DST */ - /* - ** Two transitions per year, from EPOCH_YEAR forward. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - timecnt = 0; - janfirst = 0; - yearbeg = EPOCH_YEAR; - - do { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; - time_t janfirst1 = janfirst; - yearbeg--; - if (increment_overflow_time(&janfirst1, -yearsecs)) { - janoffset = -yearsecs; - break; - } - janfirst = janfirst1; - } while (atlo < janfirst - && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); - - while (true) { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg)] * SECSPERDAY; - int yearbeg1 = yearbeg; - time_t janfirst1 = janfirst; - if (increment_overflow_time(&janfirst1, yearsecs) - || increment_overflow(&yearbeg1, 1) - || atlo <= janfirst1) - break; - yearbeg = yearbeg1; - janfirst = janfirst1; - } + if (! (*name == ',' || *name == ';')) + return false; - yearlim = yearbeg; - if (increment_overflow(&yearlim, years_of_observations)) - yearlim = INT_MAX; - for (year = yearbeg; year < yearlim; year++) { - int_fast32_t - starttime = transtime(year, &start, stdoffset), - endtime = transtime(year, &end, dstoffset); - int_fast32_t - yearsecs = (year_lengths[isleap(year)] - * SECSPERDAY); - bool reversed = endtime < starttime; - if (reversed) { - int_fast32_t swap = starttime; - starttime = endtime; - endtime = swap; - } - if (reversed - || (starttime < endtime - && endtime - starttime < yearsecs)) { - if (TZ_MAX_TIMES - 2 < timecnt) - break; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + starttime) - && atlo <= sp->ats[timecnt]) - sp->types[timecnt++] = !reversed; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + endtime) - && atlo <= sp->ats[timecnt]) { - sp->types[timecnt++] = reversed; - } - } - if (endtime < leaplo) { - yearlim = year; - if (increment_overflow(&yearlim, - years_of_observations)) - yearlim = INT_MAX; - } - if (increment_overflow_time - (&janfirst, janoffset + yearsecs)) - break; - janoffset = 0; - } - sp->timecnt = timecnt; - if (! timecnt) { - sp->ttis[0] = sp->ttis[1]; - sp->typecnt = 1; /* Perpetual DST. */ - } else if (years_of_observations <= year - yearbeg) - sp->goback = sp->goahead = true; - } else { - register int_fast32_t theirstdoffset; - register int_fast32_t theirdstoffset; - register int_fast32_t theiroffset; - register bool isdst; - register int i; - register int j; - - if (*name != '\0') - return false; - /* - ** Initial values of theirstdoffset and theirdstoffset. - */ - theirstdoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (!sp->ttis[j].tt_isdst) { - theirstdoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - theirdstoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (sp->ttis[j].tt_isdst) { - theirdstoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - /* - ** Initially we're assumed to be in standard time. - */ - isdst = false; - /* - ** Now juggle transition times and types - ** tracking offsets as you do. - */ - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - sp->types[i] = sp->ttis[j].tt_isdst; - if (sp->ttis[j].tt_ttisut) { - /* No adjustment to transition time */ - } else { - /* - ** If daylight saving time is in - ** effect, and the transition time was - ** not specified as standard time, add - ** the daylight saving time offset to - ** the transition time; otherwise, add - ** the standard time offset to the - ** transition time. - */ - /* - ** Transitions from DST to DDST - ** will effectively disappear since - ** proleptic TZ strings have only one - ** DST offset. - */ - if (isdst && !sp->ttis[j].tt_ttisstd) { - sp->ats[i] += dstoffset - - theirdstoffset; - } else { - sp->ats[i] += stdoffset - - theirstdoffset; - } - } - theiroffset = -sp->ttis[j].tt_utoff; - if (sp->ttis[j].tt_isdst) - theirdstoffset = theiroffset; - else theirstdoffset = theiroffset; - } - /* - ** Finally, fill in ttis. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - sp->typecnt = 2; + name = getrule(name + 1, &start); + if (!name) + return false; + if (*name++ != ',') + return false; + name = getrule(name, &end); + if (!name || *name) + return false; + sp->typecnt = 2; /* standard time and DST */ + /* + ** Two transitions per year, from EPOCH_YEAR forward. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + timecnt = 0; + janfirst = 0; + yearbeg = EPOCH_YEAR; + + do { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + time_t janfirst1 = janfirst; + yearbeg--; + if (increment_overflow_time(&janfirst1, -yearsecs)) { + janoffset = -yearsecs; + break; + } + janfirst = janfirst1; + } while (atlo < janfirst + && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + while (true) { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg)] * SECSPERDAY; + int yearbeg1 = yearbeg; + time_t janfirst1 = janfirst; + if (increment_overflow_time(&janfirst1, yearsecs) + || increment_overflow(&yearbeg1, 1) + || atlo <= janfirst1) + break; + yearbeg = yearbeg1; + janfirst = janfirst1; + } + + yearlim = yearbeg; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + for (year = yearbeg; year < yearlim; year++) { + int_fast32_t + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset), + yearsecs = year_lengths[isleap(year)] * SECSPERDAY; + bool reversed = endtime < starttime; + if (reversed) { + int_fast32_t swap = starttime; + starttime = endtime; + endtime = swap; + } + if (reversed + || (starttime < endtime + && endtime - starttime < yearsecs)) { + if (TZ_MAX_TIMES - 2 < timecnt) + break; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time(&sp->ats[timecnt], + janoffset + starttime) + && atlo <= sp->ats[timecnt]) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time(&sp->ats[timecnt], + janoffset + endtime) + && atlo <= sp->ats[timecnt]) { + sp->types[timecnt++] = reversed; + } + } + if (endtime < leaplo) { + yearlim = year; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + } + if (increment_overflow_time(&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; } + sp->timecnt = timecnt; + if (! timecnt) { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } else if (years_of_observations <= year - yearbeg) + sp->goback = sp->goahead = true; } else { dstlen = 0; sp->typecnt = 1; /* only standard time */ @@ -1866,7 +1800,7 @@ tzparse(const char *name, struct state *sp, struct state const *basep) static void gmtload(struct state *const sp) { - if (tzload(etc_utc, sp, TZLOAD_TZSTRING) != 0) + if (!TZ_RUNTIME_LEAPS || tzload(etc_utc, sp, TZLOAD_TZSTRING) != 0) tzparse("UTC0", sp, NULL); } @@ -1898,7 +1832,7 @@ zoneinit(struct state *sp, char const *name, char tzloadflags) /* ** User wants it fast rather than right. */ - sp->leapcnt = 0; /* so, we're off a little */ + set_leapcount(sp, 0); /* so, we're off a little */ sp->timecnt = 0; sp->typecnt = 0; sp->charcnt = 0; @@ -1917,6 +1851,20 @@ zoneinit(struct state *sp, char const *name, char tzloadflags) } } +/* If THREADED, upgrade a read lock to a write lock. + Return 0 on success, a positive errno value otherwise. */ +static int +rd2wrlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) +{ +# if THREAD_RWLOCK + if (threaded) { + dounlock(); + return pthread_rwlock_wrlock(&locallock); + } +# endif + return 0; +} + /* Like tzset(), but in a critical section. If THREADED && THREAD_RWLOCK the caller has a read lock, and this function might upgrade it to a write lock. @@ -2191,7 +2139,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, ** To get (wrong) behavior that's compatible with System V Release 2.0 ** you'd replace the statement below with ** t += ttisp->tt_utoff; - ** timesub(&t, 0L, sp, tmp); + ** timesub(&t, 0, sp, tmp); */ result = timesub(&t, ttisp->tt_utoff, sp, tmp); if (result) { @@ -2208,6 +2156,36 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, #if !USE_TIMEX_T +/* Return TMP, or a thread-specific struct tm * selected by WHICH. */ +static struct tm * +tm_multi(struct tm *tmp, ATTRIBUTE_MAYBE_UNUSED enum tm_multi which) +{ +# if THREAD_SAFE && THREAD_TM_MULTI + /* It is OK to check is_threaded() separately here; even if it + returns a different value in other places in the caller, + this function's behavior is still valid. */ + if (is_threaded()) { + /* Try to get a thread-specific struct tm *. + Fall back on TMP if this fails. */ + static pthread_once_t tm_multi_once = PTHREAD_ONCE_INIT; + pthread_once(&tm_multi_once, tm_multi_key_init); + if (!tm_multi_key_err) { + struct tm *p = pthread_getspecific(tm_multi_key); + if (!p) { + p = malloc(N_TM_MULTI * sizeof *p); + if (p && pthread_setspecific(tm_multi_key, p) != 0) { + free(p); + p = NULL; + } + } + if (p) + return &p[which]; + } + } +# endif + return tmp; +} + # if NETBSD_INSPIRED struct tm * localtime_rz(struct state *restrict sp, time_t const *restrict timep, @@ -2341,10 +2319,9 @@ static struct tm * timesub(const time_t *timep, int_fast32_t offset, const struct state *sp, struct tm *tmp) { - register const struct lsinfo * lp; register time_t tdays; register const int * ip; - register int_fast32_t corr; + int_fast32_2s corr; register int i; int_fast32_t idays, rem, dayoff, dayrem; time_t y; @@ -2355,13 +2332,13 @@ timesub(const time_t *timep, int_fast32_t offset, time_t secs_since_posleap = SECSPERMIN; corr = 0; - i = (sp == NULL) ? 0 : sp->leapcnt; + i = sp ? leapcount(sp) : 0; while (--i >= 0) { - lp = &sp->lsis[i]; - if (*timep >= lp->ls_trans) { - corr = lp->ls_corr; - if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) - secs_since_posleap = *timep - lp->ls_trans; + struct lsinfo ls = lsinfo(sp, i); + if (ls.ls_trans <= *timep) { + corr = ls.ls_corr; + if ((i == 0 ? 0 : lsinfo(sp, i - 1).ls_corr) < corr) + secs_since_posleap = *timep - ls.ls_trans; break; } } @@ -2490,6 +2467,19 @@ increment_overflow(int *ip, int j) #endif } +static bool +increment_overflow_64(int *ip, int_fast64_t j) +{ +#ifdef ckd_add + return ckd_add(ip, *ip, j); +#else + if (j < 0 ? *ip < INT_MIN - j : INT_MAX - j < *ip) + return true; + *ip += j; + return false; +#endif +} + static bool increment_overflow_time_iinntt(time_t *tp, iinntt j) { @@ -2506,7 +2496,22 @@ increment_overflow_time_iinntt(time_t *tp, iinntt j) } static bool -increment_overflow_time(time_t *tp, int_fast32_t j) +increment_overflow_time_64(time_t *tp, int_fast64_t j) +{ +#ifdef ckd_add + return ckd_add(tp, *tp, j); +#else + if (j < 0 + ? (TYPE_SIGNED(time_t) ? *tp < TIME_T_MIN - j : *tp <= -1 - j) + : TIME_T_MAX - j < *tp) + return true; + *tp += j; + return false; +#endif +} + +static bool +increment_overflow_time(time_t *tp, int_fast32_2s j) { #ifdef ckd_add return ckd_add(tp, *tp, j); @@ -2525,6 +2530,15 @@ increment_overflow_time(time_t *tp, int_fast32_t j) #endif } +/* Return A - B, where both are in the range -2**31 + 1 .. 2**31 - 1. + The result cannot overflow. */ +static int_fast64_t +utoff_diff (int_fast32_t a, int_fast32_t b) +{ + int_fast64_t aa = a; + return aa - b; +} + static int tmcomp(register const struct tm *const atmp, register const struct tm *const btmp) @@ -2721,8 +2735,18 @@ time2sub(struct tm *const tmp, 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)) { + int_fast64_t offdiff; + bool v; +# ifdef ckd_sub + v = ckd_sub(&offdiff, mytm.TM_GMTOFF, yourtm.TM_GMTOFF); +# else + /* A ckd_sub approximation that is good enough here. */ + v = !(-TWO_31_MINUS_1 <= yourtm.TM_GMTOFF + && yourtm.TM_GMTOFF <= TWO_31_MINUS_1); + if (!v) + offdiff = utoff_diff(mytm.TM_GMTOFF, yourtm.TM_GMTOFF); +# endif + if (!v && !increment_overflow_time_64(&altt, offdiff)) { struct tm alttm; if (funcp(sp, &altt, offset, &alttm) && alttm.tm_isdst == mytm.tm_isdst @@ -2752,8 +2776,12 @@ time2sub(struct tm *const tmp, continue; if (ttunspecified(sp, j)) continue; - newt = (t + sp->ttis[j].tt_utoff - - sp->ttis[i].tt_utoff); + newt = t; + if (increment_overflow_time_64 + (&newt, + utoff_diff(sp->ttis[j].tt_utoff, + sp->ttis[i].tt_utoff))) + continue; if (! funcp(sp, &newt, offset, &mytm)) continue; if (tmcomp(&mytm, &yourtm) != 0) @@ -2852,17 +2880,20 @@ time1(struct tm *const tmp, continue; for (otherind = 0; otherind < nseen; ++otherind) { otheri = types[otherind]; - if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) - continue; - tmp->tm_sec += (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - tmp->tm_sec -= (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; + if (sp->ttis[otheri].tt_isdst != tmp->tm_isdst) { + int sec = tmp->tm_sec; + if (!increment_overflow_64 + (&tmp->tm_sec, + utoff_diff(sp->ttis[otheri].tt_utoff, + sp->ttis[samei].tt_utoff))) { + tmp->tm_isdst = !tmp->tm_isdst; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + tmp->tm_isdst = !tmp->tm_isdst; + } + tmp->tm_sec = sec; + } } } return WRONG; @@ -2958,17 +2989,16 @@ timegm(struct tm *tmp) } #endif -static int_fast32_t +static int_fast32_2s leapcorr(struct state const *sp, time_t t) { - register struct lsinfo const * lp; register int i; - i = sp->leapcnt; + i = leapcount(sp); while (--i >= 0) { - lp = &sp->lsis[i]; - if (t >= lp->ls_trans) - return lp->ls_corr; + struct lsinfo ls = lsinfo(sp, i); + if (ls.ls_trans <= t) + return ls.ls_corr; } return 0; } @@ -2980,6 +3010,21 @@ leapcorr(struct state const *sp, time_t t) #if !USE_TIMEX_T # if STD_INSPIRED +static bool +decrement_overflow_time(time_t *tp, int_fast32_2s j) +{ +#ifdef ckd_sub + return ckd_sub(tp, *tp, j); +#else + if (! (j < 0 + ? *tp <= TIME_T_MAX + j + : (TYPE_SIGNED(time_t) ? TIME_T_MIN + j <= *tp : j <= *tp))) + return true; + *tp -= j; + return false; +#endif +} + /* NETBSD_INSPIRED_EXTERN functions are exported to callers if NETBSD_INSPIRED is defined, and are private otherwise. */ # if NETBSD_INSPIRED @@ -2999,7 +3044,13 @@ leapcorr(struct state const *sp, time_t t) NETBSD_INSPIRED_EXTERN time_t time2posix_z(struct state *sp, time_t t) { - return t - leapcorr(sp, t); + if (decrement_overflow_time(&t, leapcorr(sp, t))) { + /* Overflow near maximum time_t value with negative correction. + This can happen with unrealistic-but-valid TZif files. */ + errno = EOVERFLOW; + return -1; + } + return t; } time_t @@ -3022,30 +3073,27 @@ time2posix(time_t t) NETBSD_INSPIRED_EXTERN time_t posix2time_z(struct state *sp, time_t t) { - time_t x; - time_t y; - /* - ** 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(sp, t); - y = x - leapcorr(sp, x); - if (y < t) { - do { - x++; - y = x - leapcorr(sp, x); - } while (y < t); - x -= y != t; - } else if (y > t) { - do { - --x; - y = x - leapcorr(sp, x); - } while (y > t); - x += y != t; - } - return x; + int i; + for (i = leapcount(sp); 0 <= --i; ) { + struct lsinfo ls = lsinfo(sp, i); + time_t t_corr = t; + + if (increment_overflow_time(&t_corr, ls.ls_corr)) { + if (0 <= ls.ls_corr) { + /* Overflow near maximum time_t value with positive correction. + This can happen with ordinary TZif files with leap seconds. */ + errno = EOVERFLOW; + return -1; + } else { + /* A negative correction overflowed, so keep going. + This can happen with unrealistic-but-valid TZif files. */ + } + } else if (ls.ls_trans <= t_corr) + return (t_corr + - (ls.ls_trans == t_corr + && (i == 0 ? 0 : lsinfo(sp, i - 1).ls_corr) < ls.ls_corr)); + } + return t; } time_t @@ -3087,7 +3135,7 @@ posix2time(time_t t) time_t time(time_t *p) { - time_t r = sys_time(0); + time_t r = sys_time(NULL); if (r != (time_t) -1) { iinntt offset = EPOCH_LOCAL ? timezone : 0; if (offset < IINNTT_MIN + EPOCH_OFFSET diff --git a/newctime.3 b/newctime.3 index 6b85ec51be68..a8779e6a7c25 100644 --- a/newctime.3 +++ b/newctime.3 @@ -181,7 +181,7 @@ instead. .PP The .B ctime -function is equivalent to calliing +function is equivalent to calling .B localtime and then calling .B asctime @@ -311,13 +311,11 @@ and functions might (or might not) also behave this way. This is for compatibility with older platforms, as required by POSIX. .SH FILES -.ta \w'/usr/share/zoneinfo/posixrules\0\0'u +.ta \w'/usr/share/zoneinfo/GMT\0\0'u /etc/localtime local timezone file .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/posixrules default DST rules (obsolete) -.br /usr/share/zoneinfo/GMT for UTC leap seconds .PP If /usr/share/zoneinfo/GMT is absent, diff --git a/newctime.3.txt b/newctime.3.txt index 319640b84111..5ee242d00921 100644 --- a/newctime.3.txt +++ b/newctime.3.txt @@ -90,7 +90,7 @@ DESCRIPTION years. This function is deprecated starting in C23. Callers can use strftime instead. - The ctime function is equivalent to calliing localtime and then calling + The ctime function is equivalent to calling localtime and then calling asctime on the result. Like asctime, this function is deprecated starting in C23. Callers can use localtime and strftime instead. @@ -144,10 +144,9 @@ DESCRIPTION older platforms, as required by POSIX. FILES - /etc/localtime local timezone file - /usr/share/zoneinfo timezone directory - /usr/share/zoneinfo/posixrules default DST rules (obsolete) - /usr/share/zoneinfo/GMT for UTC leap seconds + /etc/localtime local timezone file + /usr/share/zoneinfo timezone directory + /usr/share/zoneinfo/GMT for UTC leap seconds If /usr/share/zoneinfo/GMT is absent, UTC leap seconds are loaded from /usr/share/zoneinfo/GMT0 if present. diff --git a/newtzset.3 b/newtzset.3 index 028cfd2563e3..77e76c9d6974 100644 --- a/newtzset.3 +++ b/newtzset.3 @@ -277,7 +277,7 @@ is a placeholder. .TP .B <\-03>3<\-02>,M3.5.0/\-2,M10.5.0/\-1 stands for time in western Greenland, 3 hours behind UT, where clocks -follow the EU rules of +follow the EU rule of springing forward on March's last Sunday at 01:00 UT (\-02:00 local time, i.e., 22:00 the previous day) and falling back on October's last Sunday at 01:00 UT (\-01:00 local time, i.e., 23:00 the previous day). @@ -290,28 +290,13 @@ If .I TZ specifies daylight saving time but does not specify a .IR rule , -and the optional -.BR tzfile (5)-format -file -.B posixrules -is present in the system time conversion information directory, the -rules in -.B posixrules -are used, with the -.B posixrules -standard and daylight saving time offsets from UT -replaced by those specified by the -.I offset -values in -.IR TZ . -However, the -.B posixrules -file is obsolete: if it is present it is only for backward compatibility, -and it does not work reliably. +the rule typically defaults to the current US daylight-saving rule, +although such a default is not guaranteed and +is incorrect for many locations outside the US. Therefore, if a .I TZ string directly specifies a timezone with daylight saving time, -it should specify the daylight saving rules explicitly. +it should specify the daylight saving rule explicitly. .PP For compatibility with System V Release 3.1, a semicolon .RB ( ; ) @@ -403,13 +388,11 @@ for any of the errors specified for the routines and .BR read (2). .SH FILES -.ta \w'/usr/share/zoneinfo/posixrules\0\0'u +.ta \w'/usr/share/zoneinfo/GMT\0\0'u /etc/localtime local timezone file .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/posixrules default DST rules (obsolete) -.br /usr/share/zoneinfo/GMT for UTC leap seconds .PP If /usr/share/zoneinfo/GMT is absent, diff --git a/newtzset.3.txt b/newtzset.3.txt index b0d929948bb8..21c4ae3958d7 100644 --- a/newtzset.3.txt +++ b/newtzset.3.txt @@ -158,22 +158,19 @@ DESCRIPTION <-03>3<-02>,M3.5.0/-2,M10.5.0/-1 stands for time in western Greenland, 3 hours behind UT, where - clocks follow the EU rules of springing forward on March's last + clocks follow the EU rule of springing forward on March's last Sunday at 01:00 UT (-02:00 local time, i.e., 22:00 the previous day) and falling back on October's last Sunday at 01:00 UT (-01:00 local time, i.e., 23:00 the previous day). The abbreviations for standard and daylight saving time are “-03” and “-02”. - If TZ specifies daylight saving time but does not specify a rule, and - the optional tzfile(5)-format file posixrules is present in the system - time conversion information directory, the rules in posixrules are - used, with the posixrules standard and daylight saving time offsets - from UT replaced by those specified by the offset values in TZ. - However, the posixrules file is obsolete: if it is present it is only - for backward compatibility, and it does not work reliably. Therefore, - if a TZ string directly specifies a timezone with daylight saving time, - it should specify the daylight saving rules explicitly. + If TZ specifies daylight saving time but does not specify a rule, the + rule typically defaults to the current US daylight-saving rule, + although such a default is not guaranteed and is incorrect for many + locations outside the US. Therefore, if a TZ string directly specifies + a timezone with daylight saving time, it should specify the daylight + saving rule explicitly. For compatibility with System V Release 3.1, a semicolon (;) may be used to separate the rule from the rest of the specification; this is @@ -214,10 +211,9 @@ ERRORS read(2). FILES - /etc/localtime local timezone file - /usr/share/zoneinfo timezone directory - /usr/share/zoneinfo/posixrules default DST rules (obsolete) - /usr/share/zoneinfo/GMT for UTC leap seconds + /etc/localtime local timezone file + /usr/share/zoneinfo timezone directory + /usr/share/zoneinfo/GMT for UTC leap seconds If /usr/share/zoneinfo/GMT is absent, UTC leap seconds are loaded from /usr/share/zoneinfo/GMT0 if present. diff --git a/private.h b/private.h index 074da284b7db..ee191b4ec33c 100644 --- a/private.h +++ b/private.h @@ -193,6 +193,10 @@ # define ctime_r _incompatible_ctime_r #endif /* HAVE_INCOMPATIBLE_CTIME_R */ +#ifndef TZ_RUNTIME_LEAPS +# define TZ_RUNTIME_LEAPS 1 +#endif + /* ** Nested includes */ @@ -410,15 +414,9 @@ typedef long long int_fast64_t; # endif # ifndef INT_FAST32_MAX -# if INT_MAX >> 31 == 0 typedef long int_fast32_t; -# define INT_FAST32_MAX LONG_MAX -# define INT_FAST32_MIN LONG_MIN -# else -typedef int int_fast32_t; -# define INT_FAST32_MAX INT_MAX -# define INT_FAST32_MIN INT_MIN -# endif +# define INT_FAST32_MAX LONG_MAX +# define INT_FAST32_MIN LONG_MIN # endif # ifndef INT_LEAST32_MAX @@ -541,9 +539,12 @@ typedef unsigned long uintmax_t; # define HAVE___HAS_C_ATTRIBUTE false #endif -#if 8 <= __GNUC__ -# define ATTRIBUTE_NONSTRING __attribute__((__nonstring__)) -#else +#ifdef __has_attribute +# if __has_attribute (nonstring) +# define ATTRIBUTE_NONSTRING __attribute__((__nonstring__)) +# endif +#endif +#ifndef ATTRIBUTE_NONSTRING # define ATTRIBUTE_NONSTRING #endif @@ -619,11 +620,11 @@ typedef unsigned long uintmax_t; # define ATTRIBUTE_UNSEQUENCED /* empty */ #endif -/* GCC attributes that are useful in tzcode. - __attribute__((const)) is stricter than [[unsequenced]], - so the latter is an adequate substitute in non-GCC C23 platforms. - __attribute__((pure)) is stricter than [[reproducible]], - so the latter is an adequate substitute in non-GCC C23 platforms. */ +/* GNU C attributes that are useful in tzcode. + Although neither __attribute__((const)) nor __attribute__((pure)) are + stricter than their C23 counterparts [[unsequenced]] and [[reproducible]], + the C23 attributes happen to work in each tzcode use of ATTRIBUTE_CONST + and ATTRIBUTE_PURE. (This might not work outside of tzcode!) */ #if __GNUC__ < 3 # define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED # define ATTRIBUTE_FORMAT(spec) /* empty */ @@ -642,6 +643,12 @@ typedef unsigned long uintmax_t; #else # define ATTRIBUTE_PURE_114833 /* empty */ #endif +/* GCC_LINT hack to pacify GCC bug 114833 even though the attribute is + not strictly correct, as the function might not return whereas pure + functions are supposed to return exactly once. This hack is not + known to generate wrong code for tzcode on any platform. + Remove this macro and its uses when the bug is fixed in a GCC release. */ +#define ATTRIBUTE_PURE_114833_HACK ATTRIBUTE_PURE_114833 #if (__STDC_VERSION__ < 199901 && !defined restrict \ && (PORT_TO_C89 || defined _MSC_VER)) @@ -922,11 +929,16 @@ time_t mktime_z(timezone_t restrict, struct tm *restrict); timezone_t tzalloc(char const *); void tzfree(timezone_t); # if STD_INSPIRED +# if TZ_RUNTIME_LEAPS +# define ATTRIBUTE_POSIX2TIME ATTRIBUTE_PURE +# else +# define ATTRIBUTE_POSIX2TIME ATTRIBUTE_CONST +# endif # if TZ_TIME_T || !defined posix2time_z -ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t); +ATTRIBUTE_POSIX2TIME time_t posix2time_z(timezone_t, time_t); # endif # if TZ_TIME_T || !defined time2posix_z -ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); +ATTRIBUTE_POSIX2TIME time_t time2posix_z(timezone_t, time_t); # endif # endif #endif @@ -937,7 +949,7 @@ ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); #define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type)) #define TYPE_SIGNED(type) (((type) -1) < 0) -#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) +#define TWOS_COMPLEMENT(type) (TYPE_SIGNED (type) && (! ~ (type) -1)) /* Minimum and maximum of two values. Use lower case to avoid naming clashes with standard include files. */ @@ -1077,7 +1089,10 @@ char *asctime_r(struct tm const *restrict, char *restrict); char *ctime_r(time_t const *, char *); #endif /* HAVE_INCOMPATIBLE_CTIME_R */ -/* Handy macros that are independent of tzfile implementation. */ +/* Handy constants that are independent of tzfile implementation. */ + +/* 2**31 - 1 as a signed integer, and usable in #if. */ +#define TWO_31_MINUS_1 2147483647 enum { SECSPERMIN = 60, diff --git a/strftime.c b/strftime.c index 487a5234cbc5..c249010561d1 100644 --- a/strftime.c +++ b/strftime.c @@ -49,8 +49,9 @@ and account for the tm_year origin (1900) and time_t origin (1970). */ #define MKTIME_FITS_IN(min, max) \ ((min) < 0 \ - && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ - && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) + && (((min) + TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 \ + < INT_MIN) \ + && INT_MAX < ((max) - TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) /* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow or if it is not known whether mktime can fail, diff --git a/theory.html b/theory.html index 6e52a929d335..12f4f7f4b439 100644 --- a/theory.html +++ b/theory.html @@ -3,14 +3,16 @@ Theory and pragmatics of the tz code and data +

Theory and pragmatics of the tz code and data

-

Outline