diff options
| -rw-r--r-- | Makefile | 48 | ||||
| -rw-r--r-- | NEWS | 99 | ||||
| -rw-r--r-- | date.1 | 2 | ||||
| -rw-r--r-- | date.1.txt | 2 | ||||
| -rw-r--r-- | localtime.c | 744 | ||||
| -rw-r--r-- | newctime.3 | 6 | ||||
| -rw-r--r-- | newctime.3.txt | 9 | ||||
| -rw-r--r-- | newtzset.3 | 29 | ||||
| -rw-r--r-- | newtzset.3.txt | 24 | ||||
| -rw-r--r-- | private.h | 55 | ||||
| -rw-r--r-- | strftime.c | 5 | ||||
| -rw-r--r-- | theory.html | 22 | ||||
| -rw-r--r-- | time2posix.3 | 134 | ||||
| -rw-r--r-- | time2posix.3.txt | 85 | ||||
| -rw-r--r-- | tz-art.html | 16 | ||||
| -rw-r--r-- | tz-how-to.html | 72 | ||||
| -rw-r--r-- | tz-link.html | 139 | ||||
| -rw-r--r-- | tzfile.5 | 7 | ||||
| -rw-r--r-- | tzfile.5.txt | 9 | ||||
| -rw-r--r-- | tzfile.h | 24 | ||||
| -rw-r--r-- | version | 2 | ||||
| -rw-r--r-- | zdump.c | 3 | ||||
| -rw-r--r-- | zic.8 | 20 | ||||
| -rw-r--r-- | zic.8.txt | 22 | ||||
| -rw-r--r-- | zic.c | 229 |
25 files changed, 991 insertions, 816 deletions
@@ -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 <sys/stat.h> does not work* @@ -322,13 +303,13 @@ LDLIBS= # variable, 0 otherwise (default is guessed) # -DHAVE_SYS_SINGLE_THREADED_H=0 if <sys/single_threaded.h> 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)/.' @@ -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 @@ -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 <pthread.h> @@ -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) - 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; + if (*name == '\0') + name = TZDEFRULESTRING; + if (! (*name == ',' || *name == ';')) + return false; - 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); + 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; - 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; - } + 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); - 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; + 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 != '\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; + 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; } } @@ -2491,6 +2468,19 @@ increment_overflow(int *ip, int j) } 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) { #ifdef ckd_add @@ -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 @@ <head> <title>Theory and pragmatics of the tz code and data</title> <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> <style> - pre {margin-left: 2em; white-space: pre-wrap;} + dd {margin-left: 1.3rem;} + pre {margin-left: 1.3rem; overflow: auto;} + ul {padding-left: 1.3rem;} </style> </head> <body> <h1>Theory and pragmatics of the <code><abbr>tz</abbr></code> code and data</h1> - <h3>Outline</h3> <nav> <ul> <li><a href="#scope">Scope of the <code><abbr>tz</abbr></code> @@ -648,14 +650,14 @@ Errors in the <code><abbr>tz</abbr></code> database arise from many sources: should be observed. In her 2015 book <cite><a - href="https://www.hup.harvard.edu/catalog.php?isbn=9780674286146">The + href="https://www.hup.harvard.edu/books/9780674286146">The Global Transformation of Time, 1870–1950</a></cite>, Vanessa Ogle writes “Outside of Europe and North America there was no system of time zones at all, often not even a stable landscape of mean times, prior to the middle decades of the twentieth century”. See: Timothy Shenk, <a -href="https://www.dissentmagazine.org/blog/booked-a-global-history-of-time-vanessa-ogle">Booked: +href="https://dissentmagazine.org/blog/booked-a-global-history-of-time-vanessa-ogle/">Booked: A Global History of Time</a>. <cite>Dissent</cite> 2015-12-17. </li> <li> @@ -789,7 +791,7 @@ href="https://www.dissentmagazine.org/blog/booked-a-global-history-of-time-vanes calendar with 24-hour days. These divergences range from relatively minor, such as Japanese bars giving times like 24:30 for the wee hours of the morning, to more-significant differences such as <a - href="https://theworld.org/stories/2015-01-30/if-you-have-meeting-ethiopia-you-better-double-check-time">the + href="https://theworld.org/stories/2015/01/30/ethiopian-time">the east African practice of starting the day at dawn</a>, renumbering the Western 06:00 to be 12:00. These practices are largely outside the scope of the <code><abbr>tz</abbr></code> code and data, which @@ -1126,7 +1128,7 @@ However POSIX.1-2024, like earlier POSIX editions, has some limitations: the name of a file from which time-related information is read. The file’s format is <dfn><abbr>TZif</abbr></dfn>, a timezone information format that contains binary data; see - <a href="https://www.rfc-editor.org/rfc/9636">Internet + <a href="https://www.rfc-editor.org/rfc/rfc9636">Internet <abbr>RFC</abbr> 9636</a>. The daylight saving time rules to be used for a particular timezone are encoded in the @@ -1438,7 +1440,7 @@ but they indicate the sort of problems that we would run into if we extended the time zone database further into the past. An excellent resource in this area is Edward M. Reingold and Nachum Dershowitz, <cite><a -href="https://www.cambridge.org/fr/academic/subjects/computer-science/computing-general-interest/calendrical-calculations-ultimate-edition-4th-edition">Calendrical +href="https://www.cambridge.org/fr/universitypress/subjects/computer-science/computing-general-interest/calendrical-calculations-ultimate-edition-4th-edition">Calendrical Calculations: The Ultimate Edition</a></cite>, Cambridge University Press (2018). Other information and sources are given in the file "<code>calendars</code>" in the <code><abbr>tz</abbr></code> distribution. @@ -1450,13 +1452,13 @@ They sometimes disagree. <h2 id="planets">Time and time zones off Earth</h2> <p> The European Space Agency is <a -href="https://www.esa.int/Applications/Navigation/Telling_time_on_the_Moon">considering</a> +href="https://www.esa.int/Applications/Satellite_navigation/Telling_time_on_the_Moon">considering</a> the establishment of a reference timescale for the Moon, which has days roughly equivalent to 29.5 Earth days, and where relativistic effects cause clocks to tick slightly faster than on Earth. Also, <abbr title="National Aeronautics and Space Administration">NASA</abbr> has been <a -href="https://www.whitehouse.gov/wp-content/uploads/2024/04/Celestial-Time-Standardization-Policy.pdf">ordered</a> +href="https://bidenwhitehouse.archives.gov/wp-content/uploads/2024/04/Celestial-Time-Standardization-Policy.pdf">ordered</a> to consider the establishment of Coordinated Lunar Time (<abbr>LTC</abbr>). It is not yet known whether the US and European efforts will result in multiple timescales on the Moon. @@ -1576,7 +1578,7 @@ Sources for time on other planets: </li> <li> Matt Williams, - “<a href="https://www.universetoday.com/37481/days-of-the-planets/">How + “<a href="https://www.universetoday.com/articles/days-of-the-planets">How long is a day on the other planets of the solar system?</a>” (2016-01-20). </li> diff --git a/time2posix.3 b/time2posix.3 index 4a6969ede2a3..adbff83aa808 100644 --- a/time2posix.3 +++ b/time2posix.3 @@ -25,8 +25,9 @@ time2posix, posix2time \- convert seconds since the Epoch .. IEEE Standard 1003.1 (POSIX) -requires the time_t value 536457599 to stand for 1986-12-31 23:59:59 UTC. -This effectively implies that POSIX time_t values cannot include leap +says that +.B time_t +values cannot count leap seconds and, therefore, that the system time must be adjusted as each leap occurs. @@ -35,18 +36,22 @@ If the time package is configured with leap-second support enabled, however, no such adjustment is needed and -time_t values continue to increase over leap events +.B time_t +values continue to increase over leap events (as a true .q "seconds since...\&" value). This means that these values will differ from those required by POSIX by the net number of leap seconds inserted since the Epoch. .PP -Typically this is not a problem as the type time_t is intended -to be +For many C programs this is not a problem as the C standard says that +.B time_t +is (mostly) -opaque \*(en time_t values should only be obtained-from and -passed-to functions such as +opaque \*(en +.B time_t +values should be obtained from and +passed to functions such as .BR time(2) , .BR localtime(3) , .BR mktime(3) , @@ -54,11 +59,14 @@ and .BR difftime(3) . However, POSIX gives an arithmetic -expression for directly computing a time_t value from a given date/time, +expression for computing a +.B time_t +value directly from a given Universal date and time, and the same relationship is assumed by some -(usually older) applications. -Any programs creating/dissecting time_t values +Any programs creating/dissecting +.B time_t +values using such a relationship will typically not handle intervals over leap seconds correctly. .PP @@ -66,30 +74,31 @@ The .B time2posix and .B posix2time -functions are provided to address this time_t mismatch by converting -between local time_t values and their POSIX equivalents. +functions address this mismatch by converting +between local +.B time_t +values and their POSIX equivalents. This is done by accounting for the number of time-base changes that would have taken place on a POSIX system as leap seconds were inserted or deleted. -These converted values can then be used in lieu of correcting the older -applications, -or when communicating with POSIX-compliant systems. +These converted values can then be used +when communicating with POSIX-compliant systems. .PP The .B time2posix -function -is single-valued. -That is, -every local time_t -corresponds to a single POSIX time_t. +function converts a +.B time_t +value to its POSIX counterpart, if one exists. The .B posix2time -function +function does the reverse but is less well-behaved: for a positive leap second hit the result is not unique, and for a negative leap second hit the corresponding -POSIX time_t doesn't exist so an adjacent value is returned. -Both of these are good indicators of the inferiority of the +POSIX +.B time_t +doesn't exist so an adjacent value is returned. +Both of these are indicate problems with the POSIX representation. .PP The following table summarizes the relationship between a time @@ -97,35 +106,70 @@ T and its conversion to, and back from, the POSIX representation over the leap second inserted at the end of June, 1993. +In this table, X=time2posix(T), Y=posix2time(X), A=741484816, and B=A\-17 +because 17 positive leap seconds preceded this leap second. +.PP +.in +2 .nf -.ta \w'93/06/30\0'u +\w'23:59:59\0'u +\w'A+0\0'u +\w'X=time2posix(T)\0'u -DATE TIME T X=time2posix(T) posix2time(X) -93/06/30 23:59:59 A+0 B+0 A+0 -93/06/30 23:59:60 A+1 B+1 A+1 or A+2 -93/07/01 00:00:00 A+2 B+1 A+1 or A+2 -93/07/01 00:00:01 A+3 B+2 A+3 - -A leap second deletion would look like... - -DATE TIME T X=time2posix(T) posix2time(X) -??/06/30 23:59:58 A+0 B+0 A+0 -??/07/01 00:00:00 A+1 B+2 A+1 -??/07/01 00:00:01 A+2 B+3 A+2 -.sp -.ce - [Note: posix2time(B+1) => A+0 or A+1] +.ta \w'1993-06-30\0'u +\w'23:59:59\0'u +\w'A+0\0'u +\w'B+0\0'u +DATE TIME T X Y +1993-06-30 23:59:59 A B A +1993-06-30 23:59:60 A+1 B+1 A+1 or A+2 +1993-07-01 00:00:00 A+2 B+1 A+1 or A+2 +1993-07-01 00:00:01 A+3 B+2 A+3 +.in .fi .PP +A leap second deletion would look like the following, and +posix2time(B+1) would return either A or A+1. +.PP +.in +2 +.nf +DATE TIME T X Y +????-06-30 23:59:58 A B A +????-07-01 00:00:00 A+1 B+2 A+1 +????-07-01 00:00:01 A+2 B+3 A+2 +.fi +.in +.PP If leap-second support is not enabled, -local time_t and -POSIX time_t values are equivalent, +local +.B time_t +and +POSIX +.B time_t +values are equivalent, and both .B time2posix and .B posix2time degenerate to the identity function. +.SH "RETURN VALUE" +If successful, these functions return the resulting timestamp without modifying +.BR errno . +Otherwise, they set +.B errno +and return +.BR "((time_t) -1)" . +.SH ERRORS +These functions fail if: +.TP +[EOVERFLOW] +The resulting value cannot be represented. +This can happen for +.B posix2time +if its argument is close to the maximum +.B time_t +value. +In environments where the +.I TZ +environment variable names a TZif file, +overflow can happen for either function for an argument sufficiently +close to an extreme +.B time_t +value if the TZif file specifies unrealistic leap second corrections. .SH SEE ALSO -difftime(3), -localtime(3), -mktime(3), -time(2) +.BR difftime (3), +.BR localtime (3), +.BR mktime (3), +.BR time (2). diff --git a/time2posix.3.txt b/time2posix.3.txt index 6b0e0449b816..57556b8789dc 100644 --- a/time2posix.3.txt +++ b/time2posix.3.txt @@ -13,10 +13,9 @@ SYNOPSIS cc ... -ltz DESCRIPTION - IEEE Standard 1003.1 (POSIX) requires the time_t value 536457599 to - stand for 1986-12-31 23:59:59 UTC. This effectively implies that POSIX - time_t values cannot include leap seconds and, therefore, that the - system time must be adjusted as each leap occurs. + IEEE Standard 1003.1 (POSIX) says that time_t values cannot count leap + seconds and, therefore, that the system time must be adjusted as each + leap occurs. If the time package is configured with leap-second support enabled, however, no such adjustment is needed and time_t values continue to @@ -24,53 +23,69 @@ DESCRIPTION means that these values will differ from those required by POSIX by the net number of leap seconds inserted since the Epoch. - Typically this is not a problem as the type time_t is intended to be - (mostly) opaque – time_t values should only be obtained-from and - passed-to functions such as time(2), localtime(3), mktime(3), and + For many C programs this is not a problem as the C standard says that + time_t is (mostly) opaque – time_t values should be obtained from and + passed to functions such as time(2), localtime(3), mktime(3), and difftime(3). However, POSIX gives an arithmetic expression for - directly computing a time_t value from a given date/time, and the same - relationship is assumed by some (usually older) applications. Any + computing a time_t value directly from a given Universal date and time, + and the same relationship is assumed by some applications. Any programs creating/dissecting time_t values using such a relationship will typically not handle intervals over leap seconds correctly. - The time2posix and posix2time functions are provided to address this - time_t mismatch by converting between local time_t values and their - POSIX equivalents. This is done by accounting for the number of time- - base changes that would have taken place on a POSIX system as leap - seconds were inserted or deleted. These converted values can then be - used in lieu of correcting the older applications, or when - communicating with POSIX-compliant systems. + The time2posix and posix2time functions address this mismatch by + converting between local time_t values and their POSIX equivalents. + This is done by accounting for the number of time-base changes that + would have taken place on a POSIX system as leap seconds were inserted + or deleted. These converted values can then be used when communicating + with POSIX-compliant systems. - The time2posix function is single-valued. That is, every local time_t - corresponds to a single POSIX time_t. The posix2time function is less - well-behaved: for a positive leap second hit the result is not unique, - and for a negative leap second hit the corresponding POSIX time_t - doesn't exist so an adjacent value is returned. Both of these are good - indicators of the inferiority of the POSIX representation. + The time2posix function converts a time_t value to its POSIX + counterpart, if one exists. The posix2time function does the reverse + but is less well-behaved: for a positive leap second hit the result is + not unique, and for a negative leap second hit the corresponding POSIX + time_t doesn't exist so an adjacent value is returned. Both of these + are indicate problems with the POSIX representation. The following table summarizes the relationship between a time T and its conversion to, and back from, the POSIX representation over the - leap second inserted at the end of June, 1993. - DATE TIME T X=time2posix(T) posix2time(X) - 93/06/30 23:59:59 A+0 B+0 A+0 - 93/06/30 23:59:60 A+1 B+1 A+1 or A+2 - 93/07/01 00:00:00 A+2 B+1 A+1 or A+2 - 93/07/01 00:00:01 A+3 B+2 A+3 + leap second inserted at the end of June, 1993. In this table, + X=time2posix(T), Y=posix2time(X), A=741484816, and B=A-17 because 17 + positive leap seconds preceded this leap second. - A leap second deletion would look like... + DATE TIME T X Y + 1993-06-30 23:59:59 A B A + 1993-06-30 23:59:60 A+1 B+1 A+1 or A+2 + 1993-07-01 00:00:00 A+2 B+1 A+1 or A+2 + 1993-07-01 00:00:01 A+3 B+2 A+3 - DATE TIME T X=time2posix(T) posix2time(X) - ??/06/30 23:59:58 A+0 B+0 A+0 - ??/07/01 00:00:00 A+1 B+2 A+1 - ??/07/01 00:00:01 A+2 B+3 A+2 + A leap second deletion would look like the following, and + posix2time(B+1) would return either A or A+1. - [Note: posix2time(B+1) => A+0 or A+1] + DATE TIME T X Y + ????-06-30 23:59:58 A B A + ????-07-01 00:00:00 A+1 B+2 A+1 + ????-07-01 00:00:01 A+2 B+3 A+2 If leap-second support is not enabled, local time_t and POSIX time_t values are equivalent, and both time2posix and posix2time degenerate to the identity function. +RETURN VALUE + If successful, these functions return the resulting timestamp without + modifying errno. Otherwise, they set errno and return ((time_t) -1). + +ERRORS + These functions fail if: + + [EOVERFLOW] + The resulting value cannot be represented. This can happen for + posix2time if its argument is close to the maximum time_t value. + In environments where the TZ environment variable names a TZif + file, overflow can happen for either function for an argument + sufficiently close to an extreme time_t value if the TZif file + specifies unrealistic leap second corrections. + SEE ALSO - difftime(3), localtime(3), mktime(3), time(2) + difftime(3), localtime(3), mktime(3), time(2). Time Zone Database time2posix(3) diff --git a/tz-art.html b/tz-art.html index 3039a86fc48c..1bd1bf781166 100644 --- a/tz-art.html +++ b/tz-art.html @@ -2,6 +2,10 @@ <html lang="en"> <head> <meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<style> +ul {padding-left: 1.3rem;} +</style> <title>Time and the Arts</title> </head> <body> @@ -20,8 +24,8 @@ with Time & Timezones – Computerphile</a>” (2013; 10:12) delves into problems that programmers have with timekeeping. </li> <li> -“<a href="https://www.rferl.org/a/28375932.html">All The Time -In The World: Explaining The Mysteries Of Time Zones</a>” (2017; 2:15) +“<a href="https://www.rferl.org/a/all-the-time-in-the-world/28375932.html">All +The Time In The World: Explaining The Mysteries Of Time Zones</a>” (2017; 2:15) briefly says why France has more time zones than Russia. </li> <li> @@ -210,7 +214,7 @@ reading a paper. Available versions include <a href="https://www.gutenberg.org/ebooks/103">an English translation</a>, and -<a href="https://fourmilab.ch/etexts/www/tdm80j">the original French</a> +<a href="https://fourmilab.ch/etexts/www/tdm80j/">the original French</a> “with illustrations from the original 1873 French-language edition”. </li> <li> @@ -516,7 +520,7 @@ their eyes to see their true interest. All the difficulty will be in the first two or three days: after which the reformation will be as natural and easy as the present irregularity; for, <em>ce n’est que le premier pas qui coûte</em>.” -<a href="http://www.webexhibits.org/daylightsaving/franklin3.html">Franklin’s +<a href="https://www.webexhibits.org/daylightsaving/franklin3.html">Franklin’s joke</a> was first published on 1784-04-26 by the <em>Journal de Paris</em> as <a href="https://en.wikipedia.org/wiki/File:Franklin-Benjamin-Journal-de-Paris-1784.jpg">an @@ -588,7 +592,7 @@ yesterday daylight [saving] time ended. Right now it’s basically midnight.” “The best method, I told folks, was to hang a large clock high on a barn wall where all the cows could see it. If you have Holsteins, you will need to use an analog clock.” (Jerry Nelson, “<a -href="http://www.agriculture.com/family/farm-humor/how-to-adjust-dairy-cows-to-daylight-savings-time">How +href="https://www.agriculture.com/family/farm-humor/how-to-adjust-dairy-cows-to-daylight-savings-time">How to adjust dairy cows to daylight saving time</a>”, <em>Successful Farming</em>, 2017-10-09) </li> @@ -597,7 +601,7 @@ to adjust dairy cows to daylight saving time</a>”, in order to change the time zone on my laptop clock. Evidently, someone is out to mess up my schedule and my clock must be secured.” (Garrison Keillor, -“<a href="http://www.garrisonkeillor.com/weve-never-been-here-before/">We’ve +“<a href="https://www.garrisonkeillor.com/weve-never-been-here-before/">We’ve never been here before</a>”, 2017-08-22) </li> <li> diff --git a/tz-how-to.html b/tz-how-to.html index ccfdc9eb4fdb..9f4f2284c7b4 100644 --- a/tz-how-to.html +++ b/tz-how-to.html @@ -3,14 +3,15 @@ <head> <title>How to Read the tz Database</title> <meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> <style> -pre {margin-left: 2em; white-space: pre-wrap;} -pre.td {margin-left: 0;} +pre {margin-left: 1.3rem; overflow: auto;} td {text-align: center;} table {border: 1px outset;} th, td {border: 1px inset;} table.rule {border: none; margin: auto;} td.footnote {text-align: left;} +ul {padding-left: 1.3rem;} </style> </head> <body> @@ -38,16 +39,7 @@ about the zones.</p> for Chicago (from the <code>northamerica</code> file in the <code>data</code> subdirectory):</p> -<table> -<tr> - <th colspan="6">From the Source File</th> -</tr> -<tr> - <td colspan="6"> - <table class="rule"> - <tr><td style="border:none;text-align:left"> -<pre class="td"> -#Rule NAME FROM TO - IN ON AT SAVE LETTER +<pre>#Rule NAME FROM TO - IN ON AT SAVE LETTER Rule Chicago 1920 only - Jun 13 2:00 1:00 D Rule Chicago 1920 1921 - Oct lastSun 2:00 0 S Rule Chicago 1921 only - Mar lastSun 2:00 1:00 D @@ -55,8 +47,7 @@ Rule Chicago 1922 1966 - Apr lastSun 2:00 1:00 D Rule Chicago 1922 1954 - Sep lastSun 2:00 0 S Rule Chicago 1955 1966 - Oct lastSun 2:00 0 S </pre> - </td></tr></table></td> -</tr> +<table> <tr> <th colspan="6">Reformatted a Bit</th> </tr> @@ -160,16 +151,7 @@ time changed in 1955. Got it?</p> <p>OK, now for the somewhat more interesting “US” rules:</p> -<table> -<tr> - <th colspan="6">From the Source File</th> -</tr> -<tr> - <td colspan="6"> - <table class="rule"> - <tr><td style="border:none;text-align:left"> -<pre class="td"> -#Rule NAME FROM TO - IN ON AT SAVE LETTER/S +<pre>#Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule US 1918 1919 - Mar lastSun 2:00 1:00 D Rule US 1918 1919 - Oct lastSun 2:00 0 S Rule US 1942 only - Feb 9 2:00 1:00 W # War @@ -184,8 +166,7 @@ Rule US 1987 2006 - Apr Sun>=1 2:00 1:00 D Rule US 2007 max - Mar Sun>=8 2:00 1:00 D Rule US 2007 max - Nov Sun>=1 2:00 0 S </pre> - </td></tr></table></td> -</tr> +<table> <tr> <th colspan="6">Reformatted a Bit</th> </tr> @@ -328,15 +309,7 @@ rule, so there should be no change.</li> <p>OK, now let’s look at a Zone record:</p> -<table> -<tr> - <th colspan="5">From the Source File</th> -</tr> -<tr> - <td colspan="5"> - <table class="rule"> - <tr><td style="border:none;text-align:left"> -<pre class="td"> +<pre> #Zone NAME STDOFF RULES FORMAT [UNTIL] Zone America/Chicago -5:50:36 - LMT 1883 Nov 18 12:09:24 -6:00 US C%sT 1920 @@ -347,8 +320,7 @@ Zone America/Chicago -5:50:36 - LMT 1883 Nov 18 12:09:24 -6:00 Chicago C%sT 1967 -6:00 US C%sT </pre> - </td></tr></table></td> -</tr> +<table> <tr> <th colspan="5">Columns Renamed</th> </tr> @@ -576,31 +548,14 @@ the true offset is undefined. <p>As a final example, here’s the complete history for Hawaii:</p> -<table> -<tr> - <th colspan="6">Relevant Excerpts from the US Rules</th> -</tr> -<tr> - <td colspan="6"> - <table class="rule"> - <tr><td style="border:none;text-align:left"> -<pre class="td"> +<pre># Relevant Excerpts from the US Rules #Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule US 1918 1919 - Oct lastSun 2:00 0 S Rule US 1942 only - Feb 9 2:00 1:00 W # War Rule US 1945 only - Aug 14 23:00u 1:00 P # Peace Rule US 1945 only - Sep lastSun 2:00 0 S -</pre> - </td></tr></table></td> -</tr> -<tr> - <th colspan="6">The Zone Record</th> -</tr> -<tr> - <td colspan="6"> - <table class="rule"> - <tr><td style="border:none;text-align:left"> -<pre class="td"> + +# The Zone Record #Zone NAME STDOFF RULES FORMAT [UNTIL] Zone Pacific/Honolulu -10:31:26 - LMT 1896 Jan 13 12:00 -10:30 - HST 1933 Apr 30 2:00 @@ -608,8 +563,7 @@ Zone Pacific/Honolulu -10:31:26 - LMT 1896 Jan 13 12:00 -10:30 US H%sT 1947 Jun 8 2:00 -10:00 - HST </pre> - </td></tr></table></td> -</tr> +<table> <tr> <th colspan="6">What We Infer</th> </tr> diff --git a/tz-link.html b/tz-link.html index 9267fb6f822b..5f1989f014b3 100644 --- a/tz-link.html +++ b/tz-link.html @@ -3,8 +3,11 @@ <head> <title>Time zone and daylight saving time data</title> <meta charset="UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> <style> -pre {margin-left: 2em; white-space: pre-wrap;} +dd {margin-left: 1.3rem;} +pre {margin-left: 1.3rem; overflow: auto;} +ul {padding-left: 1.3rem;} </style> </head> <body> @@ -18,7 +21,6 @@ histories and planned futures are often recorded only fitfully. Here is a summary of attempts to organize and record relevant data in this area. </p> - <h3>Outline</h3> <nav> <ul> <li>The <code><abbr>tz</abbr></code> database product and process @@ -90,15 +92,15 @@ title="Berkeley Software Distribution">BSD</abbr></a>, title="Web Operating System">webOS</abbr></a>, <a href="https://en.wikipedia.org/wiki/IBM_AIX"><abbr title="Advanced Interactive eXecutive">AIX</abbr></a>, -<a href="https://www.apple.com/ios"><abbr +<a href="https://www.apple.com/os/ios/"><abbr title="iPhone OS">iOS</abbr></a>, -<a href="https://www.apple.com/macos">macOS</a>, +<a href="https://www.apple.com/os/macos/">macOS</a>, <a href="https://www.microsoft.com/en-us/windows">Microsoft Windows</a>, -<a href="https://www.vmssoftware.com">Open<abbr +<a href="https://vmssoftware.com">Open<abbr title="Virtual Memory System">VMS</abbr></a>, <a href="https://www.oracle.com/database/">Oracle Database</a>, -<a href="https://www.oracle.com/solaris">Oracle Solaris</a>, -and <a href="https://blackberry.qnx.com/en">QNX</a>.</p> +<a href="https://www.oracle.com/solaris/solaris11/">Oracle Solaris</a>, +and <a href="https://qnx.software/en">QNX</a>.</p> <p> Each main entry in the database represents a <dfn>timezone</dfn> for a set of civil-time clocks that have all agreed since 1970. @@ -182,7 +184,7 @@ title="Hypertext Transfer Protocol Secure">HTTPS</abbr></a>, title="remote sync">rsync</abbr></a>, and <a href="https://en.wikipedia.org/wiki/FTP"><abbr title="File Transfer Protocol">FTP</abbr></a>. -<p>Alternatively, a development repository of code and data can bem +<p>Alternatively, a development repository of code and data can be retrieved from <a href="https://github.com">GitHub</a> via the shell command:</p> <pre><code><a href="https://git-scm.com">git</a> clone <a href="https://github.com/eggert/tz">https://github.com/eggert/tz</a> @@ -234,7 +236,7 @@ data yourself. System-specific instructions for installing the latest <code><abbr>tz</abbr></code> data have also been published for <a href="https://www.ibm.com/support/pages/aix-time-zone-olson-tzdata-updates"><abbr>AIX</abbr></a>, <a -href="https://source.android.com/devices/tech/config/timezone-rules">Android</a>, +href="https://source.android.com/docs/core/permissions/timezone-rules">Android</a>, <a href="https://unicode-org.github.io/icu/userguide/datetime/timezone/"><abbr title="International Components for Unicode">ICU</abbr></a>, @@ -287,7 +289,7 @@ community, and data distributors downstream. <p> If your government plans to change its time zone boundaries or daylight saving rules, please send email as described in -"<a href="#changes">Changes to the <code><abbr>tz</abbr></code> database</a>". +“<a href="#changes">Changes to the <code><abbr>tz</abbr></code> database</a>”. Do this well in advance, as this will lessen confusion and will coordinate updates to many cell phones, computers, and other devices around the world. @@ -321,7 +323,7 @@ year before it affects how clocks operate; otherwise, there is a good chance that many clocks will be wrong due to delays in propagating updates, and that residents will be confused or even actively resist the change. The shorter the notice, the more likely clock problems will arise; see “<a -href="https://codeofmatt.com/2016/04/23/on-the-timing-of-time-zone-changes/">On +href="https://codeofmatt.com/on-the-timing-of-time-zone-changes/">On the Timing of Time Zone Changes</a>” for examples. </p> </section> @@ -439,7 +441,7 @@ transition in the <code><abbr>tz</abbr></code> database.</li> Database Parser</a> is a <a href="https://en.wikipedia.org/wiki/C++">C++</a> parser and runtime library with a <a -href="https://en.cppreference.com/w/cpp/chrono"><code>std::chrono</code> API</a> +href="https://en.cppreference.com/w/cpp/chrono.html"><code>std::chrono</code> API</a> that is a standard part of C++. It is freely available under the <abbr title="Massachusetts Institute of Technology">MIT</abbr> license.</li> @@ -469,7 +471,7 @@ Although its source code is proprietary, its executable is available under the <a href="https://www.oracle.com/a/tech/docs/tzupdater-lic.html">Java SE Timezone Updater License Agreement</a>.</li> <li>The <a -href="https://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html">Java +href="https://www.oracle.com/technical-resources/articles/java/jf14-date-time.html">Java SE 8 Date and Time</a> <abbr>API</abbr> can be supplemented by <a href="https://www.threeten.org/threeten-extra/">ThreeTen-Extra</a>, which is freely available under a <abbr>BSD</abbr>-style license.</li> @@ -481,7 +483,7 @@ Java 8 <code>java.time</code>, which its users should migrate to once they can assume Java 8 or later. It is available under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache License</a>.</li> <li><a href="https://bell-sw.com/pages/iana-updater/">IANA Updater</a> and <a -href="https://www.azul.com/products/open-source-tools/ziupdater-time-zone-tool/">ZIUpdater</a> +href="https://www.azul.com/products/components/ziupdater-time-zone-tool/">ZIUpdater</a> are alternatives to TZUpdater. IANA Updater’s license is unclear; ZIUpdater is licensed under the <abbr>GPL</abbr>.</li> <li><a href="https://github.com/MenoData/Time4A">Time4A: Advanced date and @@ -580,7 +582,7 @@ library that compiles <code><abbr>tz</abbr></code> source into a time zone repository whose format is either proprietary or an <abbr>XML</abbr>-encoded representation.</li> -<li><a id="Tcl" href="https://tcl.tk">Tcl</a> +<li><a id="Tcl" href="https://www.tcl-lang.org">Tcl</a> contains a developer-oriented parser that compiles <code><abbr>tz</abbr></code> source into text files, along with a runtime that can read those files. Tcl is freely available under a <abbr>BSD</abbr>-style @@ -599,7 +601,7 @@ a <abbr>TZif</abbr> file reader. This library is freely available under the LGPL and is widely used in <abbr>GNU</abbr>/Linux systems.</li> <li><a href="https://www.gnome.org">GNOME</a>’s -<a href="https://developer.gnome.org/glib/">GLib</a> has +<a href="https://docs.gtk.org/glib/">GLib</a> has a <abbr>TZif</abbr> file reader written in C that creates a <code>GTimeZone</code> object representing sets of <abbr>UT</abbr> offsets. @@ -613,7 +615,7 @@ the Apache License.</li> library that translates between <abbr>UT</abbr> and civil time and can read <abbr>TZif</abbr> files. It is freely available under the Apache License.</li> -<li>The <a href="https://golang.org">Go programming language</a> +<li>The <a href="https://go.dev">Go programming language</a> has a <abbr>TZif</abbr> file reader <a href="https://pkg.go.dev/time#LoadLocationFromTZData"><code>LoadLocationFromTZData</code></a>.</li> <li>The @@ -629,7 +631,7 @@ The package is freely available under the MIT license.</li> <li><a href="https://github.com/derickr/timelib">Timelib</a> is a C library that reads <abbr>TZif</abbr> files and converts timestamps from one time zone or format to another. -It is used by <a href="https://secure.php.net"><abbr +It is used by <a href="https://www.php.net"><abbr title="PHP: Hypertext Preprocessor">PHP</abbr></a>, <a href="https://hhvm.com"><abbr title="HipHop Virtual Machine">HHVM</abbr></a>, and <a href="https://www.mongodb.com">MongoDB</a>. @@ -673,20 +675,20 @@ available under a <abbr>BSD</abbr>-style license.</li> <li><a href="https://foxclocks.org">FoxClocks</a> is an extension for <a href="https://www.google.com/chrome/">Google Chrome</a>, <a -href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> and <a -href="https://www.mozilla.org/en-US/thunderbird/">Thunderbird</a>. +href="https://www.firefox.com/en-US/">Firefox</a> and <a +href="https://www.thunderbird.net/en-US/">Thunderbird</a>. It displays multiple clocks in the application window, and has a mapping -interface to <a href="https://www.google.com/earth/">Google Earth</a>. +interface to <a href="https://earth.google.com/web/">Google Earth</a>. It is freely available under the <abbr>GPL</abbr>.</li> <li>Microsoft Windows 8.1 and later has <code><abbr>tz</abbr></code> data and <abbr>CLDR</abbr> data (mentioned <a href="#CLDR">below</a>) used by the <a href="https://en.wikipedia.org/wiki/Windows_Runtime">Windows Runtime</a> / <a href="https://en.wikipedia.org/wiki/Universal_Windows_Platform">Universal Windows Platform</a> classes -<a href="https://docs.microsoft.com/uwp/api/Windows.Globalization.DateTimeFormatting.DateTimeFormatter"><code>DateTimeFormatter</code></a> and -<a href="https://docs.microsoft.com/uwp/api/windows.globalization.calendar"><code>Calendar</code></a>. +<a href="https://learn.microsoft.com/en-us/uwp/api/Windows.Globalization.DateTimeFormatting.DateTimeFormatter"><code>DateTimeFormatter</code></a> and +<a href="https://learn.microsoft.com/en-us/uwp/api/windows.globalization.calendar"><code>Calendar</code></a>. <a id="System.TimeZoneInfo" -href="https://blogs.msdn.microsoft.com/bclteam/2007/06/07/exploring-windows-time-zones-with-system-timezoneinfo-josh-free/">Exploring +href="https://learn.microsoft.com/en-us/archive/blogs/bclteam/exploring-windows-time-zones-with-system-timezoneinfo-josh-free">Exploring Windows Time Zones with <code>System.TimeZoneInfo</code></a> describes the older, proprietary method of Microsoft Windows 2000 and later, which stores time zone data in the @@ -694,13 +696,15 @@ which stores time zone data in the <a href="https://unicode.org/cldr/charts/latest/supplemental/zone_tzid.html">Zone → Tzid table</a> or <a -href="https://github.com/unicode-org/cldr/blob/master/common/supplemental/windowsZones.xml"><abbr>XML</abbr> +href="https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml"><abbr>XML</abbr> file</a> of the <abbr>CLDR</abbr> data maps proprietary zone IDs to <code><abbr>tz</abbr></code> names. -These mappings can be performed programmatically via the <a href="https://github.com/mj1856/TimeZoneConverter">TimeZoneConverter</a> .NET library, +These mappings can be performed programmatically via the +<a href="https://github.com/mattjohnsonpint/TimeZoneConverter">TimeZoneConverter</a> +.NET library, or the ICU Java and C++ libraries mentioned <a href="#ICU">above</a>. <li><a -href="https://www.oracle.com/java/index.html">Oracle +href="https://www.oracle.com/java/">Oracle Java</a> contains a copy of a subset of a recent <code><abbr>tz</abbr></code> database in a Java-specific format.</li> @@ -713,8 +717,8 @@ Java-specific format.</li> <li><a href="https://www.astro.com/atlas">Time-zone Atlas</a> is Astrodienst’s Web version of Shanks and Pottenger’s out-of-print time zone history atlases -<a href="https://www.worldcat.org/oclc/468828649">for the US</a> and -<a href="https://www.worldcat.org/oclc/76950459">for the world</a>. +<a href="https://search.worldcat.org/title/468828649">for the US</a> and +<a href="https://search.worldcat.org/title/76950459">for the world</a>. Although these extensive atlases <a href="https://astrologynewsservice.com/opinion/how-astrologers-contributed-to-the-information-age-a-brief-history-of-time/">were sources for much of the older <code><abbr>tz</abbr></code> data</a>, @@ -728,7 +732,7 @@ its own <code>tztab</code>(4) format.</li> <li><a href="https://www.worldtimeserver.com">World Time Server</a> is another time zone database.</li> <li>The <a -href="https://www.iata.org/publications/store/Pages/standard-schedules-information.aspx">Standard +href="https://www.iata.org/en/publications/manuals/standard-schedules-information">Standard Schedules Information Manual</a> of the International Air Transport Association gives current time zone rules for airports served by commercial aviation.</li> @@ -738,31 +742,24 @@ gives current time zone rules for airports served by commercial aviation.</li> <section> <h2 id="maps">Maps</h2> <ul> -<li>The <a -href="https://www.cia.gov/the-world-factbook/maps/world-regional/">World -and Regional Maps section</a> of <em>The World Factbook</em>, published by the -<a href="https://www.cia.gov">US Central Intelligence -Agency (<abbr -title="Central Intelligence Agency">CIA</abbr>)</a>, contains a time -zone map; the -<a -href="https://legacy.lib.utexas.edu/maps/world.html">Perry–Castañeda -Library Map Collection</a> -of the University of Texas at Austin has copies of -recent editions. -The pictorial quality is good, -but the maps do not indicate daylight saving time, -and parts of the data are a few years out of date.</li> <li><a href="https://www.worldtimezone.com">World Time Zone Map with current time</a> -has several fancy time zone maps; it covers Russia particularly well. -The maps’ pictorial quality is not quite as good as the <abbr>CIA</abbr>’s -but the maps are more up to date.</li> +has several fancy time zone maps; it covers Russia particularly well.</li> <li><a href="https://blog.poormansmath.net/how-much-is-time-wrong-around-the-world/">How much is time wrong around the world?</a> maps the difference between mean solar and standard time, highlighting areas such as western China where the two differ greatly. It’s a bit out of date, unfortunately.</li> +<li>The +<a +href="https://maps.lib.utexas.edu/maps/world.html">Perry–Castañeda +Library Map Collection</a> of the University of Texas at Austin has +copies of old maps taken from <a +href="https://en.wikipedia.org/wiki/The_World_Factbook"><em>The +World Factbook</em></a>, formerly published by the +<a href="https://www.cia.gov">US Central Intelligence Agency</a>. +Although the maps’ pictorial quality is good, +the maps do not indicate daylight saving time.</li> </ul> </section> @@ -788,7 +785,7 @@ for looking up a timezone name from a GPS coordinate.</li> the <a href="https://www.geonames.org/export/web-services.html#timezone">GeoNames Timezone web service</a>, the <a -href="https://developers.google.com/maps/documentation/timezone/intro">Google +href="https://developers.google.com/maps/documentation/timezone/overview">Google Maps Time Zone API</a>, and the <a href="https://timezonedb.com/api">TimeZoneDB API</a>. Commercial network API access is provided @@ -799,7 +796,7 @@ and <a href="https://www.geogarage.com/blog/news-1/post/geogarage-time-zone-api- href="https://stackoverflow.com/questions/16086962/how-to-get-a-time-zone-from-a-location-using-latitude-and-longitude-coordinates/16086964">How to get a time zone from a location using latitude and longitude coordinates?</a>” discusses other geolocation possibilities.</li> -<li><a href="http://statoids.com/statoids.html">Administrative +<li><a href="https://statoids.com/statoids.html">Administrative Divisions of Countries (“Statoids”)</a> lists political subdivision data related to time zones.</li> <li><a href="https://manifold.net/info/freestuff.shtml">Manifold Software @@ -826,7 +823,7 @@ common.</li> Walk through Time</a> surveys the evolution of timekeeping.</li> <li>The history of daylight saving time is surveyed in <a -href="http://www.webexhibits.org/daylightsaving/">About Daylight +href="https://www.webexhibits.org/daylightsaving/">About Daylight Saving Time – History, rationale, laws & dates</a> and summarized in <a href="http://seizethedaylight.com/dst/">A Brief History of Daylight Saving Time</a>.</li> @@ -839,7 +836,7 @@ deal with civil time.</li> <li><a href="https://webspace.science.uu.nl/~gent0113/idl/idl.htm">A History of the International Date Line</a> tells the story of the most important time zone boundary.</li> -<li><a href="http://statoids.com/tconcept.html">Basic Time +<li><a href="https://statoids.com/tconcept.html">Basic Time Zone Concepts</a> discusses terminological issues behind time zones.</li> </ul> </section> @@ -886,7 +883,7 @@ hreflang="cs">When daylight saving time starts and ends (in Czech)</a> summarizes and cites historical <abbr>DST</abbr> regulations.</dd> <dt>Germany</dt> <dd>The National Institute for Science and Technology maintains the <a -href="https://www.ptb.de/cms/en/fachabteilungen/abt4/fb-44/ag-441/realisation-of-legal-time-in-germany.html">Realisation +href="https://www.ptb.de/cms/en/ptb/fachabteilungen/abt4/fb-44/ag-441/realisation-of-legal-time-in-germany.html">Realisation of Legal Time in Germany</a>.</dd> <dt>Israel</dt> <dd><a @@ -906,8 +903,8 @@ hreflang="nl">Legal time in the Netherlands (in Dutch)</a> covers the history of local time in the Netherlands from ancient times.</dd> <dt>New Zealand</dt> <dd>The Department of Internal Affairs maintains a brief <a -href="https://www.dia.govt.nz/Daylight-Saving-History">History of -Daylight Saving</a>.</dd> +href="https://www.govt.nz/browse/recreation-and-the-environment/daylight-saving/history-of-daylight-saving-in-nz/">History +of Daylight Saving in NZ</a>.</dd> <dt>Palestine</dt> <dd>The Ministry of Telecom and Digital Economy publishes a <a href="https://mtde.gov.ps/home/TimeZone" @@ -969,11 +966,17 @@ subtropical regions consume more electricity because of <abbr>DST</abbr>.”</li <li>Neumann P, von Blanckenburg K. <a href="https://journals.sagepub.com/doi/full/10.1177/0961463X241310562">What time will it be? A comprehensive literature review on daylight saving time</a>. -<em>Time Soc</em>. 2025-01-21. +<em>Time Soc</em>. 2025;34(4):684–745. doi:<a href="https://doi.org/10.1177/0961463X241310562">10.1177/0961463X241310562</a>. -This reviews DST’s effects on electricity, health, crime, road safety, -and the economy, focusing on research since 2010, and concludes that -year-round standard time is preferable overall. +This reviews <abbr>DST</abbr>’s effects on electricity, health, crime, road +safety, and the economy, focusing on research since 2010, and concludes that +year-round standard time is preferable overall.</li> +<li>Romigi A, Franco V, Scoditti E <em>et al</em>. +The effects of daylight saving time and clock time transitions on sleep and +sleepiness: a systematic review. <em>Sleep Med Rev.</em> 2025;84:102161. doi:<a +href="https://doi.org/10.1016/j.smrv.2025.102161">10.1016/j.smrv.2025.102161</a>. +This reviews <abbr>DST</abbr> and <abbr>DST</abbr> transitions, +and concludes that they both harm sleep, health and behavior.</li> </ul> <p>The following medical societies have taken positions on the @@ -981,14 +984,14 @@ advisability of clock shifts:</p> <ul> <li>In 2022 the American Medical Association issued a -<a href="https://www.ama-assn.org/press-center/press-releases/ama-calls-permanent-standard-time">statement +<a href="https://www.ama-assn.org/press-center/ama-press-releases/ama-calls-permanent-standard-time">statement supporting permanent standard time</a> on health grounds.</li> <li>Crawford MR, Winnebeck EC, von Schantz M <em>et al</em>. <a href="https://onlinelibrary.wiley.com/doi/10.1111/jsr.14352">The British Sleep Society position statement on Daylight Saving Time in the UK</a>. <em>J Sleep Res.</em> 2025;34(3):e14352. doi:<a href="https://doi.org/10.1111/jsr.14352">10.1111/jsr.14352</a>. -This recommends that the UK abolish DST for health reasons.</li> +This recommends that the UK abolish <abbr>DST</abbr> for health reasons.</li> <li>Malow BA. <a href="https://academic.oup.com/sleep/article/45/12/zsac236/6717940">It is time to abolish the clock change and adopt permanent @@ -996,7 +999,7 @@ standard time in the United States: a Sleep Research Society position statement</a>. <em>Sleep.</em> 2022;45(12):zsac236. doi:<a href="https://doi.org/10.1093/sleep/zsac236">10.1093/sleep/zsac236</a>. -After reviewing the scientific literature, the Sleep Research Society +The Sleep Research Society advocates permanent standard time due to its health benefits.</li> <li>Rishi MA, Cheng JY, Strang AR <em>et al</em>. <a href="https://jcsm.aasm.org/doi/10.5664/jcsm.10898">Permanent standard time @@ -1037,7 +1040,7 @@ Internet hosts.</li> family of software algorithms can achieve accuracy to a few tens of nanoseconds in scalable server farms without special hardware.</li> <li>The <a -href="https://www.nist.gov/intelligent-systems-division/ieee-1588">Precision +href="https://www.nist.gov/el/intelligent-systems-division-73500/ieee-1588">Precision Time Protocol</a> (<abbr title="Institute of Electrical and Electronics Engineers">IEEE</abbr> 1588) can achieve submicrosecond clock accuracy on a local area network @@ -1065,7 +1068,7 @@ code for converting among time scales like <abbr title="International Atomic Time">TAI</abbr>, <abbr>TDB</abbr>, <abbr>TDT</abbr> and <abbr>UTC</abbr>. It is freely available under the -<a href="https://www.iausofa.org/tandc.html">SOFA license</a>.</li> +<a href="https://www.iausofa.org/terms-and-conditions">SOFA license</a>.</li> <li><a href="https://www.giss.nasa.gov/tools/mars24/help/notes.html">Mars24 Sunclock – Time on Mars</a> describes Airy Mean Time (<abbr>AMT</abbr>) and the @@ -1121,8 +1124,8 @@ leap second so that they disagree with <abbr>UTC</abbr> by at most a half second, even though every <abbr>POSIX</abbr> minute has exactly sixty seconds. This approach works with the default <code><abbr>tz</abbr></code> <code>"posix"</code> configuration, is <a -href="http://bk1.ntp.org/ntp-stable/README.leapsmear">supported</a> by -the abovementioned <abbr>NTP</abbr> implementations, <a +href="https://gitlab.com/NTPsec/ntpsec/-/blob/master/docs/leapsmear.adoc">supported</a> +by the abovementioned <abbr>NTP</abbr> implementations, <a href="https://github.com/google/unsmear">supports</a> conversion between <abbr>UTC</abbr> and smeared <abbr>POSIX</abbr> timestamps, and is used by major cloud service providers. However, according to @@ -1134,7 +1137,7 @@ and is intended for use only in single, well-controlled environments.</li> <li>The <a href="https://pairlist6.pair.net/mailman/listinfo/leapsecs">Leap Second Discussion List</a> covers <a -href="https://www2.unb.ca/gge/Resources/gpsworld.november99.pdf">McCarthy +href="https://gge.ext.unb.ca/Resources/gpsworld.november99.pdf">McCarthy and Klepczynski’s 1999 proposal to discontinue leap seconds</a>, discussed further in <a href="https://www.cl.cam.ac.uk/~mgk25/time/metrologia-leapsecond.pdf">The @@ -1154,7 +1157,7 @@ would replace leap seconds with seven 13-second leap smears occurring once per decade until 2100, with leap smears after that gradually increasing in size. See: <ul> -<li>Levine J. <a href="https://www.preprints.org/manuscript/202406.0043/v1">A +<li>Levine J. <a href="https://www.preprints.org/manuscript/202406.0043">A proposal to change the leap-second adjustments to coordinated universal time</a>. <em>Metrologia.</em> 2024;61(5):055002. doi:<a href="https://doi.org/10.1088/1681-7575/ad6266">10.1088/1681-7575/ad6266</a>.</li> @@ -188,14 +188,15 @@ for another time zone specified via a proleptic TZ string that lacks rules. For example, when TZ="EET\-2EEST" and there is no TZif file "EET\-2EEST", the idea was to adapt the transition times from a TZif file with the -well-known name "posixrules" that is present only for this purpose and -is a copy of the file "Europe/Brussels", a file with a different UT offset. +well-known name "posixrules" that was present only for this purpose and +was a copy of the file "Europe/Brussels", a file with a different UT offset. POSIX does not specify the details of this obsolete transformational behavior, the default rules are installation-dependent, and no implementation is known to support this feature for timestamps past 2037, so users desiring (say) Greek time should instead specify TZ="Europe/Athens" for better historical coverage, falling back on -TZ="EET\-2EEST,M3.5.0/3,M10.5.0/4" if POSIX conformance is required +TZ="EET\-2EEST,M3.5.0/3,M10.5.0/4" +if conformance to POSIX.1-2017 or earlier is required and older timestamps need not be handled accurately. .PP The diff --git a/tzfile.5.txt b/tzfile.5.txt index 78f9eec81657..00c2e62d398d 100644 --- a/tzfile.5.txt +++ b/tzfile.5.txt @@ -131,15 +131,16 @@ DESCRIPTION appropriate for another time zone specified via a proleptic TZ string that lacks rules. For example, when TZ="EET-2EEST" and there is no TZif file "EET-2EEST", the idea was to adapt the transition times from - a TZif file with the well-known name "posixrules" that is present only - for this purpose and is a copy of the file "Europe/Brussels", a file + a TZif file with the well-known name "posixrules" that was present only + for this purpose and was a copy of the file "Europe/Brussels", a file with a different UT offset. POSIX does not specify the details of this obsolete transformational behavior, the default rules are installation- dependent, and no implementation is known to support this feature for timestamps past 2037, so users desiring (say) Greek time should instead specify TZ="Europe/Athens" for better historical coverage, falling back - on TZ="EET-2EEST,M3.5.0/3,M10.5.0/4" if POSIX conformance is required - and older timestamps need not be handled accurately. + on TZ="EET-2EEST,M3.5.0/3,M10.5.0/4" if conformance to POSIX.1-2017 or + earlier is required and older timestamps need not be handled + accurately. The localtime(3) function normally uses the first ttinfo structure in the file if either tzh_timecnt is zero or the time argument is less @@ -17,16 +17,8 @@ ** Thank you! */ -/* -** Information about time zone files. -*/ - -#ifndef TZDEFRULES -# define TZDEFRULES "posixrules" -#endif /* !defined TZDEFRULES */ - - -/* See Internet RFC 9636 for more details about the following format. */ +/* Information about time zone files. + See Internet RFC 9636 for more details about the following format. */ /* ** Each file begins with. . . @@ -97,23 +89,25 @@ struct tzhead { */ #ifndef TZ_MAX_TIMES -/* This must be at least 242 for Europe/London with 'zic -b fat'. */ +/* The following limit applies to localtime.c; zic has no such limit. + The limit must be at least 310 for Asia/Hebron with 'zic -b fat'. */ # define TZ_MAX_TIMES 2000 #endif /* !defined TZ_MAX_TIMES */ #ifndef TZ_MAX_TYPES /* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */ -# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +# define TZ_MAX_TYPES 256 /* Limited to 256 by Internet RFC 9636. */ #endif /* !defined TZ_MAX_TYPES */ #ifndef TZ_MAX_CHARS /* This must be at least 40 for America/Anchorage. */ -# define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ - /* (limited by what unsigned chars can hold) */ +# define TZ_MAX_CHARS 256 /* Maximum number of abbreviation characters */ + /* (limited to 256 by Internet RFC 9636) */ #endif /* !defined TZ_MAX_CHARS */ #ifndef TZ_MAX_LEAPS -/* This must be at least 27 for leap seconds from 1972 through mid-2023. +/* The following limit applies to localtime.c; zic has no such limit. + The limit must be at least 27 for leap seconds from 1972 through mid-2023. There's a plan to discontinue leap seconds by 2035. */ # define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ #endif /* !defined TZ_MAX_LEAPS */ @@ -1 +1 @@ -2025c +2026a @@ -123,7 +123,8 @@ size_overflow(void) /* Return A + B, exiting if the result would overflow either ptrdiff_t or size_t. A and B are both nonnegative. */ -ATTRIBUTE_PURE_114833 static ptrdiff_t +ATTRIBUTE_PURE_114833_HACK +static ptrdiff_t sumsize(ptrdiff_t a, ptrdiff_t b) { #ifdef ckd_add @@ -107,15 +107,11 @@ any already-existing link is removed. .BI "\-L " leapsecondfilename Read leap second information from the file with the given name. If this option is not used, -no leap second information appears in output files. +no leap second information appears in output files; +this is required by some TZif readers. .TP .BI "\-p " timezone -Use -.IR timezone 's -rules when handling nonstandard -TZ strings like "EET\-2EEST" that lack transition rules. -.B zic -will act as if the input contained a link line of the form +Act as if the input contained a link line of the form .sp .ti +2 Link \fItimezone\fP posixrules @@ -129,13 +125,21 @@ is Unless .I timezone is .q "\-" , -this option is obsolete and poorly supported. +this option is obsolete and is no longer supported by most runtimes. Among other things it should not be used for timestamps after the year 2037, and it should not be combined with .B "\-b slim" if .IR timezone 's transitions are at standard time or Universal Time (UT) instead of local time. +The option is present only to support obsolete runtimes that used +.IR timezone 's +rules when handling obsolescent +TZ strings like "AST4ADT" that lack transition rules; +modern runtimes that support these TZ strings +typically just use current US rules +as the TZ strings were mainly used in the US. +Similarly, any Zone or Link named "posixrules" is obsolete and problematic. .TP .BI "\-m " mode Create TZif files with the given file mode bits. diff --git a/zic.8.txt b/zic.8.txt index 2bf66e1aa64d..426978307e88 100644 --- a/zic.8.txt +++ b/zic.8.txt @@ -46,23 +46,27 @@ OPTIONS -L leapsecondfilename Read leap second information from the file with the given name. If this option is not used, no leap second information appears - in output files. + in output files; this is required by some TZif readers. -p timezone - Use timezone's rules when handling nonstandard TZ strings like - "EET-2EEST" that lack transition rules. zic will act as if the - input contained a link line of the form + Act as if the input contained a link line of the form Link timezone posixrules If timezone is “-” (the default), any already-existing link is removed. - Unless timezone is “-”, this option is obsolete and poorly - supported. Among other things it should not be used for - timestamps after the year 2037, and it should not be combined - with -b slim if timezone's transitions are at standard time or - Universal Time (UT) instead of local time. + Unless timezone is “-”, this option is obsolete and is no longer + supported by most runtimes. Among other things it should not be + used for timestamps after the year 2037, and it should not be + combined with -b slim if timezone's transitions are at standard + time or Universal Time (UT) instead of local time. The option + is present only to support obsolete runtimes that used + timezone's rules when handling obsolescent TZ strings like + "AST4ADT" that lack transition rules; modern runtimes that + support these TZ strings typically just use current US rules as + the TZ strings were mainly used in the US. Similarly, any Zone + or Link named "posixrules" is obsolete and problematic. -m mode Create TZif files with the given file mode bits. By default the @@ -31,8 +31,8 @@ typedef int_fast64_t zic_t; static zic_t const ZIC_MIN = INT_FAST64_MIN, ZIC_MAX = INT_FAST64_MAX, - ZIC32_MIN = -1 - (zic_t) 0x7fffffff, - ZIC32_MAX = 0x7fffffff; + ZIC32_MIN = -1 - (zic_t) TWO_31_MINUS_1, + ZIC32_MAX = TWO_31_MINUS_1; #define SCNdZIC SCNdFAST64 #ifndef ZIC_MAX_ABBR_LEN_WO_WARN @@ -164,6 +164,11 @@ static uid_t output_owner = -1; # include <stdalign.h> #endif +/* The name used for the file implementing the obsolete -p option. */ +#ifndef TZDEFRULES +# define TZDEFRULES "posixrules" +#endif + /* The maximum length of a text line, including the trailing newline. */ #ifndef _POSIX2_LINE_MAX # define _POSIX2_LINE_MAX 2048 @@ -271,6 +276,7 @@ static char lowerit(char); static void mkdirs(char const *, bool); static void newabbr(const char * abbr); static zic_t oadd(zic_t t1, zic_t t2); +static zic_t omul(zic_t, zic_t); static void outzone(const struct zone * zp, ptrdiff_t ntzones); static zic_t rpytime(const struct rule * rp, zic_t wantedy); static bool rulesub(struct rule * rp, @@ -293,7 +299,8 @@ static int charcnt; static bool errors; static bool warnings; static int filenum; -static int leapcnt; +static ptrdiff_t leapcnt; +static ptrdiff_t leap_alloc; static bool leapseen; static zic_t leapminyear; static zic_t leapmaxyear; @@ -534,9 +541,11 @@ static unsigned char desigidx[TZ_MAX_TYPES]; static bool ttisstds[TZ_MAX_TYPES]; static bool ttisuts[TZ_MAX_TYPES]; static char chars[TZ_MAX_CHARS]; -static zic_t trans[TZ_MAX_LEAPS]; -static zic_t corr[TZ_MAX_LEAPS]; -static char roll[TZ_MAX_LEAPS]; +static struct { + zic_t trans; + zic_t corr; + char roll; +} *leap; /* ** Memory allocation. @@ -555,7 +564,8 @@ size_overflow(void) memory_exhausted(_("size overflow")); } -ATTRIBUTE_PURE_114833 static ptrdiff_t +ATTRIBUTE_PURE_114833_HACK +static ptrdiff_t size_sum(size_t a, size_t b) { #ifdef ckd_add @@ -569,7 +579,8 @@ size_sum(size_t a, size_t b) size_overflow(); } -ATTRIBUTE_PURE_114833 static ptrdiff_t +ATTRIBUTE_PURE_114833_HACK +static ptrdiff_t size_product(ptrdiff_t nitems, ptrdiff_t itemsize) { #ifdef ckd_mul @@ -584,7 +595,8 @@ size_product(ptrdiff_t nitems, ptrdiff_t itemsize) size_overflow(); } -ATTRIBUTE_PURE_114833 static ptrdiff_t +ATTRIBUTE_PURE_114833_HACK +static ptrdiff_t align_to(ptrdiff_t size, ptrdiff_t alignment) { ptrdiff_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits); @@ -814,8 +826,8 @@ close_file(FILE *stream, char const *dir, char const *name, char const *tempname) { char const *e = (ferror(stream) ? _("I/O error") - : (fflush(stream) < 0 - || (tempname && chmetadata(stream) < 0) + : ((tempname + && (fflush(stream) < 0 || chmetadata(stream) < 0)) || fclose(stream) < 0) ? strerror(errno) : NULL); if (e) { @@ -1283,6 +1295,9 @@ main(int argc, char **argv) case 'p': if (psxrules) duplicate_options("-p"); + if (strcmp(optarg, "-") != 0) + warning(_("-p is obsolete" + " and likely ineffective")); psxrules = optarg; break; case 't': @@ -1691,7 +1706,8 @@ relname(char const *target, char const *linkname) /* Return true if A and B must have the same parent dir if A and B exist. Return false if this is not necessarily true (though it might be true). Keep it simple, and do not inspect the file system. */ -ATTRIBUTE_PURE_114833 static bool +ATTRIBUTE_PURE_114833 +static bool same_parent_dirs(char const *a, char const *b) { for (; *a == *b; a++, b++) @@ -2073,15 +2089,11 @@ gethms(char const *string, char const *errstring) error("%s", errstring); return 0; } - if (ZIC_MAX / SECSPERHOUR < hh) { - error(_("time overflow")); - return 0; - } ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths; /* Round to even. */ if (noise && (hh > HOURSPERDAY || (hh == HOURSPERDAY && (mm != 0 || ss != 0)))) warning(_("values over 24 hours not handled by pre-2007 versions of zic")); - return oadd(sign * hh * SECSPERHOUR, + return oadd(omul(hh, sign * SECSPERHOUR), sign * (mm * SECSPERMIN + ss)); } @@ -2241,10 +2253,6 @@ inzsub(char **fields, int nfields, bool iscont) z.z_untiltime = rpytime(&z.z_untilrule, z.z_untilrule.r_loyear); if (iscont && nzones > 0 && - z.z_untiltime > min_time && - z.z_untiltime < max_time && - zones[nzones - 1].z_untiltime > min_time && - zones[nzones - 1].z_untiltime < max_time && zones[nzones - 1].z_untiltime >= z.z_untiltime) { error(_("Zone continuation line end time is" " not after end time of previous line")); @@ -2326,15 +2334,7 @@ getleapdatetime(char **fields, bool expire_line) return -1; } dayoff = oadd(dayoff, day - 1); - if (dayoff < min_time / SECSPERDAY) { - error(_("time too small")); - return -1; - } - if (dayoff > max_time / SECSPERDAY) { - error(_("time too large")); - return -1; - } - t = dayoff * SECSPERDAY; + t = omul(dayoff, SECSPERDAY); tod = gethms(fields[LP_TIME], _("invalid time of day")); t = tadd(t, tod); if (t < 0) @@ -2594,7 +2594,7 @@ atcomp(const void *avp, const void *bvp) struct timerange { int defaulttype; ptrdiff_t base, count; - int leapbase, leapcount; + ptrdiff_t leapbase, leapcount; bool leapexpiry; }; @@ -2614,13 +2614,13 @@ limitrange(struct timerange r, zic_t lo, zic_t hi, positive leap second if and only if it has a positive correction. This supports common TZif readers that assume that the first leap second is positive if and only if its correction is positive. */ - while (1 < r.leapcount && trans[r.leapbase + 1] <= lo) { + while (1 < r.leapcount && leap[r.leapbase + 1].trans <= lo) { r.leapcount--; r.leapbase++; } while (0 < r.leapbase - && ((corr[r.leapbase - 1] < corr[r.leapbase]) - != (0 < corr[r.leapbase]))) { + && ((leap[r.leapbase - 1].corr < leap[r.leapbase].corr) + != (0 < leap[r.leapbase].corr))) { r.leapcount++; r.leapbase--; } @@ -2630,7 +2630,7 @@ limitrange(struct timerange r, zic_t lo, zic_t hi, if (hi < max_time) { while (0 < r.count && hi + 1 < ats[r.base + r.count - 1]) r.count--; - while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1]) + while (0 < r.leapcount && hi + 1 < leap[r.leapbase + r.leapcount - 1].trans) r.leapcount--; } @@ -2666,7 +2666,7 @@ writezone(const char *const name, const char *const string, char version, if (timecnt > 1) qsort(attypes, timecnt, sizeof *attypes, atcomp); /* - ** Optimize. + ** Optimize and skip unwanted transitions. */ { ptrdiff_t fromi, toi; @@ -2674,16 +2674,28 @@ writezone(const char *const name, const char *const string, char version, toi = 0; fromi = 0; for ( ; fromi < timecnt; ++fromi) { - if (toi != 0 - && ((attypes[fromi].at + if (toi != 0) { + /* Skip the previous transition if it is unwanted + because its local time is not earlier. + The UT offset additions can't overflow because + of how the times were calculated. */ + unsigned char type_2 = + toi == 1 ? 0 : attypes[toi - 2].type; + if ((attypes[fromi].at + utoffs[attypes[toi - 1].type]) - <= (attypes[toi - 1].at - + utoffs[toi == 1 ? 0 - : attypes[toi - 2].type]))) { + <= attypes[toi - 1].at + utoffs[type_2]) { + if (attypes[fromi].type == type_2) + toi--; + else attypes[toi - 1].type = attypes[fromi].type; - continue; + continue; + } } + + /* Use a transition if it is the first one, + or if it cannot be merged for other reasons, + or if it transitions to different timekeeping. */ if (toi == 0 || attypes[fromi].dontmerge || (utoffs[attypes[toi - 1].type] @@ -2697,14 +2709,19 @@ writezone(const char *const name, const char *const string, char version, timecnt = toi; } - if (noise && timecnt > 1200) { - if (timecnt > TZ_MAX_TIMES) + if (noise) { + if (1200 < timecnt) { + if (TZ_MAX_TIMES < timecnt) warning(_("reference clients mishandle" " more than %d transition times"), TZ_MAX_TIMES); - else + else warning(_("pre-2014 clients may mishandle" " more than 1200 transition times")); + } + if (TZ_MAX_LEAPS < leapcnt) + warning(_("reference clients mishandle more than %d leap seconds"), + TZ_MAX_LEAPS); } /* ** Transfer. @@ -2720,8 +2737,8 @@ writezone(const char *const name, const char *const string, char version, for (i = 0; i < timecnt; ++i) { j = leapcnt; while (--j >= 0) - if (ats[i] > trans[j] - corr[j]) { - ats[i] = tadd(ats[i], corr[j]); + if (leap[j].trans - leap[j].corr < ats[i]) { + ats[i] = tadd(ats[i], leap[j].corr); break; } } @@ -2750,7 +2767,7 @@ writezone(const char *const name, const char *const string, char version, version = '4'; } if (0 < r->leapcount - && corr[r->leapbase] != 1 && corr[r->leapbase] != -1) { + && leap[r->leapbase].corr != 1 && leap[r->leapbase].corr != -1) { if (noise) warning(_("%s: pre-2021b clients may mishandle" " leap second table truncation"), @@ -2765,7 +2782,7 @@ writezone(const char *const name, const char *const string, char version, for (pass = 1; pass <= 2; ++pass) { register ptrdiff_t thistimei, thistimecnt, thistimelim; - register int thisleapi, thisleapcnt, thisleaplim; + register ptrdiff_t thisleapi, thisleapcnt, thisleaplim; struct tzhead tzh; int pretranstype = -1, thisdefaulttype; bool locut, hicut, thisleapexpiry; @@ -2970,6 +2987,11 @@ writezone(const char *const name, const char *const string, char version, continue; } + if (pass == 2 && noise && 50 < thischarcnt) + warning(_("%s: pre-2026 reference clients mishandle" + " more than 50 bytes of abbreviations"), + name); + /* Output a LO_TIME transition if needed; see limitrange. But do not go below the minimum representable value for this pass. */ @@ -3005,8 +3027,8 @@ writezone(const char *const name, const char *const string, char version, for (i = thisleapi; i < thisleaplim; ++i) { register zic_t todo; - if (roll[i]) { - if (timecnt == 0 || trans[i] < ats[0]) { + if (leap[i].roll) { + if (timecnt == 0 || leap[i].trans < ats[0]) { j = 0; while (isdsts[j]) if (++j >= typecnt) { @@ -3016,14 +3038,14 @@ writezone(const char *const name, const char *const string, char version, } else { j = 1; while (j < timecnt && - trans[i] >= ats[j]) + ats[j] <= leap[i].trans) ++j; j = types[j - 1]; } - todo = tadd(trans[i], -utoffs[j]); - } else todo = trans[i]; + todo = tadd(leap[i].trans, -utoffs[j]); + } else todo = leap[i].trans; puttzcodepass(todo, fp, pass); - puttzcode(corr[i], fp); + puttzcode(leap[i].corr, fp); } if (thisleapexpiry) { /* Append a no-op leap correction indicating when the leap @@ -3032,7 +3054,7 @@ writezone(const char *const name, const char *const string, char version, the plan is to amend the RFC to allow this in version 4 TZif files. */ puttzcodepass(leapexpires, fp, pass); - puttzcode(thisleaplim ? corr[thisleaplim - 1] : 0, fp); + puttzcode(thisleaplim ? leap[thisleaplim - 1].corr : 0, fp); } if (stdcnt != 0) for (i = old0; i < typecnt; i++) @@ -3092,11 +3114,10 @@ doabbr(char *abbr, struct zone const *zp, char const *letters, bool isdst, zic_t save, bool doquotes) { register char * cp; - register char * slashp; ptrdiff_t len; char const *format = zp->z_format; + char const *slashp = strchr(format, '/'); - slashp = strchr(format, '/'); if (slashp == NULL) { char letterbuf[PERCENT_Z_LEN_BOUND + 1]; if (zp->z_format_specifier == 'z') @@ -3570,9 +3591,6 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount) if (!r->r_todisstd) offset = oadd(offset, save); jtime = r->r_temp; - if (jtime == min_time || - jtime == max_time) - continue; jtime = tadd(jtime, -offset); if (k < 0 || jtime < ktime) { k = j; @@ -3752,7 +3770,8 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut) { register int i, j; - if (! (-1L - 2147483647L <= utoff && utoff <= 2147483647L)) { + /* RFC 9636 section 3.2 specifies this range for utoff. */ + if (! (-TWO_31_MINUS_1 <= utoff && utoff <= TWO_31_MINUS_1)) { error(_("UT offset out of range")); exit(EXIT_FAILURE); } @@ -3791,32 +3810,27 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut) static void leapadd(zic_t t, int correction, int rolling) { - register int i; + register ptrdiff_t i; - if (TZ_MAX_LEAPS <= leapcnt) { - error(_("too many leap seconds")); - exit(EXIT_FAILURE); - } if (rolling && (lo_time != min_time || hi_time != max_time)) { error(_("Rolling leap seconds not supported with -r")); exit(EXIT_FAILURE); } + leap = growalloc(leap, sizeof *leap, leapcnt, &leap_alloc); for (i = 0; i < leapcnt; ++i) - if (t <= trans[i]) + if (t <= leap[i].trans) break; - memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans); - memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr); - memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll); - trans[i] = t; - corr[i] = correction; - roll[i] = rolling; + memmove(&leap[i + 1], &leap[i], (leapcnt - i) * sizeof *leap); + leap[i].trans = t; + leap[i].corr = correction; + leap[i].roll = rolling; ++leapcnt; } static void adjleap(void) { - register int i; + register ptrdiff_t i; register zic_t last = 0; register zic_t prevtrans = 0; @@ -3824,18 +3838,18 @@ adjleap(void) ** propagate leap seconds forward */ for (i = 0; i < leapcnt; ++i) { - if (trans[i] - prevtrans < 28 * SECSPERDAY) { + if (leap[i].trans - prevtrans < 28 * SECSPERDAY) { error(_("Leap seconds too close together")); exit(EXIT_FAILURE); } - prevtrans = trans[i]; - trans[i] = tadd(trans[i], last); - last = corr[i] += last; + prevtrans = leap[i].trans; + leap[i].trans = tadd(prevtrans, last); + last = leap[i].corr += last; } if (0 <= leapexpires) { leapexpires = oadd(leapexpires, last); - if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) { + if (! (leapcnt == 0 || (leap[leapcnt - 1].trans < leapexpires))) { error(_("last Leap time does not precede Expires time")); exit(EXIT_FAILURE); } @@ -3893,7 +3907,8 @@ lowerit(char a) } /* case-insensitive equality */ -ATTRIBUTE_PURE_114833 static bool +ATTRIBUTE_PURE_114833 +static bool ciequal(register const char *ap, register const char *bp) { while (lowerit(*ap) == lowerit(*bp++)) @@ -3902,7 +3917,8 @@ ciequal(register const char *ap, register const char *bp) return false; } -ATTRIBUTE_PURE_114833 static bool +ATTRIBUTE_PURE_114833 +static bool itsabbr(register const char *abbr, register const char *word) { if (lowerit(*abbr) != lowerit(*word)) @@ -3918,7 +3934,8 @@ itsabbr(register const char *abbr, register const char *word) /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case. */ -ATTRIBUTE_PURE_114833 static bool +ATTRIBUTE_PURE_114833 +static bool ciprefix(char const *abbr, char const *word) { do @@ -4028,7 +4045,9 @@ time_overflow(void) exit(EXIT_FAILURE); } -ATTRIBUTE_PURE_114833 static zic_t +/* Return T1 + T2, but diagnose any overflow and exit. */ +ATTRIBUTE_PURE_114833_HACK +static zic_t oadd(zic_t t1, zic_t t2) { #ifdef ckd_add @@ -4042,25 +4061,41 @@ oadd(zic_t t1, zic_t t2) time_overflow(); } -ATTRIBUTE_PURE_114833 static zic_t +/* Return T1 + T2, but diagnose any overflow and exit. + This is like oadd, except the result must fit in min_time..max_time range, + which on oddball machines can be a smaller range than ZIC_MIN..ZIC_MAX. */ +ATTRIBUTE_PURE_114833_HACK +static zic_t tadd(zic_t t1, zic_t t2) { -#ifdef ckd_add - zic_t sum; - if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time) + zic_t sum = oadd(t1, t2); + if (min_time <= sum && sum <= max_time) return sum; + time_overflow(); +} + +/* Return T1 * T2, but diagnose any overflow and exit. */ +ATTRIBUTE_PURE_114833_HACK +static zic_t +omul(zic_t t1, zic_t t2) +{ +#ifdef ckd_mul + zic_t product; + if (!ckd_mul(&product, t1, t2)) + return product; #else - if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1) - return t1 + t2; + if (t2 < 0 + ? ZIC_MAX / t2 <= t1 && (t2 == -1 || t1 <= ZIC_MIN / t2) + : t2 == 0 || (ZIC_MIN / t2 <= t1 && t1 <= ZIC_MAX / t2)) + return t1 * t2; #endif - if (t1 == min_time || t1 == max_time) - return t1; time_overflow(); } /* ** Given a rule, and a year, compute the date (in seconds since January 1, ** 1970, 00:00 LOCAL time) in that year that the rule refers to. +** Do not count leap seconds. On error, diagnose and exit. */ static zic_t @@ -4071,10 +4106,6 @@ rpytime(const struct rule *rp, zic_t wantedy) register zic_t t, y; int yrem; - if (wantedy == ZIC_MIN) - return min_time; - if (wantedy == ZIC_MAX) - return max_time; m = TM_JANUARY; y = EPOCH_YEAR; @@ -4132,11 +4163,7 @@ rpytime(const struct rule *rp, zic_t wantedy) will not work with pre-2004 versions of zic")); } } - if (dayoff < min_time / SECSPERDAY) - return min_time; - if (dayoff > max_time / SECSPERDAY) - return max_time; - t = (zic_t) dayoff * SECSPERDAY; + t = omul(dayoff, SECSPERDAY); return tadd(t, rp->r_tod); } |
