Module Name: src Committed By: christos Date: Thu Jan 23 22:44:22 UTC 2025
Modified Files: src/lib/libc/time: Makefile NEWS asctime.c ctime.3 localtime.c private.h strftime.3 strftime.c theory.html tz-link.html tzfile.5 tzselect.8 version zdump.8 zdump.c zic.8 zic.c Log Message: update to 2025a Changes to code strftime %s now generates the correct numeric string even when the represented number does not fit into time_t. This is better than generating the numeric equivalent of (time_t) -1, as strftime did in TZDB releases 96a (when %s was introduced) through 2020a and in releases 2022b through 2024b. It is also better than failing and returning 0, as strftime did in releases 2020b through 2022a. strftime now outputs an invalid conversion specifier as-is, instead of eliding the leading '%', which confused debugging. An invalid TZ now generates the time zone abbreviation "-00", not "UTC", to help the user see that an error has occurred. (Thanks to Arthur David Olson for suggesting a "wrong result".) mktime and timeoff no longer incorrectly fail merely because a struct tm component near INT_MIN or INT_MAX overflows when a lower-order component carries into it. TZNAME_MAXIMUM, the maximum number of bytes in a proleptic TZ string's time zone abbreviation, now defaults to 254 not 255. This helps reduce the size of internal state from 25480 to 21384 on common platforms. This change should not be a problem, as nobody uses such long "abbreviations" and the longstanding tzcode maximum was 16 until release 2023a. For those who prefer no arbitrary limits, you can now specify TZNAME_MAXIMUM values up to PTRDIFF_MAX, a limit forced by C anyway; formerly tzcode silently misbehaved unless TZNAME_MAXIMUM was less than INT_MAX. tzset and related functions no longer leak a file descriptor if another thread forks or execs at about the same time and if the platform has O_CLOFORK and O_CLOEXEC respectively. Also, the functions no longer let a TZif file become a controlling terminal. 'zdump -' now reads TZif data from /dev/stdin. (From a question by Arthur David Olson.) To generate a diff of this commit: cvs rdiff -u -r1.58 -r1.59 src/lib/libc/time/Makefile cvs rdiff -u -r1.45 -r1.46 src/lib/libc/time/NEWS cvs rdiff -u -r1.31 -r1.32 src/lib/libc/time/asctime.c cvs rdiff -u -r1.71 -r1.72 src/lib/libc/time/ctime.3 cvs rdiff -u -r1.144 -r1.145 src/lib/libc/time/localtime.c cvs rdiff -u -r1.70 -r1.71 src/lib/libc/time/private.h cvs rdiff -u -r1.37 -r1.38 src/lib/libc/time/strftime.3 cvs rdiff -u -r1.56 -r1.57 src/lib/libc/time/strftime.c cvs rdiff -u -r1.20 -r1.21 src/lib/libc/time/theory.html cvs rdiff -u -r1.17 -r1.18 src/lib/libc/time/tz-link.html cvs rdiff -u -r1.36 -r1.37 src/lib/libc/time/tzfile.5 cvs rdiff -u -r1.14 -r1.15 src/lib/libc/time/tzselect.8 cvs rdiff -u -r1.26 -r1.27 src/lib/libc/time/version cvs rdiff -u -r1.25 -r1.26 src/lib/libc/time/zdump.8 cvs rdiff -u -r1.64 -r1.65 src/lib/libc/time/zdump.c cvs rdiff -u -r1.51 -r1.52 src/lib/libc/time/zic.8 cvs rdiff -u -r1.93 -r1.94 src/lib/libc/time/zic.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/lib/libc/time/Makefile diff -u src/lib/libc/time/Makefile:1.58 src/lib/libc/time/Makefile:1.59 --- src/lib/libc/time/Makefile:1.58 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/Makefile Thu Jan 23 17:44:22 2025 @@ -137,7 +137,7 @@ TIME_T_ALTERNATIVES_TAIL = int_least32_t uint_least64_t.ck # What kind of TZif data files to generate. (TZif is the binary time -# zone data format that zic generates; see Internet RFC 8536.) +# zone data format that zic generates; see Internet RFC 9636.) # If you want only POSIX time, with time values interpreted as # seconds since the epoch (not counting leap seconds), use # REDO= posix_only @@ -255,6 +255,7 @@ LDLIBS= # -DHAVE_UNISTD_H=0 if <unistd.h> does not work* # -DHAVE_UTMPX_H=0 if <utmpx.h> does not work* # -Dlocale_t=XXX if your system uses XXX instead of locale_t +# -DMKTIME_MIGHT_OVERFLOW if mktime might fail due to time_t overflow # -DPORT_TO_C89 if tzcode should also run on mostly-C89 platforms+ # Typically it is better to use a later standard. For example, # with GCC 4.9.4 (2016), prefer '-std=gnu11' to '-DPORT_TO_C89'. @@ -262,7 +263,7 @@ LDLIBS= # feature (integers at least 64 bits wide) and maybe more. # -DRESERVE_STD_EXT_IDS if your platform reserves standard identifiers # with external linkage, e.g., applications cannot define 'localtime'. -# -Dssize_t=long on hosts like MS-Windows that lack ssize_t +# -Dssize_t=int on hosts like MS-Windows that lack ssize_t # -DSUPPORT_C89=0 if the tzcode library should not support C89 callers # Although -DSUPPORT_C89=0 might work around latent bugs in callers, # it does not conform to POSIX. @@ -285,7 +286,7 @@ LDLIBS= # 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. -# -DTZNAME_MAXIMUM=N to limit time zone abbreviations to N bytes (default 255) +# -DTZNAME_MAXIMUM=N to limit time zone abbreviations to N bytes (default 254) # -DUNINIT_TRAP if reading uninitialized storage can cause problems # other than simply getting garbage data # -DUSE_LTZ=0 to build zdump with the system time zone library @@ -319,7 +320,8 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 \ $(GCC_INSTRUMENT) \ -Wall -Wextra \ -Walloc-size-larger-than=100000 -Warray-bounds=2 \ - -Wbad-function-cast -Wbidi-chars=any,ucn -Wcast-align=strict -Wdate-time \ + -Wbad-function-cast -Wbidi-chars=any,ucn -Wcast-align=strict -Wcast-qual \ + -Wdate-time \ -Wdeclaration-after-statement -Wdouble-promotion \ -Wduplicated-branches -Wduplicated-cond -Wflex-array-member-not-at-end \ -Wformat=2 -Wformat-overflow=2 -Wformat-signedness -Wformat-truncation \ @@ -336,7 +338,7 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 \ -Wsuggest-attribute=noreturn -Wsuggest-attribute=pure \ -Wtrampolines -Wundef -Wunused-macros -Wuse-after-free=3 \ -Wvariadic-macros -Wvla -Wwrite-strings \ - -Wno-format-nonliteral -Wno-sign-compare + -Wno-format-nonliteral -Wno-sign-compare -Wno-type-limits # # If your system has a "GMT offset" field in its "struct tm"s # (or if you decide to add such a field in your system's "time.h" file), @@ -614,8 +616,8 @@ TZS_YEAR= 2050 TZS_CUTOFF_FLAG= -c $(TZS_YEAR) TZS= to$(TZS_YEAR).tzs TZS_NEW= to$(TZS_YEAR)new.tzs -TZS_DEPS= $(YDATA) asctime.c localtime.c \ - private.h tzfile.h zdump.c zic.c +TZS_DEPS= $(YDATA) localtime.c private.h \ + strftime.c tzfile.h zdump.c zic.c TZDATA_DIST = $(COMMON) $(DATA) $(MISC) # EIGHT_YARDS is just a yard short of the whole ENCHILADA. EIGHT_YARDS = $(TZDATA_DIST) $(DOCS) $(SOURCES) tzdata.zi @@ -855,10 +857,10 @@ tzselect: tzselect.ksh version chmod +x $@.out mv $@.out $@ -check: check_mild back.ck +check: check_mild back.ck now.ck check_mild: check_web check_zishrink \ character-set.ck white-space.ck links.ck mainguard.ck \ - name-lengths.ck now.ck slashed-abbrs.ck sorted.ck \ + name-lengths.ck slashed-abbrs.ck sorted.ck \ tables.ck ziguard.ck tzs.ck # True if UTF8_LOCALE does not work; @@ -1103,7 +1105,7 @@ set-timestamps.out: $(EIGHT_YARDS) touch -md @1 test.out; then \ rm -f test.out && \ for file in $$files; do \ - if git diff --quiet $$file; then \ + if git diff --quiet HEAD $$file; then \ time=$$(TZ=UTC0 git log -1 \ --format='tformat:%cd' \ --date='format:%Y-%m-%dT%H:%M:%SZ' \ @@ -1354,13 +1356,13 @@ long-long.ck unsigned.ck: $(VERSION_DEPS zonenames: tzdata.zi @$(AWK) '/^Z/ { print $$2 } /^L/ { print $$3 }' tzdata.zi -asctime.o: private.h tzfile.h +asctime.o: private.h date.o: private.h difftime.o: private.h -localtime.o: private.h tzfile.h tzdir.h -strftime.o: private.h tzfile.h -zdump.o: version.h -zic.o: private.h tzfile.h tzdir.h version.h +localtime.o: private.h tzdir.h tzfile.h +strftime.o: localtime.c private.h tzdir.h tzfile.h +zdump.o: private.h version.h +zic.o: private.h tzdir.h tzfile.h version.h .PHONY: ALL INSTALL all .PHONY: check check_mild check_time_t_alternatives Index: src/lib/libc/time/NEWS diff -u src/lib/libc/time/NEWS:1.45 src/lib/libc/time/NEWS:1.46 --- src/lib/libc/time/NEWS:1.45 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/NEWS Thu Jan 23 17:44:22 2025 @@ -1,5 +1,83 @@ News for the tz database +Release 2025a - 2025-01-15 10:47:24 -0800 + + Briefly: + Paraguay adopts permanent -03 starting spring 2024. + Improve pre-1991 data for the Philippines. + Etc/Unknown is now reserved. + + Changes to future timestamps + + Paraguay will stop changing its clocks after the spring-forward + transition on 2024-10-06, so it is now permanently at -03. + (Thanks to Heitor David Pinto and Even Scharning.) + This affects timestamps starting 2025-03-22, as well as the + obsolescent tm_isdst flags starting 2024-10-15. + + Changes to past timestamps + + Correct timestamps for the Philippines before 1900, and from 1937 + through 1990. (Thanks to P Chan for the heads-up and citations.) + This includes adjusting local mean time before 1899; fixing + transitions in September 1899, January 1937, and June 1954; adding + transitions in December 1941, November 1945, March and September + 1977, and May and July 1990; and removing incorrect transitions in + March and September 1978. + + Changes to data + + Add zone1970.tab lines for the Concordia and Eyre Bird Observatory + research stations. (Thanks to Derick Rethans and Jule Dabars.) + + Changes to code + + strftime %s now generates the correct numeric string even when the + represented number does not fit into time_t. This is better than + generating the numeric equivalent of (time_t) -1, as strftime did + in TZDB releases 96a (when %s was introduced) through 2020a and in + releases 2022b through 2024b. It is also better than failing and + returning 0, as strftime did in releases 2020b through 2022a. + + strftime now outputs an invalid conversion specifier as-is, + instead of eliding the leading '%', which confused debugging. + + An invalid TZ now generates the time zone abbreviation "-00", not + "UTC", to help the user see that an error has occurred. (Thanks + to Arthur David Olson for suggesting a "wrong result".) + + mktime and timeoff no longer incorrectly fail merely because a + struct tm component near INT_MIN or INT_MAX overflows when a + lower-order component carries into it. + + TZNAME_MAXIMUM, the maximum number of bytes in a proleptic TZ + string's time zone abbreviation, now defaults to 254 not 255. + This helps reduce the size of internal state from 25480 to 21384 + on common platforms. This change should not be a problem, as + nobody uses such long "abbreviations" and the longstanding tzcode + maximum was 16 until release 2023a. For those who prefer no + arbitrary limits, you can now specify TZNAME_MAXIMUM values up to + PTRDIFF_MAX, a limit forced by C anyway; formerly tzcode silently + misbehaved unless TZNAME_MAXIMUM was less than INT_MAX. + + tzset and related functions no longer leak a file descriptor if + another thread forks or execs at about the same time and if the + platform has O_CLOFORK and O_CLOEXEC respectively. Also, the + functions no longer let a TZif file become a controlling terminal. + + 'zdump -' now reads TZif data from /dev/stdin. + (From a question by Arthur David Olson.) + + Changes to documentation + + The name Etc/Unknown is now reserved: it will not be used by TZDB. + This is for compatibility with CLDR, which uses the string + "Etc/Unknown" for an unknown or invalid timezone. (Thanks to + Justin Grant, Mark Davis, and Guy Harris.) + + Cite Internet RFC 9636, which obsoletes RFC 8536 for TZif format. + + Release 2024b - 2024-09-04 12:27:47 -0700 Briefly: @@ -116,7 +194,7 @@ Release 2024b - 2024-09-04 12:27:47 -070 Changes to commentary Commentary about historical transitions in Portugal and her former - colonies has been expanded with links to many relevant legislation. + colonies has been expanded with links to relevant legislation. (Thanks to Tim Parenti.) @@ -204,10 +282,10 @@ Release 2023d - 2023-12-21 20:02:24 -080 changing its time zone from -01/+00 to -02/-01 at the same moment as the spring-forward transition. Its clocks will therefore not spring forward as previously scheduled. The time zone change - reverts to its common practice before 1981. + reverts to its common practice before 1981. (Thanks to Jule Dabars.) Fix predictions for DST transitions in Palestine in 2072-2075, - correcting a typo introduced in 2023a. + correcting a typo introduced in 2023a. (Thanks to Jule Dabars.) Changes to past and future timestamps Index: src/lib/libc/time/asctime.c diff -u src/lib/libc/time/asctime.c:1.31 src/lib/libc/time/asctime.c:1.32 --- src/lib/libc/time/asctime.c:1.31 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/asctime.c Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: asctime.c,v 1.31 2024/09/11 13:50:34 christos Exp $ */ +/* $NetBSD: asctime.c,v 1.32 2025/01/23 22:44:22 christos Exp $ */ /* asctime a la ISO C. */ @@ -9,6 +9,7 @@ /* ** Avoid the temptation to punt entirely to strftime; +** strftime can behave badly when tm components are out of range, and ** the output of strftime is supposed to be locale specific ** whereas the output of asctime is supposed to be constant. */ @@ -18,7 +19,7 @@ #if 0 static char elsieid[] = "@(#)asctime.c 8.5"; #else -__RCSID("$NetBSD: asctime.c,v 1.31 2024/09/11 13:50:34 christos Exp $"); +__RCSID("$NetBSD: asctime.c,v 1.32 2025/01/23 22:44:22 christos Exp $"); #endif #endif /* LIBC_SCCS and not lint */ @@ -28,38 +29,8 @@ __RCSID("$NetBSD: asctime.c,v 1.31 2024/ #include "private.h" #include <stdio.h> -#ifndef __LIBC12_SOURCE__ - -#ifdef __weak_alias -__weak_alias(asctime_r,_asctime_r) -#endif - -/* -** All years associated with 32-bit time_t values are exactly four digits long; -** some years associated with 64-bit time_t values are not. -** Vintage programs are coded for years that are always four digits long -** and may assume that the newline always lands in the same place. -** For years that are less than four digits, we pad the output with -** leading zeroes to get the newline in the traditional place. -** The -4 ensures that we get four characters of output even if -** we call a strftime variant that produces fewer characters for some years. -** This conforms to recent ISO C and POSIX standards, which say behavior -** is undefined when the year is less than 1000 or greater than 9999. -*/ -static char const ASCTIME_FMT[] = "%s %s%3d %.2d:%.2d:%.2d %-4s\n"; -/* -** For years that are more than four digits we put extra spaces before the year -** so that code trying to overwrite the newline won't end up overwriting -** a digit within a year and truncating the year (operating on the assumption -** that no output is better than wrong output). -*/ -static char const ASCTIME_FMT_B[] = "%s %s%3d %.2d:%.2d:%.2d %s\n"; - -enum { STD_ASCTIME_BUF_SIZE = 26 }; -#endif - /* Publish asctime_r and ctime_r only when supporting older POSIX. */ -#if SUPPORT_POSIX2008 +#if SUPPORT_POSIX2008 || defined(__NetBSD__) # define asctime_static #else # define asctime_static static @@ -69,6 +40,15 @@ enum { STD_ASCTIME_BUF_SIZE = 26 }; # define ctime_r static_ctime_r #endif +#ifndef __LIBC12_SOURCE__ + +#ifdef __weak_alias +__weak_alias(asctime_r,_asctime_r) +#endif + +enum { STD_ASCTIME_BUF_SIZE = 26 }; + + /* ** Big enough for something such as ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n @@ -79,20 +59,28 @@ enum { STD_ASCTIME_BUF_SIZE = 26 }; ** as an example; the size expression below is a bound for the system at ** hand. */ -static char buf_ctime[2*3 + 5*INT_STRLEN_MAXIMUM(int) + 7 + 2 + 1 + 1]; +static char buf_asctime[2*3 + 5*INT_STRLEN_MAXIMUM(int) + 7 + 2 + 1 + 1]; -#ifndef __LIBC12_SOURCE__ -/* A similar buffer for ctime. - C89 requires that they be the same buffer. - This requirement was removed in C99, so support it only if requested, - as support is more likely to lead to bugs in badly written programs. */ -#if SUPPORT_C89 -# define buf_asctime buf_ctime -#else -static char buf_asctime[sizeof buf_ctime]; +/* On pre-C99 platforms, a snprintf substitute good enough for us. */ +#if !HAVE_SNPRINTF +# include <stdarg.h> +ATTRIBUTE_FORMAT((printf, 3, 4)) static int +my_snprintf(char *s, size_t size, char const *format, ...) +{ + int n; + va_list args; + char stackbuf[sizeof buf_asctime]; + va_start(args, format); + n = vsprintf(stackbuf, format, args); + va_end (args); + if (0 <= n && n < size) + memcpy (s, stackbuf, n + 1); + return n; +} +# undef snprintf +# define snprintf my_snprintf #endif - asctime_static char * asctime_r(struct tm const *restrict timeptr, char *restrict buf) @@ -106,12 +94,17 @@ asctime_r(struct tm const *restrict time }; const char * wn; const char * mn; - char year[INT_STRLEN_MAXIMUM(int) + 2]; - char result[sizeof buf_ctime]; + int year, mday, hour, min, sec; + long long_TM_YEAR_BASE = TM_YEAR_BASE; + size_t bufsize = (buf == buf_asctime + ? sizeof buf_asctime : STD_ASCTIME_BUF_SIZE); if (timeptr == NULL) { + strcpy(buf, "??? ??? ?? ??:??:?? ????\n"); + /* Set errno now, since strcpy might change it in + POSIX.1-2017 and earlier. */ errno = EINVAL; - return strcpy(buf, "??? ??? ?? ??:??:?? ????\n"); + return buf; } if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK) wn = "???"; @@ -119,23 +112,41 @@ asctime_r(struct tm const *restrict time if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) mn = "???"; else mn = mon_name[timeptr->tm_mon]; - /* - ** Use strftime's %Y to generate the year, to avoid overflow problems - ** when computing timeptr->tm_year + TM_YEAR_BASE. - ** Assume that strftime is unaffected by other out-of-range members - ** (e.g., timeptr->tm_mday) when processing "%Y". - */ - (void) strftime(year, sizeof year, "%Y", timeptr); - (void) snprintf(result, - sizeof(result), - ((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B), - wn, mn, - timeptr->tm_mday, timeptr->tm_hour, - timeptr->tm_min, timeptr->tm_sec, - year); - if (strlen(result) < STD_ASCTIME_BUF_SIZE - || buf == buf_ctime || buf == buf_asctime) - return strcpy(buf, result); + + year = timeptr->tm_year; + mday = timeptr->tm_mday; + hour = timeptr->tm_hour; + min = timeptr->tm_min; + sec = timeptr->tm_sec; + + /* Vintage programs are coded for years that are always four bytes long + and may assume that the newline always lands in the same place. + For years that are less than four bytes, pad the output with + leading zeroes to get the newline in the traditional place. + For years longer than four bytes, put extra spaces before the year + so that vintage code trying to overwrite the newline + won't overwrite a digit within a year and truncate the year, + using the principle that no output is better than wrong output. + This conforms to ISO C and POSIX standards, which say behavior + is undefined when the year is less than 1000 or greater than 9999. + + Also, avoid overflow when formatting tm_year + TM_YEAR_BASE. */ + + if ((size_t)(year <= INT_MAX - TM_YEAR_BASE + ? snprintf (buf, bufsize, + ((-999 - TM_YEAR_BASE <= year + && year <= 9999 - TM_YEAR_BASE) + ? "%s %s%3d %.2d:%.2d:%.2d %04ld\n" + : "%s %s%3d %.2d:%.2d:%.2d %ld\n"), + wn, mn, mday, hour, min, sec, + year + long_TM_YEAR_BASE) + : snprintf (buf, bufsize, + "%s %s%3d %.2d:%.2d:%.2d %d%d\n", + wn, mn, mday, hour, min, sec, + year / 10 + TM_YEAR_BASE / 10, + year % 10)) + < bufsize) + return buf; else { errno = EOVERFLOW; return NULL; @@ -170,5 +181,8 @@ ctime_r(const time_t *timep, char *buf) char * ctime(const time_t *timep) { - return ctime_r(timep, buf_ctime); + /* Do not call localtime_r, as C23 requires ctime to initialize the + static storage that localtime updates. */ + struct tm *tmp = localtime(timep); + return tmp ? asctime(tmp) : NULL; } Index: src/lib/libc/time/ctime.3 diff -u src/lib/libc/time/ctime.3:1.71 src/lib/libc/time/ctime.3:1.72 --- src/lib/libc/time/ctime.3:1.71 Sat Sep 16 14:40:26 2023 +++ src/lib/libc/time/ctime.3 Thu Jan 23 17:44:22 2025 @@ -1,9 +1,9 @@ -.\" $NetBSD: ctime.3,v 1.71 2023/09/16 18:40:26 christos Exp $ +.\" $NetBSD: ctime.3,v 1.72 2025/01/23 22:44:22 christos Exp $ .\" .\" This file is in the public domain, so clarified as of .\" 2009-05-17 by Arthur David Olson. .\" -.Dd September 16, 2023 +.Dd January 23, 2025 .Dt CTIME 3 .Os .Sh NAME @@ -28,11 +28,11 @@ .Vt extern char *tzname[2]; .Ft [[deprecated]] char * .Fn asctime "const struct tm *tm" -.Ft char * +.Ft [[only in POSIX.1-2017 and earlier]] char * .Fn asctime_r "const struct tm *restrict tm" "char * restrict buf" .Ft [[deprecated]] char * .Fn ctime "const time_t *clock" -.Ft char * +.Ft [[only in POSIX.1-2017 and earlier]] char * .Fn ctime_r "const time_t *clock" "char *buf" .Ft char * .Fn ctime_rz "timezone_t restrict tz" "const time_t *clock" "char *buf" @@ -68,6 +68,19 @@ structure to a string with the following .Bd -literal -offset indent Thu Nov 24 18:22:48 1986\en\e0 .Ed +Years requiring fewer than four characters are padded with leading zeroes. +For years longer than four characters, the string is of the form: +.Bd -literal -offset indent +Thu Nov 24 18:22:48 81986\n\0 +.Ed +with five spaces before the year. +This unusual format is designed to make it less likely that older +software that expects exactly 26 bytes of output will mistakenly output +misleading values for out-of-range years. +This function is deprecated starting in C23. +Callers can use +.Fn strftime +instead. .Pp The .Fa tm Index: src/lib/libc/time/localtime.c diff -u src/lib/libc/time/localtime.c:1.144 src/lib/libc/time/localtime.c:1.145 --- src/lib/libc/time/localtime.c:1.144 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/localtime.c Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: localtime.c,v 1.144 2024/09/11 13:50:34 christos Exp $ */ +/* $NetBSD: localtime.c,v 1.145 2025/01/23 22:44:22 christos Exp $ */ /* Convert timestamp from time_t to struct tm. */ @@ -12,7 +12,7 @@ #if 0 static char elsieid[] = "@(#)localtime.c 8.17"; #else -__RCSID("$NetBSD: localtime.c,v 1.144 2024/09/11 13:50:34 christos Exp $"); +__RCSID("$NetBSD: localtime.c,v 1.145 2025/01/23 22:44:22 christos Exp $"); #endif #endif /* LIBC_SCCS and not lint */ @@ -36,6 +36,97 @@ __weak_alias(daylight,_daylight) __weak_alias(tzname,_tzname) #endif +#if HAVE_SYS_STAT_H +# include <sys/stat.h> +#endif +#if !defined S_ISREG && defined S_IFREG +/* Ancient UNIX or recent MS-Windows. */ +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif + +#ifdef __NetBSD__ +#define lock() (rwlock_wrlock(&__lcl_lock), 0) +#define unlock() rwlock_unlock(&__lcl_lock) +#else +#if defined THREAD_SAFE && THREAD_SAFE +# include <pthread.h> +static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; +static int lock(void) { return pthread_mutex_lock(&locallock); } +static void unlock(void) { pthread_mutex_unlock(&locallock); } +#else +static int lock(void) { return 0; } +static void unlock(void) { } +#endif +#endif + +/* 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. */ +#ifdef INTPTR_MAX +# define UNCONST(a) ((char *) (intptr_t) (a)) +#else +# define UNCONST(a) ((char *) (a)) +#endif + +/* A signed type wider than int, so that we can add 1900 + tm_mon/12 to tm_year + without overflow. The static_assert checks that it is indeed wider + than int; if this fails on your platform please let us know. */ +#if INT_MAX < LONG_MAX +typedef long iinntt; +# define IINNTT_MIN LONG_MIN +# define IINNTT_MAX LONG_MAX +#elif INT_MAX < LLONG_MAX +typedef long long iinntt; +# define IINNTT_MIN LLONG_MIN +# define IINNTT_MAX LLONG_MAX +#else +typedef intmax_t iinntt; +# define IINNTT_MIN INTMAX_MIN +# define IINNTT_MAX INTMAX_MAX +#endif +/*CONSTCOND*/ +static_assert(IINNTT_MIN < INT_MIN && INT_MAX < IINNTT_MAX); + +/* On platforms where offtime or mktime might overflow, + strftime.c defines USE_TIMEX_T to be true and includes us. + This tells us to #define time_t to an internal type timex_t that is + wide enough so that strftime %s never suffers from integer overflow, + and to #define offtime (if TM_GMTOFF is defined) or mktime (otherwise) + 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 +# undef time_t +# define time_t timex_t +# if MKTIME_FITS_IN(LONG_MIN, LONG_MAX) +typedef long timex_t; +# define TIME_T_MIN LONG_MIN +# define TIME_T_MAX LONG_MAX +# elif MKTIME_FITS_IN(LLONG_MIN, LLONG_MAX) +typedef long long timex_t; +# define TIME_T_MIN LLONG_MIN +# define TIME_T_MAX LLONG_MAX +# else +typedef intmax_t timex_t; +# define TIME_T_MIN INTMAX_MIN +# define TIME_T_MAX INTMAX_MAX +# endif + +# ifdef TM_GMTOFF +# undef timeoff +# define timeoff timex_timeoff +# undef EXTERN_TIMEOFF +# else +# undef mktime +# define mktime timex_mktime +# endif +#endif + #ifndef TZ_ABBR_CHAR_SET # define TZ_ABBR_CHAR_SET \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" @@ -45,12 +136,23 @@ __weak_alias(tzname,_tzname) # define TZ_ABBR_ERR_CHAR '_' #endif /* !defined TZ_ABBR_ERR_CHAR */ -/* -** Support non-POSIX platforms that distinguish between text and binary files. -*/ +/* Port to platforms that lack some O_* flags. Unless otherwise + specified, the flags are standardized by POSIX. */ #ifndef O_BINARY -# define O_BINARY 0 +# define O_BINARY 0 /* MS-Windows */ +#endif +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif +#ifndef O_CLOFORK +# define O_CLOFORK 0 +#endif +#ifndef O_IGNORE_CTTY +# define O_IGNORE_CTTY 0 /* GNU/Hurd */ +#endif +#ifndef O_NOCTTY +# define O_NOCTTY 0 #endif #ifndef WILDABBR @@ -79,7 +181,10 @@ __weak_alias(tzname,_tzname) static const char wildabbr[] = WILDABBR; static char const etc_utc[] = "Etc/UTC"; + +#if !USE_TIMEX_T || defined TM_ZONE || !defined TM_GMTOFF 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. @@ -93,10 +198,31 @@ static char const *utc = etc_utc + sizeo typedef int_fast64_t __time_t; +/* Limit to time zone abbreviation length in proleptic TZ strings. + This is distinct from TZ_MAX_CHARS, which limits TZif file contents. + It defaults to 254, not 255, so that desigidx_type can be an unsigned char. + unsigned char suffices for TZif files, so the only reason to increase + TZNAME_MAXIMUM is to support TZ strings specifying abbreviations + longer than 254 bytes. There is little reason to do that, though, + as strings that long are hardly "abbreviations". */ +#ifndef TZNAME_MAXIMUM +# define TZNAME_MAXIMUM 254 +#endif + +#if TZNAME_MAXIMUM < UCHAR_MAX +typedef unsigned char desigidx_type; +#elif TZNAME_MAXIMUM < INT_MAX +typedef int desigidx_type; +#elif TZNAME_MAXIMUM < PTRDIFF_MAX +typedef ptrdiff_t desigidx_type; +#else +# error "TZNAME_MAXIMUM too large" +#endif + struct ttinfo { /* time type information */ - int_fast32_t tt_utoff; /* UT offset in seconds */ + int_least32_t tt_utoff; /* UT offset in seconds */ + desigidx_type tt_desigidx; /* abbreviation list index */ bool tt_isdst; /* used to set tm_isdst */ - int tt_desigidx; /* abbreviation list index */ bool tt_ttisstd; /* transition is std time */ bool tt_ttisut; /* transition is UT */ }; @@ -115,12 +241,6 @@ static char const UNSPEC[] = "-00"; for ttunspecified to work without crashing. */ enum { CHARS_EXTRA = max(sizeof UNSPEC, 2) - 1 }; -/* Limit to time zone abbreviation length in proleptic TZ strings. - This is distinct from TZ_MAX_CHARS, which limits TZif file contents. */ -#ifndef TZNAME_MAXIMUM -# define TZNAME_MAXIMUM 255 -#endif - #define state __state /* A representation of the contents of a TZif file. Ideally this would have no size limits; the following sizes should suffice for @@ -162,26 +282,37 @@ static struct tm *gmtsub(struct state co 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 normalize_overflow32(int_fast32_t *, int *, int); 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 *); -static timezone_t gmtptr; +#ifndef ALL_STATE +static struct state * gmtptr; +#else +static struct state gmtmem; +static struct state *const gmtptr = &gmtmem; +#endif #ifndef TZ_STRLEN_MAX # define TZ_STRLEN_MAX 255 #endif /* !defined TZ_STRLEN_MAX */ +#if !USE_TIMEX_T || !defined TM_GMTOFF static char lcl_TZname[TZ_STRLEN_MAX + 1]; static int lcl_is_set; +#endif #if !defined(__LIBC12_SOURCE__) -timezone_t __lclptr; -#ifdef _REENTRANT +# ifndef ALL_STATE +struct state * __lclptr; +# else +static struct state lclmem; +struct state *const __lclptr = &lclmem; +# endif +# ifdef _REENTRANT rwlock_t __lcl_lock = RWLOCK_INITIALIZER; -#endif +# endif #endif /* @@ -196,39 +327,41 @@ rwlock_t __lcl_lock = RWLOCK_INITIALIZER ** trigger latent bugs in programs. */ -#if SUPPORT_C89 -static struct tm tm; -#endif +#if !USE_TIMEX_T -#if 2 <= HAVE_TZNAME + TZ_TIME_T || defined(__NetBSD__) -# if !defined(__LIBC12_SOURCE__) +# if SUPPORT_C89 +static struct tm tm; +# endif -__aconst char * tzname[2] = { - (__aconst char *)__UNCONST(wildabbr), - (__aconst char *)__UNCONST(wildabbr) +# if 2 <= HAVE_TZNAME + TZ_TIME_T || defined(__NetBSD__) +# if !defined(__LIBC12_SOURCE__) +__aconst char *tzname[2] = { + (__aconst char *) UNCONST(wildabbr), + (__aconst char *) UNCONST(wildabbr), }; - -# else +# else extern __aconst char * tzname[2]; -# endif /* __LIBC12_SOURCE__ */ -#endif +# endif /* __LIBC12_SOURCE__ */ +# endif -#if 2 <= USG_COMPAT + TZ_TIME_T || defined(__NetBSD__) -# if !defined(__LIBC12_SOURCE__) +# if 2 <= USG_COMPAT + TZ_TIME_T || defined(__NetBSD__) +# if !defined(__LIBC12_SOURCE__) long timezone = 0; int daylight = 0; -# endif /* __LIBC12_SOURCE__ */ -#endif /* 2<= USG_COMPAT + TZ_TIME_T */ +# endif /* __LIBC12_SOURCE__ */ +# endif /* 2<= USG_COMPAT + TZ_TIME_T */ -#if 2 <= ALTZONE + TZ_TIME_T +# if 2 <= ALTZONE + TZ_TIME_T long altzone = 0; -#endif /* 2 <= ALTZONE + TZ_TIME_T */ +# endif /* 2 <= ALTZONE + TZ_TIME_T */ +#endif /* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ static void -init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx) +init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, + desigidx_type desigidx) { s->tt_utoff = utoff; s->tt_isdst = isdst; @@ -327,20 +460,22 @@ tzgetgmtoff(const timezone_t sp, int isd return l; } +#if !USE_TIMEX_T || !defined TM_GMTOFF + static void update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) { -#if HAVE_TZNAME - tzname[ttisp->tt_isdst] = __UNCONST(&sp->chars[ttisp->tt_desigidx]); -#endif -#if USG_COMPAT +# if HAVE_TZNAME + tzname[ttisp->tt_isdst] = UNCONST(&sp->chars[ttisp->tt_desigidx]); +# endif +# if USG_COMPAT if (!ttisp->tt_isdst) timezone = - ttisp->tt_utoff; -#endif -#if ALTZONE +# endif +# if ALTZONE if (ttisp->tt_isdst) altzone = - ttisp->tt_utoff; -#endif +# endif } /* If STDDST_MASK indicates that SP's TYPE provides useful info, @@ -371,18 +506,18 @@ settzname(void) When STDDST_MASK becomes zero we can stop looking. */ int stddst_mask = 0; -#if HAVE_TZNAME - tzname[0] = tzname[1] = __UNCONST(sp ? wildabbr : utc); +# if HAVE_TZNAME + tzname[0] = tzname[1] = UNCONST(sp ? wildabbr : utc); stddst_mask = 3; -#endif -#if USG_COMPAT +# endif +# if USG_COMPAT timezone = 0; stddst_mask = 3; -#endif -#if ALTZONE +# endif +# if ALTZONE altzone = 0; stddst_mask |= 2; -#endif +# endif /* ** And to get the latest time zone abbreviations into tzname. . . */ @@ -391,11 +526,11 @@ settzname(void) stddst_mask = may_update_tzname_etc(stddst_mask, sp, sp->types[i]); for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--) stddst_mask = may_update_tzname_etc(stddst_mask, sp, i); - } + } -#if USG_COMPAT +# if USG_COMPAT daylight = (unsigned int)stddst_mask >> 1 ^ 1; -#endif +# endif } /* Replace bogus characters in time zone abbreviations. @@ -422,6 +557,8 @@ scrub_abbrs(struct state *sp) return 0; } +#endif + /* Input buffer for data read from a compiled tz file. */ union input_buffer { /* The first part of the buffer, interpreted as a header. */ @@ -456,11 +593,15 @@ union local_storage { char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)]; }; -/* Load tz data from the file named NAME into *SP. Read extended - format if DOEXTEND. Use *LSP for temporary storage. Return 0 on +/* These tzload flags can be ORed together, and fit into 'char'. */ +enum { TZLOAD_FROMENV = 1 }; /* The TZ string came from the environment. */ +enum { TZLOAD_TZSTRING = 2 }; /* Read any newline-surrounded TZ string. */ + +/* Load tz data from the file named NAME into *SP. Respect TZLOADFLAGS. + Use *LSP for temporary storage. Return 0 on success, an errno value on failure. */ static int -tzloadbody(char const *name, struct state *sp, bool doextend, +tzloadbody(char const *name, struct state *sp, char tzloadflags, union local_storage *lsp) { register int i; @@ -511,9 +652,27 @@ tzloadbody(char const *name, struct stat name = lsp->fullname; } - if (doaccess && access(name, R_OK) != 0) - return errno; - fid = open(name, O_RDONLY | O_BINARY); + if (doaccess && (tzloadflags & TZLOAD_FROMENV)) { + /* Check for security violations and for devices whose mere + opening could have unwanted side effects. Although these + checks are racy, they're better than nothing and there is + no portable way to fix the races. */ + if (access(name, R_OK) < 0) + return errno; +#if 0 /* handle with O_REGULAR instead */ +#ifdef S_ISREG + { + struct stat st; + if (stat(name, &st) < 0) + return errno; + if (!S_ISREG(st.st_mode)) + return EINVAL; + } +#endif +#endif + } + fid = open(name, (O_RDONLY | O_BINARY | O_CLOEXEC | O_CLOFORK + | O_IGNORE_CTTY | O_NOCTTY | O_REGULAR)); if (fid < 0) return errno; @@ -640,7 +799,7 @@ tzloadbody(char const *name, struct stat correction must differ from the previous one's by 1 second or less, except that the first correction can be any value; these requirements are more generous than - RFC 8536, to allow future RFC extensions. */ + RFC 9636, to allow future RFC extensions. */ if (! (i == 0 || (prevcorr < corr ? corr == prevcorr + 1 @@ -691,7 +850,7 @@ tzloadbody(char const *name, struct stat if (!version) break; } - if (doextend && nread > 2 && + if ((tzloadflags & TZLOAD_TZSTRING) && nread > 2 && up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && sp->typecnt + 2 <= TZ_MAX_TYPES) { struct state *ts = &lsp->u.st; @@ -766,19 +925,24 @@ tzloadbody(char const *name, struct stat return 0; } -/* Load tz data from the file named NAME into *SP. Read extended - format if DOEXTEND. Return 0 on success, an errno value on failure. */ +/* Load tz data from the file named NAME into *SP. Respect TZLOADFLAGS. + Return 0 on success, an errno value on failure. */ static int -tzload(char const *name, struct state *sp, bool doextend) +tzload(char const *name, struct state *sp, char tzloadflags) { +#ifdef ALL_STATE union local_storage *lsp = malloc(sizeof *lsp); if (!lsp) { return /*CONSTCOND*/HAVE_MALLOC_ERRNO ? errno : ENOMEM; } else { - int err = tzloadbody(name, sp, doextend, lsp); + int err = tzloadbody(name, sp, tzloadflags, lsp); free(lsp); return err; } +#else + union local_storage ls; + return tzloadbody(name, sp, tzloadflags, &ls); +#endif } static const int mon_lengths[2][MONSPERYEAR] = { @@ -1125,7 +1289,7 @@ tzparse(const char *name, struct state * sp->leapcnt = basep->leapcnt; memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); } else { - load_ok = tzload(TZDEFRULES, sp, false) == 0; + load_ok = tzload(TZDEFRULES, sp, 0) == 0; if (!load_ok) sp->leapcnt = 0; /* So, we're off a little. */ } @@ -1332,8 +1496,8 @@ tzparse(const char *name, struct state * } theiroffset = -sp->ttis[j].tt_utoff; if (sp->ttis[j].tt_isdst) - theirstdoffset = theiroffset; - else theirdstoffset = theiroffset; + theirdstoffset = theiroffset; + else theirstdoffset = theiroffset; } /* ** Finally, fill in ttis. @@ -1365,14 +1529,17 @@ tzparse(const char *name, struct state * static void gmtload(struct state *const sp) { - if (tzload(etc_utc, sp, true) != 0) + if (tzload(etc_utc, sp, TZLOAD_TZSTRING) != 0) (void)tzparse("UTC0", sp, NULL); } +#if !USE_TIMEX_T || !defined TM_GMTOFF + /* Initialize *SP to a value appropriate for the TZ setting NAME. + Respect TZLOADFLAGS. Return 0 on success, an errno value on failure. */ static int -zoneinit(struct state *sp, char const *name) +zoneinit(struct state *sp, char const *name, char tzloadflags) { if (name && ! name[0]) { /* @@ -1387,7 +1554,7 @@ zoneinit(struct state *sp, char const *n strcpy(sp->chars, utc); return 0; } else { - int err = tzload(name, sp, true); + int err = tzload(name, sp, tzloadflags); if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) err = 0; if (err == 0) @@ -1406,25 +1573,41 @@ tzsetlcl(char const *name) : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) return; +# ifndef ALL_STATE if (! sp) __lclptr = sp = malloc(sizeof *__lclptr); +# endif if (sp) { - if (zoneinit(sp, name) != 0) - zoneinit(sp, ""); + if (zoneinit(sp, name, TZLOAD_FROMENV | TZLOAD_TZSTRING) != 0) { + zoneinit(sp, "", 0); + strcpy(sp->chars, UNSPEC); + } if (0 < lcl) strcpy(lcl_TZname, name); } settzname(); lcl_is_set = lcl; } +#endif + +#if !USE_TIMEX_T +void +tzset(void) +{ + if (lock() != 0) + return; + tzset_unlocked(); + unlock(); +} #ifdef STD_INSPIRED void tzsetwall(void) { - rwlock_wrlock(&__lcl_lock); + if (lock() != 0) + return; tzsetlcl(NULL); - rwlock_unlock(&__lcl_lock); + unlock(); } #endif @@ -1433,37 +1616,33 @@ tzset_unlocked(void) { tzsetlcl(getenv("TZ")); } - -void -tzset(void) -{ - rwlock_wrlock(&__lcl_lock); - tzset_unlocked(); - rwlock_unlock(&__lcl_lock); -} +#endif static void gmtcheck(void) { static bool gmt_is_set; - rwlock_wrlock(&__lcl_lock); + if (lock() != 0) + return; if (! gmt_is_set) { +#ifdef ALL_STATE gmtptr = malloc(sizeof *gmtptr); +#endif if (gmtptr) gmtload(gmtptr); gmt_is_set = true; } - rwlock_unlock(&__lcl_lock); + unlock(); } -#if NETBSD_INSPIRED +#if NETBSD_INSPIRED && !USE_TIMEX_T timezone_t tzalloc(char const *name) { timezone_t sp = malloc(sizeof *sp); if (sp) { - int err = zoneinit(sp, name); + int err = zoneinit(sp, name, TZLOAD_TZSTRING); if (err != 0) { free(sp); errno = err; @@ -1493,6 +1672,8 @@ tzfree(timezone_t sp) #endif +#if !USE_TIMEX_T || !defined TM_GMTOFF + /* ** The easy way to behave "as if no library function calls" localtime ** is to not call it, so we drop its guts into "localsub", which can be @@ -1550,14 +1731,14 @@ localsub(struct state const *sp, time_t } result = localsub(sp, &newt, setname, tmp); if (result) { -#if defined ckd_add && defined ckd_sub +# if defined ckd_add && defined ckd_sub if (t < sp->ats[0] ? ckd_sub(&result->tm_year, result->tm_year, years) : ckd_add(&result->tm_year, result->tm_year, years)) return NULL; -#else +# else register int_fast64_t newy; newy = result->tm_year; @@ -1569,7 +1750,7 @@ localsub(struct state const *sp, time_t return NULL; } result->tm_year = (int)newy; -#endif +# endif } return result; } @@ -1598,51 +1779,57 @@ localsub(struct state const *sp, time_t result = timesub(&t, ttisp->tt_utoff, sp, tmp); if (result) { result->tm_isdst = ttisp->tt_isdst; -#ifdef TM_ZONE - result->TM_ZONE = __UNCONST(&sp->chars[ttisp->tt_desigidx]); -#endif /* defined TM_ZONE */ +# ifdef TM_ZONE + result->TM_ZONE = UNCONST(&sp->chars[ttisp->tt_desigidx]); +# endif if (setname) update_tzname_etc(sp, ttisp); } return result; } +#endif -#if NETBSD_INSPIRED +#if !USE_TIMEX_T +# if NETBSD_INSPIRED struct tm * localtime_rz(struct state *__restrict sp, time_t const *__restrict timep, struct tm *__restrict tmp) { return localsub(sp, timep, 0, tmp); } - -#endif +# endif static struct tm * localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) { - rwlock_wrlock(&__lcl_lock); + int err = lock(); + if (err) { + errno = err; + return NULL; + } if (setname || !lcl_is_set) tzset_unlocked(); tmp = localsub(__lclptr, timep, setname, tmp); - rwlock_unlock(&__lcl_lock); + unlock(); return tmp; } struct tm * localtime(const time_t *timep) { -#if !SUPPORT_C89 +# if !SUPPORT_C89 static struct tm tm; -#endif +# endif return localtime_tzset(timep, &tm, true); } struct tm * localtime_r(const time_t *__restrict timep, struct tm *__restrict tmp) { - return localtime_tzset(timep, tmp, true); + return localtime_tzset(timep, tmp, false); } +#endif /* ** gmtsub is to gmtime as localsub is to localtime. @@ -1662,12 +1849,14 @@ gmtsub(ATTRIBUTE_MAYBE_UNUSED struct sta ** but this is no time for a treasure hunt. */ if (result) - result->TM_ZONE = offset ? __UNCONST(wildabbr) : gmtptr ? - gmtptr->chars : __UNCONST(utc); + result->TM_ZONE = UNCONST(offset ? wildabbr : gmtptr ? + gmtptr->chars : utc); #endif /* defined TM_ZONE */ return result; } +#if !USE_TIMEX_T + /* * Re-entrant version of gmtime. */ @@ -1682,13 +1871,13 @@ gmtime_r(time_t const *__restrict timep, struct tm * gmtime(const time_t *timep) { -#if !SUPPORT_C89 +# if !SUPPORT_C89 static struct tm tm; -#endif +# endif return gmtime_r(timep, &tm); } -#if STD_INSPIRED +# if STD_INSPIRED /* This function is obsolescent and may disappear in future releases. Callers can instead use localtime_rz with a fixed-offset zone. */ @@ -1698,9 +1887,9 @@ offtime(const time_t *timep, long offset { gmtcheck(); -#if !SUPPORT_C89 +# if !SUPPORT_C89 static struct tm tm; -#endif +# endif return gmtsub(gmtptr, timep, (int_fast32_t)offset, &tm); } @@ -1711,45 +1900,7 @@ offtime_r(const time_t *timep, long offs return gmtsub(NULL, timep, (int_fast32_t)offset, tmp); } -#endif /* STD_INSPIRED */ - -#if TZ_TIME_T - -# if USG_COMPAT -# define daylight 0 -# define timezone 0 -# endif -# if !ALTZONE -# define altzone 0 # endif - -/* Convert from the underlying system's time_t to the ersatz time_tz, - which is called 'time_t' in this file. Typically, this merely - converts the time's integer width. On some platforms, the system - time is local time not UT, or uses some epoch other than the POSIX - epoch. - - Although this code appears to define a function named 'time' that - returns time_t, the macros in private.h cause this code to actually - define a function named 'tz_time' that returns tz_time_t. The call - to sys_time invokes the underlying system's 'time' function. */ - -time_t -time(time_t *p) -{ - __time_t r = sys_time(0); - if (r != (time_t) -1) { - int_fast32_t offset = EPOCH_LOCAL ? (daylight ? timezone : altzone) : 0; - if (increment_overflow32(&offset, -EPOCH_OFFSET) - || increment_overflow_time(&r, offset)) { - errno = EOVERFLOW; - r = -1; - } - } - if (p) - *p = (time_t)r; - return (time_t)r; -} #endif /* @@ -1808,7 +1959,7 @@ timesub(const time_t *timep, int_fast32_ dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; rem %= SECSPERDAY; /* y = (EPOCH_YEAR - + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), sans overflow. But calculate against 1570 (EPOCH_YEAR - YEARSPERREPEAT) instead of against 1970 so that things work for localtime values before 1970 when time_t is unsigned. */ @@ -1925,17 +2076,17 @@ increment_overflow(int *ip, int j) } static bool -increment_overflow32(int_fast32_t *const lp, int const m) +increment_overflow_time_iinntt(time_t *tp, iinntt j) { #ifdef ckd_add - return ckd_add(lp, *lp, m); + return ckd_add(tp, *tp, j); #else - register int_fast32_t const l = *lp; - - if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l)) - return true; - *lp += m; - return false; + 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 } @@ -1959,30 +2110,6 @@ increment_overflow_time(__time_t *tp, in #endif } -static bool -normalize_overflow(int *const tensptr, int *const unitsptr, const int base) -{ - register int tensdelta; - - tensdelta = (*unitsptr >= 0) ? - (*unitsptr / base) : - (-1 - (-1 - *unitsptr) / base); - *unitsptr -= tensdelta * base; - return increment_overflow(tensptr, tensdelta); -} - -static bool -normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base) -{ - register int tensdelta; - - tensdelta = (*unitsptr >= 0) ? - (*unitsptr / base) : - (-1 - (-1 - *unitsptr) / base); - *unitsptr -= tensdelta * base; - return increment_overflow32(tensptr, tensdelta); -} - static int tmcomp(register const struct tm *const atmp, register const struct tm *const btmp) @@ -2027,14 +2154,12 @@ time2sub(struct tm *const tmp, { register int dir; register int i, j; - register int saved_seconds; - register int_fast32_t li; register time_t lo; register time_t hi; #ifdef NO_ERROR_IN_DST_GAP time_t ilo; #endif - int_fast32_t y; + iinntt y, mday, hour, min, saved_seconds; time_t newt; time_t t; struct tm yourtm, mytm; @@ -2045,36 +2170,56 @@ time2sub(struct tm *const tmp, #ifdef NO_ERROR_IN_DST_GAP again: #endif + min = yourtm.tm_min; if (do_norm_secs) { - if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, - SECSPERMIN)) - goto out_of_range; + min += yourtm.tm_sec / SECSPERMIN; + yourtm.tm_sec %= SECSPERMIN; + if (yourtm.tm_sec < 0) { + yourtm.tm_sec += SECSPERMIN; + min--; + } + } + + hour = yourtm.tm_hour; + hour += min / MINSPERHOUR; + yourtm.tm_min = min % MINSPERHOUR; + if (yourtm.tm_min < 0) { + yourtm.tm_min += MINSPERHOUR; + hour--; + } + + mday = yourtm.tm_mday; + mday += hour / HOURSPERDAY; + yourtm.tm_hour = hour % HOURSPERDAY; + if (yourtm.tm_hour < 0) { + yourtm.tm_hour += HOURSPERDAY; + mday--; } - if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR)) - goto out_of_range; - if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) - goto out_of_range; y = yourtm.tm_year; - if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR)) - goto out_of_range; + y += yourtm.tm_mon / MONSPERYEAR; + yourtm.tm_mon %= MONSPERYEAR; + if (yourtm.tm_mon < 0) { + yourtm.tm_mon += MONSPERYEAR; + y--; + } + /* ** Turn y into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ - if (increment_overflow32(&y, TM_YEAR_BASE)) - goto out_of_range; - while (yourtm.tm_mday <= 0) { - if (increment_overflow32(&y, -1)) - goto out_of_range; - li = y + (1 < yourtm.tm_mon); - yourtm.tm_mday += year_lengths[isleap(li)]; - } - while (yourtm.tm_mday > DAYSPERLYEAR) { - li = y + (1 < yourtm.tm_mon); - yourtm.tm_mday -= year_lengths[isleap(li)]; - if (increment_overflow32(&y, 1)) - goto out_of_range; + y += TM_YEAR_BASE; + + while (mday <= 0) { + iinntt li = y - (yourtm.tm_mon <= 1); + mday += year_lengths[isleap(li)]; + y--; + } + while (DAYSPERLYEAR < mday) { + iinntt li = y + (1 < yourtm.tm_mon); + mday -= year_lengths[isleap(li)]; + y++; } + yourtm.tm_mday = (int)mday; for ( ; ; ) { i = mon_lengths[isleap(y)][yourtm.tm_mon]; if (yourtm.tm_mday <= i) @@ -2082,16 +2227,14 @@ again: yourtm.tm_mday -= i; if (++yourtm.tm_mon >= MONSPERYEAR) { yourtm.tm_mon = 0; - if (increment_overflow32(&y, 1)) - goto out_of_range; + y++; } } #ifdef ckd_add if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE)) - return WRONG; -#else - if (increment_overflow32(&y, -TM_YEAR_BASE)) goto out_of_range; +#else + y -= TM_YEAR_BASE; if (! (INT_MIN <= y && y <= INT_MAX)) goto out_of_range; yourtm.tm_year = (int)y; @@ -2107,9 +2250,8 @@ again: ** not in the same minute that a leap second was deleted from, ** which is a safer assumption than using 58 would be. */ - if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN)) - goto out_of_range; - saved_seconds = yourtm.tm_sec; + saved_seconds = yourtm.tm_sec; + saved_seconds -= SECSPERMIN - 1; yourtm.tm_sec = SECSPERMIN - 1; } else { saved_seconds = yourtm.tm_sec; @@ -2225,6 +2367,8 @@ again: for (j = sp->typecnt - 1; j >= 0; --j) { if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) continue; + if (ttunspecified(sp, j)) + continue; newt = (time_t)(t + sp->ttis[j].tt_utoff - sp->ttis[i].tt_utoff); if (! funcp(sp, &newt, offset, &mytm)) @@ -2243,10 +2387,8 @@ again: goto invalid; } label: - newt = t + saved_seconds; - if ((newt < t) != (saved_seconds < 0)) - goto out_of_range; - t = newt; + if (increment_overflow_time_iinntt(&t, saved_seconds)) + return WRONG; if (funcp(sp, &t, offset, tmp)) { *okayp = true; return t; @@ -2359,8 +2501,10 @@ time1(struct tm *const tmp, return WRONG; } +#if !defined TM_GMTOFF || !USE_TIMEX_T + static time_t -mktime_tzname(timezone_t sp, struct tm *tmp, bool setname) +mktime_tzname(struct state *sp, struct tm *tmp, bool setname) { if (sp) return time1(tmp, localsub, sp, setname); @@ -2370,29 +2514,35 @@ mktime_tzname(timezone_t sp, struct tm * } } -#if NETBSD_INSPIRED - +# if USE_TIMEX_T +static +# endif time_t -mktime_z(struct state *__restrict sp, struct tm *__restrict tmp) +mktime(struct tm *tmp) { - return mktime_tzname(sp, tmp, false); + time_t t; + int err = lock(); + if (err) { + errno = err; + return -1; + } + tzset_unlocked(); + t = mktime_tzname(__lclptr, tmp, true); + unlock(); + return t; } #endif +#if NETBSD_INSPIRED && !USE_TIMEX_T time_t -mktime(struct tm *tmp) +mktime_z(struct state *restrict sp, struct tm *restrict tmp) { - time_t t; - - rwlock_wrlock(&__lcl_lock); - tzset_unlocked(); - t = mktime_tzname(__lclptr, tmp, true); - rwlock_unlock(&__lcl_lock); - return t; + return mktime_tzname(sp, tmp, false); } +#endif -#if STD_INSPIRED +#if STD_INSPIRED && !USE_TIMEX_T /* This function is obsolescent and may disappear in future releases. Callers can instead use mktime. */ time_t @@ -2412,12 +2562,14 @@ timelocal(struct tm *tmp) } #endif -#ifndef EXTERN_TIMEOFF -# ifndef timeoff -# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 <time.h>. */ +#if defined TM_GMTOFF || !USE_TIMEX_T + +# ifndef EXTERN_TIMEOFF +# ifndef timeoff +# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 <time.h>. */ +# endif +# define EXTERN_TIMEOFF static # endif -# define EXTERN_TIMEOFF static -#endif /* This function is obsolescent and may disappear in future releases. Callers can instead use mktime_z with a fixed-offset zone. */ @@ -2429,7 +2581,9 @@ timeoff(struct tm *tmp, long offset) gmtcheck(); return time1(tmp, gmtsub, gmtptr, (int_fast32_t)offset); } +#endif +#if !USE_TIMEX_T time_t timegm(struct tm *tmp) { @@ -2442,6 +2596,7 @@ timegm(struct tm *tmp) *tmp = tmcpy; return t; } +#endif static int_fast32_t leapcorr(struct state const *sp, __time_t t) @@ -2458,13 +2613,20 @@ leapcorr(struct state const *sp, __time_ return 0; } +/* +** XXX--is the below the right way to conditionalize?? +*/ + +#if !USE_TIMEX_T +# if STD_INSPIRED + /* NETBSD_INSPIRED_EXTERN functions are exported to callers if NETBSD_INSPIRED is defined, and are private otherwise. */ -# if NETBSD_INSPIRED -# define NETBSD_INSPIRED_EXTERN -# else -# define NETBSD_INSPIRED_EXTERN static -# endif +# if NETBSD_INSPIRED +# define NETBSD_INSPIRED_EXTERN +# else +# define NETBSD_INSPIRED_EXTERN static +# endif /* ** IEEE Std 1003.1 (POSIX) says that 536457599 @@ -2475,7 +2637,7 @@ leapcorr(struct state const *sp, __time_ */ NETBSD_INSPIRED_EXTERN time_t -time2posix_z(timezone_t sp, time_t t) +time2posix_z(struct state *sp, time_t t) { return (time_t)(t - leapcorr(sp, t)); } @@ -2483,23 +2645,21 @@ time2posix_z(timezone_t sp, time_t t) time_t time2posix(time_t t) { - rwlock_wrlock(&__lcl_lock); + int err = lock(); + if (err) { + errno = err; + return -1; + } if (!lcl_is_set) tzset_unlocked(); if (__lclptr) t = (time_t)(t - leapcorr(__lclptr, t)); - rwlock_unlock(&__lcl_lock); + unlock(); return t; } -/* -** XXX--is the below the right way to conditionalize?? -*/ - -#if STD_INSPIRED - NETBSD_INSPIRED_EXTERN time_t -posix2time_z(timezone_t sp, time_t t) +posix2time_z(struct state *sp, time_t t) { time_t x; time_t y; @@ -2530,13 +2690,55 @@ posix2time_z(timezone_t sp, time_t t) time_t posix2time(time_t t) { - rwlock_wrlock(&__lcl_lock); + int err = lock(); + if (err) { + errno = err; + return -1; + } if (!lcl_is_set) tzset_unlocked(); if (__lclptr) t = posix2time_z(__lclptr, t); - rwlock_unlock(&__lcl_lock); + unlock(); return t; } -#endif /* defined STD_INSPIRED */ +# endif /* STD_INSPIRED */ + +# if TZ_TIME_T + +# if !USG_COMPAT +# define timezone 0 +# endif + +/* Convert from the underlying system's time_t to the ersatz time_tz, + which is called 'time_t' in this file. Typically, this merely + converts the time's integer width. On some platforms, the system + time is local time not UT, or uses some epoch other than the POSIX + epoch. + + Although this code appears to define a function named 'time' that + returns time_t, the macros in private.h cause this code to actually + define a function named 'tz_time' that returns tz_time_t. The call + to sys_time invokes the underlying system's 'time' function. */ + +time_t +time(time_t *p) +{ + __time_t r = sys_time(0); + if (r != (time_t) -1) { + iinntt offset = EPOCH_LOCAL ? (daylight ? timezone : altzone) : 0; + if (offset < IINNTT_MIN + EPOCH_OFFSET + || increment_overflow_time_iinntt(&r, offset - EPOCH_OFFSET)) { + errno = EOVERFLOW; + r = -1; + } + } + if (p) + *p = (time_t)r; + return (time_t)r; +} + +# endif +#endif + Index: src/lib/libc/time/private.h diff -u src/lib/libc/time/private.h:1.70 src/lib/libc/time/private.h:1.71 --- src/lib/libc/time/private.h:1.70 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/private.h Thu Jan 23 17:44:22 2025 @@ -1,6 +1,6 @@ /* Private header for tzdb code. */ -/* $NetBSD: private.h,v 1.70 2024/09/11 13:50:34 christos Exp $ */ +/* $NetBSD: private.h,v 1.71 2025/01/23 22:44:22 christos Exp $ */ #ifndef PRIVATE_H #define PRIVATE_H @@ -49,6 +49,38 @@ # define SUPPORT_C89 1 #endif + +/* The following feature-test macros should be defined before + any #include of a system header. */ + +/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */ +#define _GNU_SOURCE 1 +/* Fix asctime_r on Solaris 11. */ +#define _POSIX_PTHREAD_SEMANTICS 1 +/* Enable strtoimax on pre-C99 Solaris 11. */ +#define __EXTENSIONS__ 1 +/* Cause MS-Windows headers to define POSIX names. */ +#define _CRT_DECLARE_NONSTDC_NAMES 1 +/* Prevent MS-Windows headers from defining min and max. */ +#define NOMINMAX 1 + +/* On GNUish systems where time_t might be 32 or 64 bits, use 64. + On these platforms _FILE_OFFSET_BITS must also be 64; otherwise + setting _TIME_BITS to 64 does not work. The code does not + otherwise rely on _FILE_OFFSET_BITS being 64, since it does not + use off_t or functions like 'stat' that depend on off_t. */ +#ifndef _TIME_BITS +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# if _FILE_OFFSET_BITS == 64 +# define _TIME_BITS 64 +# endif +#endif + +/* End of feature-test macro definitions. */ + + #ifndef __STDC_VERSION__ # define __STDC_VERSION__ 0 #endif @@ -100,11 +132,11 @@ #if !defined HAVE_GETTEXT && defined __has_include # if __has_include(<libintl.h>) -# define HAVE_GETTEXT true +# define HAVE_GETTEXT 1 # endif #endif #ifndef HAVE_GETTEXT -# define HAVE_GETTEXT false +# define HAVE_GETTEXT 0 #endif #ifndef HAVE_INCOMPATIBLE_CTIME_R @@ -137,20 +169,20 @@ #if !defined HAVE_SYS_STAT_H && defined __has_include # if !__has_include(<sys/stat.h>) -# define HAVE_SYS_STAT_H false +# define HAVE_SYS_STAT_H 0 # endif #endif #ifndef HAVE_SYS_STAT_H -# define HAVE_SYS_STAT_H true +# define HAVE_SYS_STAT_H 1 #endif #if !defined HAVE_UNISTD_H && defined __has_include # if !__has_include(<unistd.h>) -# define HAVE_UNISTD_H false +# define HAVE_UNISTD_H 0 # endif #endif #ifndef HAVE_UNISTD_H -# define HAVE_UNISTD_H true +# define HAVE_UNISTD_H 1 #endif #ifndef NETBSD_INSPIRED @@ -162,25 +194,6 @@ # define ctime_r _incompatible_ctime_r #endif /* HAVE_INCOMPATIBLE_CTIME_R */ -/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */ -#define _GNU_SOURCE 1 -/* Fix asctime_r on Solaris 11. */ -#define _POSIX_PTHREAD_SEMANTICS 1 -/* Enable strtoimax on pre-C99 Solaris 11. */ -#define __EXTENSIONS__ 1 - -/* On GNUish systems where time_t might be 32 or 64 bits, use 64. - On these platforms _FILE_OFFSET_BITS must also be 64; otherwise - setting _TIME_BITS to 64 does not work. The code does not - otherwise rely on _FILE_OFFSET_BITS being 64, since it does not - use off_t or functions like 'stat' that depend on off_t. */ -#ifndef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 -#endif -#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64 -# define _TIME_BITS 64 -#endif - /* ** Nested includes */ @@ -277,6 +290,10 @@ # endif #endif +#ifndef HAVE_SNPRINTF +# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) +#endif + #ifndef HAVE_STRFTIME_L # if _POSIX_VERSION < 200809 # define HAVE_STRFTIME_L 0 @@ -322,7 +339,7 @@ ** stdint.h, even with pre-C99 compilers. */ #if !defined HAVE_STDINT_H && defined __has_include -# define HAVE_STDINT_H true /* C23 __has_include implies C99 stdint.h. */ +# define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h. */ #endif #ifndef HAVE_STDINT_H # define HAVE_STDINT_H \ @@ -392,11 +409,15 @@ typedef int int_fast32_t; # endif #endif +#ifndef INT_LEAST32_MAX +typedef int_fast32_t int_least32_t; +#endif + #ifndef INTMAX_MAX # ifdef LLONG_MAX typedef long long intmax_t; # ifndef HAVE_STRTOLL -# define HAVE_STRTOLL true +# define HAVE_STRTOLL 1 # endif # if HAVE_STRTOLL # define strtoimax strtoll @@ -476,7 +497,7 @@ typedef unsigned long uintmax_t; hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */ #if !defined HAVE_STDCKDINT_H && defined __has_include # if __has_include(<stdckdint.h>) -# define HAVE_STDCKDINT_H true +# define HAVE_STDCKDINT_H 1 # endif #endif #ifdef HAVE_STDCKDINT_H @@ -571,13 +592,26 @@ typedef unsigned long uintmax_t; # define ATTRIBUTE_REPRODUCIBLE /* empty */ #endif +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(unsequenced) +# define ATTRIBUTE_UNSEQUENCED [[unsequenced]] +# endif +#endif +#ifndef ATTRIBUTE_UNSEQUENCED +# 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. */ #if __GNUC__ < 3 +# define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED # define ATTRIBUTE_FORMAT(spec) /* empty */ # define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE #else +# define ATTRIBUTE_CONST __attribute__((const)) # define ATTRIBUTE_FORMAT(spec) __attribute__((format spec)) # define ATTRIBUTE_PURE __attribute__((pure)) #endif @@ -610,6 +644,12 @@ typedef unsigned long uintmax_t; # define RESERVE_STD_EXT_IDS 0 #endif +#ifdef time_tz +# define defined_time_tz true +#else +# define defined_time_tz false +#endif + /* If standard C identifiers with external linkage (e.g., localtime) are reserved and are not already being renamed anyway, rename them as if compiling with '-Dtime_tz=time_t'. */ @@ -625,9 +665,9 @@ typedef unsigned long uintmax_t; ** typical platforms. */ #if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 -# define TZ_TIME_T 1 +# define TZ_TIME_T true #else -# define TZ_TIME_T 0 +# define TZ_TIME_T false #endif #if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T @@ -724,7 +764,7 @@ DEPRECATED_IN_C23 char *ctime(time_t con char *asctime_r(struct tm const *restrict, char *restrict); char *ctime_r(time_t const *, char *); #endif -double difftime(time_t, time_t); +ATTRIBUTE_CONST double difftime(time_t, time_t); size_t strftime(char *restrict, size_t, char const *restrict, struct tm const *restrict); # if HAVE_STRFTIME_L @@ -746,9 +786,9 @@ void tzset(void); || defined __GLIBC__ || defined __tm_zone /* musl */ \ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ || (defined __APPLE__ && defined __MACH__)) -# define HAVE_DECL_TIMEGM true +# define HAVE_DECL_TIMEGM 1 # else -# define HAVE_DECL_TIMEGM false +# define HAVE_DECL_TIMEGM 0 # endif #endif #if !HAVE_DECL_TIMEGM && !defined timegm @@ -788,7 +828,11 @@ extern long altzone; */ #ifndef STD_INSPIRED -# define STD_INSPIRED 0 +# ifdef __NetBSD__ +# define STD_INSPIRED 1 +# else +# define STD_INSPIRED 0 +# endif #endif #if STD_INSPIRED # if TZ_TIME_T || !defined tzsetwall @@ -899,7 +943,7 @@ ATTRIBUTE_PURE time_t time2posix_z(timez default: TIME_T_MAX_NO_PADDING) \ : (time_t) -1) enum { SIGNED_PADDING_CHECK_NEEDED - = _Generic((time_t) 0, + = _Generic((time_t) 0, signed char: false, short: false, int: false, long: false, long long: false, default: true) }; @@ -946,8 +990,8 @@ static_assert(! TYPE_SIGNED(time_t) || ! # define UNINIT_TRAP 0 #endif -/* localtime.c sometimes needs access to timeoff if it is not already public. - tz_private_timeoff should be used only by localtime.c. */ +/* strftime.c sometimes needs access to timeoff if it is not already public. + tz_private_timeoff should be used only by localtime.c and strftime.c. */ #if (!defined EXTERN_TIMEOFF \ && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) # ifndef timeoff Index: src/lib/libc/time/strftime.3 diff -u src/lib/libc/time/strftime.3:1.37 src/lib/libc/time/strftime.3:1.38 --- src/lib/libc/time/strftime.3:1.37 Tue Oct 25 04:07:07 2022 +++ src/lib/libc/time/strftime.3 Thu Jan 23 17:44:22 2025 @@ -30,7 +30,7 @@ .\" SUCH DAMAGE. .\" .\" from: @(#)strftime.3 5.12 (Berkeley) 6/29/91 -.\" $NetBSD: strftime.3,v 1.37 2022/10/25 08:07:07 kre Exp $ +.\" $NetBSD: strftime.3,v 1.38 2025/01/23 22:44:22 christos Exp $ .\" .Dd October 9, 2020 .Dt STRFTIME 3 @@ -73,60 +73,120 @@ Otherwise, zero is returned. .Pp Each conversion specification is replaced by the characters as follows which are then copied into the array. +The characters depend on the values of zero or more members of +.Fa timeptr +as specified by brackets in the description. +If a bracketed member name is followed by +.Dq + , +.Nm strftime +can use the named member even though POSIX.1-2024 does not list it; +if the name is followed by +.Dq \&- , +.Nm strftime +ignores the member even though POSIX.1-2024 lists it +which means portable code should set it. +For portability, +.Fa timeptr +should be initialized as if by a successful call to +.Xr gmtime 3 , +.Xr localtime 3 , +.Xr mktime 3 , +.Xr timegm 3 , +or similar functions. .Bl -tag -width "xxxx" .It Cm \&%A is replaced by the locale's full weekday name. +.Dv [ tm_wday ] .It Cm %a is replaced by the locale's abbreviated weekday name. +.Dv [ tm_wday ] .It Cm \&%B is replaced by the locale's full month name. +.Dv [ tm_mon ] .It Cm \&%b No or Cm \&%h is replaced by the locale's abbreviated month name. +.RI [ tm_mon ] .It Cm \&%C is replaced by the century (a year divided by 100 and truncated to an integer) -as a decimal number [00,99]. +as a decimal number, with at least two digits by default. +.RI [ tm_year ] .It Cm \&%c is replaced by the locale's appropriate date and time representation. +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_mon , +.Dv tm_mday , +.Dv tm_wday , +.Dv tm_hour , +.Dv tm_min , +.Dv tm_sec , +.Dv tm_gmtoff , +.Dv tm_zone , +.Dv tm_isdst \&-]. .It Cm \&%D is replaced by the date in the format .Dq Li %m/%d/%y . +Although used in the United States for current dates, +this format is ambiguous elsewhere +and for dates that might involve other centuries. +.Dv [ tm_year , +.Dv tm_mon , +.Dv tm_mday ] .It Cm \&%d is replaced by the day of the month as a decimal number [01,31]. +.Dv [ tm_mday ] .It Cm \&%e is replaced by the day of month as a decimal number [1,31]; single digits are preceded by a blank. +.Dv [ tm_mday ] .It Cm \&%F is equivalent to .Dq Li %Y-%m-%d (the ISO 8601 date format). +.Dv [ tm_year , +.Dv tm_mon , +.Dv tm_mday ] .It Cm \&%G is replaced by the ISO 8601 year with century as a decimal number. See also the .Cm \&%V conversion specification +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_wday ] .It Cm \&%g -is replaced by the ISO 8601 year without century as a decimal number [00-99]. +is replaced by the ISO 8601 year without century as a decimal number. This is the year that includes the greater part of the week. (Monday as the first day of a week). See also the .Ql \&%V conversion specification. +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_wday ] .It Cm \&%H is replaced by the hour (24-hour clock) as a decimal number [00,23]. +.Dv [ tm_hour ] .It Cm \&%I is replaced by the hour (12-hour clock) as a decimal number [01,12]. +.Dv [ tm_hour ] .It Cm \&%j is replaced by the day of the year as a decimal number [001,366]. +.Dv [ tm_yday ] .It Cm \&%k is replaced by the hour (24-hour clock) as a decimal number [0,23]; single digits are preceded by a blank. +.Dv [ tm_hour ] .It Cm \&%l is replaced by the hour (12-hour clock) as a decimal number [1,12]; single digits are preceded by a blank. +.Dv [ tm_hour ] .It Cm \&%M is replaced by the minute as a decimal number [00,59]. +.Dv [ tm_min ] .It Cm %m is replaced by the month as a decimal number [01,12]. +.Dv [ tm_mon ] .It Cm %n is replaced by a newline. .It Cm %p @@ -134,9 +194,12 @@ is replaced by the locale's equivalent o .Dq Tn AM or .Dq Tn PM . +.Dv [ tm_hour ] .It Cm \&%R is replaced by the time in the format .Dq Li %H:%M . +.Dv [ tm_hour , +.Dv tm_min ] .It Cm \&%r is replaced by the locale's representation of 12-hour clock time using AM/PM notation. @@ -145,20 +208,61 @@ is replaced by the second as a decimal n The range of seconds is [00-60] instead of [00-59] to allow for the periodic occurrence of leap seconds. +.Dv [ tm_sec ] .It Cm \&%s is replaced by the number of seconds since the Epoch (see .Xr ctime 3 ) . +Although %s is reliable in this implementation, +it can have glitches on other platforms +(notably obsolescent platforms lacking +.Fa tm_gmtoff +or where +.Tp time_t +is no wider than int), and POSIX allows +.Nm strftime +to set +.Dv errno +to +.Dv EINVAL +or +.Dv EOVERFLOW +and return 0 if the number of seconds would be negative or out of range for +.Tp time_t . +Portable code should therefore format a +.Tp time_t +value directly via something like +.Xr snprintf 3 +instead of via +.Xr localtime 3 +followed by +.Nm strftime +with "%s". +.Dv [ tm_year , +.Dv tm_mon , +.Dv tm_mday , +.Dv tm_hour , +.Dv tm_min , +.Dv tm_sec , +.Dv tm_gmtoff +, +.Dv tm_isdst \&-]. .It Cm \&%T is replaced by the time in the format .Dq Li %H:%M:%S . +.Dv [ tm_hour , +.Dv tm_min , +.Dv tm_sec ] .It Cm \&%t is replaced by a tab. .It Cm \&%U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. +.Dv [ tm_wday , +.Dv tm_yday , +.Dv tm_year \&-] .It Cm \&%u is replaced by the weekday (Monday as the first day of the week) as a decimal number [1,7]. +.Dv [ tm_wday ] .It Cm \&%V is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [01,53]. According to ISO 8601 the week @@ -167,26 +271,63 @@ otherwise it is week 53 of the previous The year is given by the .Ql \&%G conversion specification. +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_wday ] .It Cm \&%v is replaced by the date in the format .Dq Li %e-%b-%Y . +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_wday ] .It Cm \&%W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. +.Dv [ tm_yday , +.Dv tm_wday ] .It Cm \&%w is replaced by the weekday (Sunday as the first day of the week) as a decimal number [0,6]. +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_wday ] .It Cm \&%X is replaced by the locale's appropriate time representation. +.Dv [ tm_year \&-, +.Dv tm_yday \&-, +.Dv tm_mon \&-, +.Dv tm_mday \&-, +.Dv tm_wday \&-, +.Dv tm_hour , +.Dv tm_min , +.Dv tm_sec , +.Dv tm_gmtoff , +.Dv tm_zone , +.Dv tm_isdst \&-]. .It Cm \&%x is replaced by the locale's appropriate date representation. +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_mon , +.Dv tm_mday , +.Dv tm_wday , +.Dv tm_hour \&-, +.Dv tm_min \&-, +.Dv tm_sec \&-, +.Dv tm_gmtoff \&-, +.Dv tm_zone \&-, +.Dv tm_isdst \&-]. .It Cm \&%Y is replaced by the year with century as a decimal number. +.Dv [ tm_year ] .It Cm \&%y is replaced by the year without century as a decimal number [00,99]. +.Dv [ tm_year ] .It Cm \&%Z is replaced by the time zone abbreviation, or the empty string if this is not determinable. +.Dv [ tm_zone , +.Dv tm_isdst \&-] .It Cm \&%z is replaced by the offset from the Prime Meridian in the format +HHMM or -HHMM (ISO 8601) as appropriate, with positive values representing @@ -205,6 +346,16 @@ format. On .Nx currently this only works for the C locale. +.Dv [ tm_year , +.Dv tm_yday , +.Dv tm_mon , +.Dv tm_mday , +.Dv tm_wday , +.Dv tm_hour , +.Dv tm_min , +.Dv tm_sec , +.Dv tm_gmtoff , +.Dv tm_zone ] .It Cm %-* GNU libc extension. Do not do any padding when performing numerical outputs. @@ -215,10 +366,21 @@ Explicitly specify space for padding. GNU libc extension. Explicitly specify zero for padding. .It Cm %% -is replaced by +is replaced by as single .Ql % . .El .Pp +As a side effect, +.Nm strftime +also behaves as if +.Xr tzset 3 +were called. +This is for compatibility with older platforms, as required by POSIX; +it is not needed for +.Nm strftime +'s +own use. +.Pp The .Fn strftime_z function is similar to Index: src/lib/libc/time/strftime.c diff -u src/lib/libc/time/strftime.c:1.56 src/lib/libc/time/strftime.c:1.57 --- src/lib/libc/time/strftime.c:1.56 Fri Jun 7 09:53:23 2024 +++ src/lib/libc/time/strftime.c Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: strftime.c,v 1.56 2024/06/07 13:53:23 riastradh Exp $ */ +/* $NetBSD: strftime.c,v 1.57 2025/01/23 22:44:22 christos Exp $ */ /* Convert a broken-down timestamp to a string. */ @@ -35,7 +35,7 @@ static char elsieid[] = "@(#)strftime.c 7.64"; static char elsieid[] = "@(#)strftime.c 8.3"; #else -__RCSID("$NetBSD: strftime.c,v 1.56 2024/06/07 13:53:23 riastradh Exp $"); +__RCSID("$NetBSD: strftime.c,v 1.57 2025/01/23 22:44:22 christos Exp $"); #endif #endif /* LIBC_SCCS and not lint */ @@ -69,8 +69,63 @@ __RCSID("$NetBSD: strftime.c,v 1.56 2024 #include <locale.h> #include <stdio.h> +/* If true, the value returned by an idealized unlimited-range mktime + always fits into an integer type with bounds MIN and MAX. + If false, the value might not fit. + This macro is usable in #if if its arguments are. + Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, + divide by the maximum number of non-leap seconds in a year, + divide again by two just to be safe, + 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) + +/* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow + or if it is not known whether mktime can fail, + and is false if mktime definitely cannot fail. + This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. + If the builder has not configured this macro, guess based on what + known platforms do. When in doubt, guess true. */ +#ifndef MKTIME_MIGHT_OVERFLOW +# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ +# include <sys/param.h> +# endif +# if ((/* The following heuristics assume native time_t. */ \ + defined_time_tz) \ + || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ + assume overflow unless we're on a known-safe host. */ \ + !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ + && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ + if __TIMESIZE is 64. */ \ + !defined __TIMESIZE || __TIMESIZE < 64) \ + && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ + and later has 64-bit time_t on all platforms but i386 which \ + is currently scheduled for end-of-life on 2028-11-30. */ \ + !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ + || defined __i386) \ + && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ + !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ + && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ + !defined OpenBSD || OpenBSD < 201405))) +# define MKTIME_MIGHT_OVERFLOW 1 +# else +# define MKTIME_MIGHT_OVERFLOW 0 +# endif +#endif +/* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ +static_assert(MKTIME_MIGHT_OVERFLOW + || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); + +#if MKTIME_MIGHT_OVERFLOW +/* Do this after system includes as it redefines time_t, mktime, timeoff. */ +# define USE_TIMEX_T true +# include "localtime.c" +#endif + #ifndef DEPRECATE_TWO_DIGIT_YEARS -# define DEPRECATE_TWO_DIGIT_YEARS false +# define DEPRECATE_TWO_DIGIT_YEARS 0 #endif #ifdef __weak_alias @@ -152,10 +207,6 @@ strftime_lz(const timezone_t sp, char *c enum warn warn = IN_NONE; p = _fmt(sp, format, t, s, s + maxsize, &warn, loc); - if (!p) { - errno = EOVERFLOW; - return 0; - } if (/*CONSTCOND*/DEPRECATE_TWO_DIGIT_YEARS && warn != IN_NONE && getenv(YEAR_2000_NAME)) { (void) fprintf(stderr, "\n"); @@ -192,6 +243,12 @@ _fmt(const timezone_t sp, const char *fo label: switch (*++format) { case '\0': + /* Output unknown conversion specifiers as-is, + to aid debugging. This includes '%' at + format end. This conforms to C23 section + 7.29.3.5 paragraph 6, which says behavior + is undefined here. */ + default: --format; break; case 'A': @@ -381,15 +438,17 @@ label: tm.tm_mday = t->tm_mday; tm.tm_mon = t->tm_mon; tm.tm_year = t->tm_year; + + /* Get the time_t value for TM. + Native time_t, or its redefinition + by localtime.c above, is wide enough + so that this cannot overflow. */ +#if defined TM_GMTOFF + mkt = timeoff(&tm, t->TM_GMTOFF); +#else tm.tm_isdst = t->tm_isdst; -#if defined TM_GMTOFF && ! UNINIT_TRAP - tm.TM_GMTOFF = t->TM_GMTOFF; -#endif mkt = mktime_z(sp, &tm); - /* If mktime fails, %s expands to the - value of (time_t) -1 as a failure - marker; this is better in practice - than strftime failing. */ +#endif /* CONSTCOND */ if (TYPE_SIGNED(time_t)) { intmax_t n = mkt; @@ -716,12 +775,6 @@ label: PadIndex = PAD_ZERO; goto label; case '%': - /* - ** X311J/88-090 (4.12.3.5): if conversion char is - ** undefined, behavior is undefined. Print out the - ** character itself as printf(3) also does. - */ - default: break; } } Index: src/lib/libc/time/theory.html diff -u src/lib/libc/time/theory.html:1.20 src/lib/libc/time/theory.html:1.21 --- src/lib/libc/time/theory.html:1.20 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/theory.html Thu Jan 23 17:44:22 2025 @@ -123,8 +123,9 @@ If geolocation information is available, locate the user on a timezone map or prioritize names that are geographically close. For an example selection interface, see the <code>tzselect</code> program in the <code><abbr>tz</abbr></code> code. -The <a href="https://cldr.unicode.org">Unicode Common Locale Data -Repository</a> contains data that may be useful for other selection +Unicode's <a href="https://cldr.unicode.org">Common Locale Data +Repository (<abbr>CLDR</abbr>)</a> +contains data that may be useful for other selection interfaces; it maps timezone names like <code>Europe/Prague</code> to locale-dependent strings like "Prague", "Praha", "Прага", and "å¸ƒæ‹‰æ ¼". </p> @@ -200,6 +201,8 @@ in decreasing order of importance: <li> A name must not be empty, or contain '<code>//</code>', or start or end with '<code>/</code>'. + Also, a name must not be '<code>Etc/Unknown</code>', as + <abbr>CLDR</abbr> uses that string for an unknown or invalid timezone. </li> <li> Do not use names that differ only in case. @@ -220,10 +223,18 @@ in decreasing order of importance: do not need locations, since local time is not defined there. </li> <li> - If all the clocks in a timezone have agreed since 1970, - do not bother to include more than one timezone - even if some of the clocks disagreed before 1970. + If all clocks in a region have agreed since 1970, + give them just one name even if some of the clocks disagreed before 1970, + or reside in different countries or in notable or faraway locations. Otherwise these tables would become annoyingly large. + For example, do not create a name <code>Indian/Crozet</code> + as a near-duplicate or alias of <code>Asia/Dubai</code> + merely because they are different countries or territories, + or their clocks disagreed before 1970, or the + <a href="https://en.wikipedia.org/wiki/Crozet_Islands">Crozet Islands</a> + are notable in their own right, + or the Crozet Islands are not adjacent to other locations + that use <code>Asia/Dubai</code>. </li> <li> If boundaries between regions are fluid, such as during a war or @@ -579,10 +590,10 @@ in decreasing order of importance: locations while uninhabited. The leading '<code>-</code>' is a flag that the <abbr>UT</abbr> offset is in some sense undefined; this notation is derived - from <a href="https://datatracker.ietf.org/doc/html/rfc3339">Internet + from <a href="https://www.rfc-editor.org/rfc/rfc3339">Internet <abbr title="Request For Comments">RFC</abbr> 3339</a>. (The abbreviation 'Z' that - <a href="https://datatracker.ietf.org/doc/html/rfc9557">Internet + <a href="https://www.rfc-editor.org/rfc/rfc9557">Internet <abbr>RFC</abbr> 9557</a> uses for this concept would violate the POSIX requirement of at least three characters in an abbreviation.) @@ -1115,8 +1126,8 @@ However POSIX.1-2024, like earlier POSIX 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://datatracker.ietf.org/doc/html/8536">Internet - <abbr>RFC</abbr> 8536</a>. + <a href="https://www.rfc-editor.org/rfc/9636">Internet + <abbr>RFC</abbr> 9636</a>. The daylight saving time rules to be used for a particular timezone are encoded in the <abbr>TZif</abbr> file; the format of the file allows <abbr>US</abbr>, @@ -1201,12 +1212,15 @@ The vestigial <abbr>API</abbr>s are: The <code>tm_isdst</code> member is almost never needed and most of its uses should be discouraged in favor of the abovementioned <abbr>API</abbr>s. + It was intended as an index into the <code>tzname</code> variable, + but as mentioned previously that usage is obsolete. Although it can still be used in arguments to <code>mktime</code> to disambiguate timestamps near a <abbr>DST</abbr> transition when the clock jumps back on platforms lacking <code>tm_gmtoff</code>, this - disambiguation does not work when standard time itself jumps back, - which can occur when a location changes to a time zone with a + disambiguation works only for proleptic <code>TZ</code> strings; + it does not work in general for geographical timezones, + such as when a location changes to a time zone with a lesser <abbr>UT</abbr> offset. </li> </ul> @@ -1223,8 +1237,8 @@ The vestigial <abbr>API</abbr>s are: Programs that in the past used the <code>timezone</code> function may now examine <code>localtime(&clock)->tm_zone</code> (if <code>TM_ZONE</code> is defined) or - <code>tzname[localtime(&clock)->tm_isdst]</code> - (if <code>HAVE_TZNAME</code> is nonzero) to learn the correct time + use <code>strftime</code> with a <code>%Z</code> conversion specification + to learn the correct time zone abbreviation to use. </li> <li> Index: src/lib/libc/time/tz-link.html diff -u src/lib/libc/time/tz-link.html:1.17 src/lib/libc/time/tz-link.html:1.18 --- src/lib/libc/time/tz-link.html:1.17 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/tz-link.html Thu Jan 23 17:44:22 2025 @@ -194,9 +194,9 @@ After obtaining the code and data files, The code lets you compile the <code><abbr>tz</abbr></code> source files into machine-readable binary files, one for each location. The binary files are in a special format specified by -<a href="https://datatracker.ietf.org/doc/html/8536">The +<a href="https://www.rfc-editor.org/rfc/9636">The Time Zone Information Format (<abbr>TZif</abbr>)</a> -(Internet <abbr title="Request For Comments">RFC</abbr> 8536). +(Internet <abbr title="Request For Comments">RFC</abbr> 9636). The code also lets you read a <abbr>TZif</abbr> file and interpret timestamps for that location.</p> @@ -260,7 +260,7 @@ Studio Code</a>. </p> <p> For further information about updates, please see -<a href="https://datatracker.ietf.org/doc/html/rfc6557">Procedures for +<a href="https://www.rfc-editor.org/rfc/rfc6557">Procedures for Maintaining the Time Zone Database</a> (Internet <abbr>RFC</abbr> 6557). More detail can be found in <a href="theory.html">Theory and pragmatics of the @@ -379,26 +379,26 @@ calculates the current time difference b <li>The <a href="https://www.ietf.org">Internet Engineering Task Force</a>'s <a href="https://datatracker.ietf.org/wg/tzdist/charter/">Time Zone Data Distribution Service (tzdist) working group</a> defined <a -href="https://datatracker.ietf.org/doc/html/rfc7808">TZDIST</a> +href="https://www.rfc-editor.org/rfc/rfc7808">TZDIST</a> (Internet <abbr>RFC</abbr> 7808), a time zone data distribution service, -along with <a href="https://datatracker.ietf.org/doc/html/rfc7809">CalDAV</a> +along with <a href="https://www.rfc-editor.org/rfc/rfc7809">CalDAV</a> (Internet <abbr>RFC</abbr> 7809), a calendar access protocol for transferring time zone data by reference. <a href="https://devguide.calconnect.org/Time-Zones/TZDS/">TZDIST implementations</a> are available. The <a href="https://www.ietf.org/mailman/listinfo/tzdist-bis">tzdist-bis mailing list</a> discusses possible extensions.</li> -<li>The <a href="https://datatracker.ietf.org/doc/html/rfc5545"> +<li>The <a href="https://www.rfc-editor.org/rfc/rfc5545"> Internet Calendaring and Scheduling Core Object Specification (iCalendar)</a> (Internet <abbr>RFC</abbr> 5445) covers time zone data; see its VTIMEZONE calendar component. The iCalendar format requires specialized parsers and generators; a -variant <a href="https://datatracker.ietf.org/doc/html/rfc6321">xCal</a> +variant <a href="https://www.rfc-editor.org/rfc/rfc6321">xCal</a> (Internet <abbr>RFC</abbr> 6321) uses <a href="https://www.w3.org/XML/"><abbr title="Extensible Markup Language">XML</abbr></a> format, and a variant -<a href="https://datatracker.ietf.org/doc/html/rfc7265">jCal</a> +<a href="https://www.rfc-editor.org/rfc/rfc7265">jCal</a> (Internet <abbr>RFC</abbr> 7265) uses <a href="https://www.json.org/json-en.html"><abbr title="JavaScript Object Notation">JSON</abbr></a> format.</li> @@ -935,7 +935,13 @@ with perhaps the best-documented history <dt>United States</dt> <dd>The Department of Transportation's <a href="https://www.transportation.gov/regulations/recent-time-zone-proceedings">Recent -Time Zone Proceedings</a> lists changes to time zone boundaries.</dd> +Time Zone Proceedings</a> lists changes to +official written time zone boundaries, and its <a +href="https://geodata.bts.gov/datasets/usdot::time-zones/about">Time +Zones dataset</a> maps current boundaries. +These boundaries are only for standard time, so the current map puts +all of Arizona in one time zone even though part of Arizona +observes <abbr>DST</abbr> and part does not.</dd> <dt>Uruguay</dt> <dd>The Oceanography, Hydrography, and Meteorology Service of the Uruguayan Navy (SOHMA) publishes an annual <a @@ -994,7 +1000,8 @@ should we abolish Daylight Saving Time?< <em>J Biol Rhythms.</em> 2019;34(3):227–230. doi:<a href="https://doi.org/10.1177/0748730419854197">10.1177/0748730419854197</a>. The Society for Research on Biological Rhythms -opposes DST changes and permanent DST, and advocates that governments adopt +opposes <abbr>DST</abbr> changes and permanent <abbr>DST</abbr>, +and advocates that governments adopt "permanent Standard Time for the health and safety of their citizens".</li> </ul> </section> @@ -1023,7 +1030,7 @@ title="Institute of Electrical and Elect can achieve submicrosecond clock accuracy on a local area network with special-purpose hardware.</li> <li><a -href="https://datatracker.ietf.org/doc/html/rfc4833">Timezone +href="https://www.rfc-editor.org/rfc/rfc4833">Timezone Options for <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr></a> (Internet <abbr>RFC</abbr> 4833) specifies a <a @@ -1105,7 +1112,7 @@ the abovementioned <abbr>NTP</abbr> impl 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 -<a href="https://datatracker.ietf.org/doc/html/rfc8633#section-3.7.1">§3.7.1 of +<a href="https://www.rfc-editor.org/rfc/rfc8633#section-3.7.1">§3.7.1 of Network Time Protocol Best Current Practices</a> (Internet <abbr>RFC</abbr> 8633), leap smearing is not suitable for applications requiring accurate <abbr>UTC</abbr> or civil time, @@ -1165,16 +1172,16 @@ interchange – Part 1: Basic rules< <a href="https://www.w3.org/TR/xmlschema/#dateTime"><abbr>XML</abbr> Schema: Datatypes – dateTime</a> specifies a format inspired by <abbr>ISO</abbr> 8601 that is in common use in <abbr>XML</abbr> data.</li> -<li><a href="https://datatracker.ietf.org/doc/html/rfc5322#section-3.3">§3.3 of +<li><a href="https://www.rfc-editor.org/rfc/rfc5322#section-3.3">§3.3 of Internet Message Format</a> (Internet <abbr>RFC</abbr> 5322) specifies the time notation used in email and <a href="https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol"><abbr>HTTP</abbr></a> headers.</li> <li> -<a href="https://datatracker.ietf.org/doc/html/rfc3339">Date and Time +<a href="https://www.rfc-editor.org/rfc/rfc3339">Date and Time on the Internet: Timestamps</a> (Internet <abbr>RFC</abbr> 3339) specifies an <abbr>ISO</abbr> 8601 profile for use in new Internet protocols. -An extension, <a href="https://datatracker.ietf.org/doc/html/rfc9557">Date +An extension, <a href="https://www.rfc-editor.org/rfc/rfc9557">Date and Time on the Internet: Timestamps with Additional Information</a> (Internet <abbr>RFC</abbr> 9557) extends this profile to let you specify the <code><abbr>tzdb</abbr></code> timezone of a timestamp Index: src/lib/libc/time/tzfile.5 diff -u src/lib/libc/time/tzfile.5:1.36 src/lib/libc/time/tzfile.5:1.37 --- src/lib/libc/time/tzfile.5:1.36 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/tzfile.5 Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -.\" $NetBSD: tzfile.5,v 1.36 2024/09/11 13:50:34 christos Exp $ +.\" $NetBSD: tzfile.5,v 1.37 2025/01/23 22:44:22 christos Exp $ .\" .\" @(#)tzfile.5 8.3 .\" This file is in the public domain, so clarified as of @@ -16,7 +16,7 @@ are typically found under a directory wi .Pa /usr/share/zoneinfo . These files use the format described in Internet .Rs -.%R RFC 8536 +.%R RFC 9536 .Re Each file is a sequence of 8-bit bytes. In a file, a binary integer is represented by a sequence of one or @@ -154,7 +154,7 @@ Each pair denotes one leap second, eithe except that if the last pair has the same correction as the previous one, the last pair denotes the leap second table's expiration time. Each leap second is at the end of a UTC calendar month. -The first leap second has a nonnegative occurrence time, +The first leap second has a non-negative occurrence time, and is a positive leap second if and only if its correction is positive; the correction for each leap second after the first differs from the previous leap second by either @@ -230,7 +230,7 @@ after the last transition time stored in or for all instants if the file has no transitions. The TZ string is empty (i.e., nothing between the newlines) if there is no proleptic representation for such instants. -If nonempty, the TZ string must agree with the local time +If non-empty, the TZ string must agree with the local time type after the last transition time if present in the eight-byte data; for example, given the string .Dq WET0WEST,M3.5.0/1,M10.5.0 @@ -327,16 +327,16 @@ through 60 instead of the usual 59; the This section documents common problems in reading or writing TZif files. Most of these are problems in generating TZif files for use by older readers. -The goals of this section are: +The goals of this section are to help: .Bl -bullet .It -to help TZif writers output files that avoid common +TZif writers output files that avoid common pitfalls in older or buggy TZif readers, .It -to help TZif readers avoid common pitfalls when reading +TZif readers avoid common pitfalls when reading files generated by future TZif writers, and .It -to help any future specification authors see what sort of +any future specification authors see what sort of problems arise when the TZif format is changed. .El .Pp @@ -347,7 +347,7 @@ reader was designed for. When complete compatibility was not achieved, an attempt was made to limit glitches to rarely used timestamps and allow simple partial workarounds in writers designed to generate -new-version data useful even for older-version readers. +newer-version data useful even for older-version readers. This section attempts to document these compatibility issues and workarounds, as well as to document other common bugs in readers. @@ -402,9 +402,9 @@ for the next time zone east \(en e.g., for permanent Atlantic Standard Time (\-04). .It -Some readers designed for version 2 or 3, and that require strict -conformance to RFC 8536, reject version 4 files whose leap second -tables are truncated at the start or that end in expiration times. +Some readers designed for version 2 or 3 and that require strict +conformance to RFC 9536 reject version 4 files whose leap second +tables are truncated at the start or end in expiration times. .It Some readers ignore the footer, and instead predict future timestamps from the time type of the last transition. @@ -418,7 +418,7 @@ As a partial workaround, a writer can ou first transition at an early time. .It Some readers mishandle timestamps before the first -transition that has a timestamp not less than -2**31. +transition that has a timestamp that is not less than -2**31. Readers that support only 32-bit timestamps are likely to be more prone to this problem, for example, when they process 64-bit transitions only some of which are representable in 32 @@ -430,7 +430,7 @@ Some readers mishandle a transition if i the minimum possible signed 64-bit value. Timestamps less than \&-2**59 are not recommended. .It -Some readers mishandle TZ strings that +Some readers mishandle proleptic TZ strings that contain .Dq < or @@ -447,7 +447,7 @@ non-ASCII characters. These characters are not recommended. .Pp Some readers may mishandle time zone abbreviations that -contain fewer than 3 or more than 6 characters, or that +contain fewer than 3 or more than 6 characters or that contain ASCII characters other than alphanumerics, .Dq \&- . and @@ -472,7 +472,7 @@ abbreviations correctly. .It Some readers generate ambiguous timestamps for positive leap seconds that occur when the UTC offset is not a multiple of 60 seconds. -For example, in a timezone with UTC offset +01:23:45 and with +For example, with UTC offset +01:23:45 and with a positive leap second 78796801 (1972-06-30 23:59:60 UTC), some readers will map both 78796800 and 78796801 to 01:23:45 local time the next day instead of mapping the latter to 01:23:46, and they will map 78796815 to @@ -491,7 +491,7 @@ Developers of distributed applications s in mind if they need to deal with pre-1970 data. .It Some readers mishandle timestamps before the first -transition that has a nonnegative timestamp. +transition that has a non-negative timestamp. Readers that do not support negative timestamps are likely to be more prone to this problem. .It @@ -508,7 +508,7 @@ support locations like Kiritimati that a range. .It Some readers mishandle UT offsets in the range [\*-3599, \*-1] -seconds from UT, because they integer-divide the offset by +seconds from UT because they integer-divide the offset by 3600 to get 0 and then display the hour part as .Dq +00 . .It @@ -526,8 +526,8 @@ Future changes to the format may append .Rs .%A Olson A, Eggert P, Murchison K. .%T The Time Zone Information Format (TZif). -.%D Feb 2019. -.%U https://datatracker.ietf.org/doc/html/rfc8536 -.%U https://doi.org/10.17487/RFC8536 -.%R RFC 8536 +.%D October 2024. +%U https://\:www.rfc-editor.org/\:rfc/\:rfc9636 +%U https://doi.org/10.17487/RFC9536 +%R RFC 9536 .Re Index: src/lib/libc/time/tzselect.8 diff -u src/lib/libc/time/tzselect.8:1.14 src/lib/libc/time/tzselect.8:1.15 --- src/lib/libc/time/tzselect.8:1.14 Sat Sep 16 14:40:26 2023 +++ src/lib/libc/time/tzselect.8 Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -.\" $NetBSD: tzselect.8,v 1.14 2023/09/16 18:40:26 christos Exp $ +.\" $NetBSD: tzselect.8,v 1.15 2025/01/23 22:44:22 christos Exp $ .\" .\" This file is in the public domain, so clarified as of .\" 2009-05-17 by Arthur David Olson. @@ -6,8 +6,6 @@ .SH NAME tzselect \- select a timezone .SH SYNOPSIS -.ie \n(.g .ds - \f(CR-\fP -.el .ds - \- .ds d " degrees .ds m " minutes .ds s " seconds @@ -22,15 +20,15 @@ tzselect \- select a timezone .\} .B tzselect [ -.B \*-c +.B \-c .I coord ] [ -.B \*-n +.B \-n .I limit ] [ -.B \*-\*-help +.B \-\-help ] [ -.B \*-\*-version +.B \-\-version ] .SH DESCRIPTION The @@ -42,7 +40,7 @@ The output is suitable as a value for th All interaction with the user is done via standard input and standard error. .SH OPTIONS .TP -.BI "\*-c " coord +.BI "\-c " coord Instead of asking for continent and then country and then city, ask for selection from time zones whose largest cities are closest to the location with geographical coordinates @@ -72,27 +70,27 @@ seconds, with any trailing fractions rep .I SS is present) seconds. The decimal point is that of the current locale. For example, in the (default) C locale, -.B "\*-c\ +40.689\*-074.045" +.B "\-c\ +40.689\-074.045" specifies 40.689\*d\*_N, 74.045\*d\*_W, -.B "\*-c\ +4041.4\*-07402.7" +.B "\-c\ +4041.4\-07402.7" specifies 40\*d\*_41.4\*m\*_N, 74\*d\*_2.7\*m\*_W, and -.B "\*-c\ +404121\*-0740240" +.B "\-c\ +404121\-0740240" specifies 40\*d\*_41\*m\*_21\*s\*_N, 74\*d\*_2\*m\*_40\*s\*_W. If .I coord is not one of the documented forms, the resulting behavior is unspecified. .TP -.BI "\*-n " limit +.BI "\-n " limit When -.B \*-c +.B \-c is used, display the closest .I limit locations (default 10). .TP -.B "\*-\*-help" +.B "\-\-help" Output help information and exit. .TP -.B "\*-\*-version" +.B "\-\-version" Output version information and exit. .SH "ENVIRONMENT VARIABLES" .TP Index: src/lib/libc/time/version diff -u src/lib/libc/time/version:1.26 src/lib/libc/time/version:1.27 --- src/lib/libc/time/version:1.26 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/version Thu Jan 23 17:44:22 2025 @@ -1 +1 @@ -2024b +2025a Index: src/lib/libc/time/zdump.8 diff -u src/lib/libc/time/zdump.8:1.25 src/lib/libc/time/zdump.8:1.26 --- src/lib/libc/time/zdump.8:1.25 Wed Dec 6 16:45:11 2023 +++ src/lib/libc/time/zdump.8 Thu Jan 23 17:44:22 2025 @@ -1,9 +1,9 @@ -.\" $NetBSD: zdump.8,v 1.25 2023/12/06 21:45:11 kre Exp $ +.\" $NetBSD: zdump.8,v 1.26 2025/01/23 22:44:22 christos Exp $ .\" @(#)zdump.8 8.2 .\" This file is in the public domain, so clarified as of .\" 2009-05-17 by Arthur David Olson. .\" .TH zdump 8 -.Dd December 6, 2023 +.Dd January 23, 2025 .Dt ZDUMP 8 .Os .Sh NAME @@ -26,6 +26,14 @@ The program prints the current time in each .Ar timezone named on the command line. +A +.Dv timezone +of +.Dq \&- +is treated as if it were +.Pa /dev/stdin ; +this can be used to pipe TZif data into +.Nm zdump . .Sh OPTIONS .Bl -tag -width XXXXXXXXX -compact .It Fl \-version Index: src/lib/libc/time/zdump.c diff -u src/lib/libc/time/zdump.c:1.64 src/lib/libc/time/zdump.c:1.65 --- src/lib/libc/time/zdump.c:1.64 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/zdump.c Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: zdump.c,v 1.64 2024/09/11 13:50:34 christos Exp $ */ +/* $NetBSD: zdump.c,v 1.65 2025/01/23 22:44:22 christos Exp $ */ /* Dump time zone data in a textual format. */ /* @@ -8,7 +8,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: zdump.c,v 1.64 2024/09/11 13:50:34 christos Exp $"); +__RCSID("$NetBSD: zdump.c,v 1.65 2025/01/23 22:44:22 christos Exp $"); #endif /* !defined lint */ #ifndef NETBSD_INSPIRED @@ -19,10 +19,6 @@ __RCSID("$NetBSD: zdump.c,v 1.64 2024/09 #include "private.h" #include <stdio.h> -#ifndef HAVE_SNPRINTF -# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) -#endif - #ifndef HAVE_LOCALTIME_R # define HAVE_LOCALTIME_R 1 #endif @@ -153,17 +149,6 @@ sumsize(size_t a, size_t b) size_overflow(); } -/* Return the size of the string STR, including its trailing NUL. - Report an error and exit if this would exceed INDEX_MAX which means - pointer subtraction wouldn't work. */ -static ptrdiff_t -xstrsize(char const *str) -{ - size_t len = strlen(str); - if (len < INDEX_MAX) - return len + 1; - size_overflow(); -} /* Return a pointer to a newly allocated buffer of size SIZE, exiting on failure. SIZE should be positive. */ @@ -270,7 +255,7 @@ tzalloc(char const *val) static ptrdiff_t fakeenv0size; void *freeable = NULL; char **env = fakeenv, **initial_environ; - ptrdiff_t valsize = xstrsize(val); + ptrdiff_t valsize = strlen(val) + 1; if (fakeenv0size < valsize) { char **e = environ, **to; ptrdiff_t initial_nenvptrs = 1; /* Counting the trailing NULL pointer. */ @@ -281,7 +266,7 @@ tzalloc(char const *val) || INDEX_MAX < initial_nenvptrs) size_overflow(); # else - if (initial_nenvptrs == INDEX_MAX / sizeof *environ)) + if (initial_nenvptrs == INDEX_MAX / sizeof *environ) size_overflow(); initial_nenvptrs++; # endif @@ -425,7 +410,7 @@ saveabbr(char **buf, ptrdiff_t *bufalloc if (HAVE_LOCALTIME_RZ) return ab; else { - ptrdiff_t absize = xstrsize(ab); + ptrdiff_t absize = strlen(ab) + 1; if (*bufalloc < absize) { free(*buf); @@ -485,6 +470,7 @@ main(int argc, char *argv[]) time_t cuthitime; time_t now; bool iflag = false; + size_t arglenmax = 0; cutlotime = absolute_min_time; cuthitime = absolute_max_time; @@ -584,15 +570,21 @@ main(int argc, char *argv[]) now = time(NULL); now |= !now; } - longest = 0; for (i = optind; i < argc; i++) { - size_t arglen = strlen(argv[i]); - if (longest < arglen) - longest = min(arglen, INT_MAX); + size_t arglen = strlen(argv[i]); + if (arglenmax < arglen) + arglenmax = arglen; } + if (!HAVE_SETENV && INDEX_MAX <= arglenmax) + size_overflow(); + longest = min(arglenmax, INT_MAX - 2); for (i = optind; i < argc; ++i) { - timezone_t tz = tzalloc(argv[i]); + /* Treat "-" as standard input on platforms with /dev/stdin. + It's not worth the bother of supporting "-" on other + platforms, as that would need temp files. */ + timezone_t tz = tzalloc(strcmp(argv[i], "-") == 0 + ? "/dev/stdin" : argv[i]); char const *ab; time_t t; struct tm tm, newtm; @@ -692,7 +684,7 @@ yeartot(intmax_t y) return absolute_max_time; seconds = diff400 * SECSPER400YEARS; years = diff400 * 400; - } else { + } else { seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR; years = 1; } @@ -923,13 +915,10 @@ showextrema(timezone_t tz, char *zone, t } } -#if HAVE_SNPRINTF -# define my_snprintf snprintf -#else +/* On pre-C99 platforms, a snprintf substitute good enough for us. */ +#if !HAVE_SNPRINTF # include <stdarg.h> - -/* A substitute for snprintf that is good enough for zdump. */ -static int +ATTRIBUTE_FORMAT((printf, 3, 4)) static int my_snprintf(char *s, size_t size, char const *format, ...) { int n; @@ -957,6 +946,7 @@ my_snprintf(char *s, size_t size, char c va_end(args); return n; } +# define snprintf my_snprintf #endif /* Store into BUF, of size SIZE, a formatted local time taken from *TM. @@ -971,10 +961,10 @@ format_local_time(char *buf, ptrdiff_t s { int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour; return (ss - ? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss) + ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss) : mm - ? my_snprintf(buf, size, "%02d:%02d", hh, mm) - : my_snprintf(buf, size, "%02d", hh)); + ? snprintf(buf, size, "%02d:%02d", hh, mm) + : snprintf(buf, size, "%02d", hh)); } /* Store into BUF, of size SIZE, a formatted UT offset for the @@ -1009,10 +999,10 @@ format_utc_offset(char *buf, ptrdiff_t s mm = off / 60 % 60; hh = off / 60 / 60; return (ss || 100 <= hh - ? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss) + ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss) : mm - ? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm) - : my_snprintf(buf, size, "%c%02ld", sign, hh)); + ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm) + : snprintf(buf, size, "%c%02ld", sign, hh)); } /* Store into BUF (of size SIZE) a quoted string representation of P. @@ -1115,7 +1105,7 @@ istrftime(char *buf, ptrdiff_t size, cha for (abp = ab; is_alpha(*abp); abp++) continue; len = (!*abp && *ab - ? my_snprintf(b, s, "%s", ab) + ? snprintf(b, s, "%s", ab) : format_quoted_string(b, s, ab)); if (s <= len) return false; @@ -1123,7 +1113,7 @@ istrftime(char *buf, ptrdiff_t size, cha } formatted_len = (tm->tm_isdst - ? my_snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst) + ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst) : 0); } break; Index: src/lib/libc/time/zic.8 diff -u src/lib/libc/time/zic.8:1.51 src/lib/libc/time/zic.8:1.52 --- src/lib/libc/time/zic.8:1.51 Wed Sep 11 09:50:34 2024 +++ src/lib/libc/time/zic.8 Thu Jan 23 17:44:22 2025 @@ -1,9 +1,9 @@ -.\" $NetBSD: zic.8,v 1.51 2024/09/11 13:50:34 christos Exp $ +.\" $NetBSD: zic.8,v 1.52 2025/01/23 22:44:22 christos Exp $ .\" @(#)zic.8 8.6 .\" This file is in the public domain, so clarified as of .\" 2009-05-17 by Arthur David Olson. .\" .TH zic 8 -.Dd September 10, 2024 +.Dd January 21, 2025 .Dt ZIC 8 .Os .Sh NAME @@ -385,7 +385,15 @@ of years the rule would apply. .\" .It Ar IN Names the month in which the rule takes effect. -Month names may be abbreviated. +Month names may be abbreviated as mentioned previously; +for example, January can appear as +.Dq January , +.Dq JANU ++or +.Dq Ja , +but not as +.Dq j +which would be ambiguous with both June and July. .\" .It Ar ON Gives the day on which the rule takes effect. @@ -412,6 +420,12 @@ or a weekday name preceded by (e.g., .Ql lastSunday ) may be abbreviated or spelled out in full. +may be abbreviated as mentioned previously, +e.g., +.Dq Su +for Sunday and +.Dq lastsa +for the last Saturday. There must be no white space characters within the .Ar ON field. Index: src/lib/libc/time/zic.c diff -u src/lib/libc/time/zic.c:1.93 src/lib/libc/time/zic.c:1.94 --- src/lib/libc/time/zic.c:1.93 Thu Oct 3 23:18:01 2024 +++ src/lib/libc/time/zic.c Thu Jan 23 17:44:22 2025 @@ -1,4 +1,4 @@ -/* $NetBSD: zic.c,v 1.93 2024/10/04 03:18:01 rillig Exp $ */ +/* $NetBSD: zic.c,v 1.94 2025/01/23 22:44:22 christos Exp $ */ /* ** This file is in the public domain, so clarified as of ** 2006-07-17 by Arthur David Olson. @@ -11,7 +11,7 @@ #include <sys/cdefs.h> #ifndef lint -__RCSID("$NetBSD: zic.c,v 1.93 2024/10/04 03:18:01 rillig Exp $"); +__RCSID("$NetBSD: zic.c,v 1.94 2025/01/23 22:44:22 christos Exp $"); #endif /* !defined lint */ /* Use the system 'time' function, instead of any private replacement. @@ -31,9 +31,6 @@ __RCSID("$NetBSD: zic.c,v 1.93 2024/10/0 #include <stdio.h> #include <unistd.h> #include <util.h> -#define emalloc zic_malloc -#define erealloc zic_realloc -#define estrdup zic_strdup typedef int_fast64_t zic_t; static zic_t const @@ -179,13 +176,8 @@ symlink(char const *target, char const * } #endif #ifndef AT_SYMLINK_FOLLOW -# if HAVE_LINK -# define linkat(targetdir, target, linknamedir, linkname, flag) \ - (itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname)) -# else # define linkat(targetdir, target, linknamedir, linkname, flag) \ (errno = ENOTSUP, -1) -# endif #endif static void addtt(zic_t starttime, int type); @@ -205,7 +197,7 @@ static void inrule(char ** fields, int n static bool inzcont(char ** fields, int nfields); static bool inzone(char ** fields, int nfields); static bool inzsub(char **, int, bool); -static bool itssymlink(char const *); +static int itssymlink(char const *, int *); static bool is_alpha(char a); static char lowerit(char); static void mkdirs(char const *, bool); @@ -541,19 +533,19 @@ memcheck(void *ptr) } static void * -emalloc(size_t size) +xmalloc(size_t size) { return memcheck(malloc(size)); } static void * -erealloc(void *ptr, size_t size) +xrealloc(void *ptr, size_t size) { return memcheck(realloc(ptr, size)); } static char * -estrdup(char const *str) +xstrdup(char const *str) { return memcheck(strdup(str)); } @@ -582,7 +574,7 @@ growalloc(void *ptr, size_t itemsize, pt { return (nitems < *nitems_alloc ? ptr - : erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize))); + : xrealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize))); } /* @@ -914,7 +906,8 @@ static zic_t const max_time = MAXVAL(zic static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); -/* The time specified by the -R option, defaulting to MIN_TIME. */ +/* The time specified by the -R option, defaulting to MIN_TIME; + or lo_time, whichever is greater. */ static zic_t redundant_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); /* The time specified by an Expires line, or negative if no such line. */ @@ -987,8 +980,8 @@ want_bloat(void) int main(int argc, char **argv) { - int c, k; - ptrdiff_t i, j; + register int c, k; + register ptrdiff_t i, j; bool timerange_given = false; #ifdef S_IWGRP @@ -1038,7 +1031,8 @@ main(int argc, char **argv) directory = optarg; else { fprintf(stderr, -_("%s: More than one -d option specified\n"), + _("%s: More than one -d option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -1048,7 +1042,8 @@ _("%s: More than one -d option specified lcltime = optarg; else { fprintf(stderr, -_("%s: More than one -l option specified\n"), + _("%s: More than one -l option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -1058,7 +1053,8 @@ _("%s: More than one -l option specified psxrules = optarg; else { fprintf(stderr, -_("%s: More than one -p option specified\n"), + _("%s: More than one -p option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -1081,7 +1077,8 @@ _("%s: More than one -p option specified leapsec = optarg; else { fprintf(stderr, -_("%s: More than one -L option specified\n"), + _("%s: More than one -L option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -1092,7 +1089,8 @@ _("%s: More than one -L option specified case 'r': if (timerange_given) { fprintf(stderr, -_("%s: More than one -r option specified\n"), + _("%s: More than one -r option" + " specified\n"), progname); return EXIT_FAILURE; } @@ -1121,6 +1119,8 @@ _("%s: invalid time range: %s\n"), fprintf(stderr, _("%s: -R time exceeds -r cutoff\n"), progname); return EXIT_FAILURE; } + if (redundant_time < lo_time) + redundant_time = lo_time; if (bloat == 0) { static char const bloat_default[] = ZIC_BLOAT_DEFAULT; if (strcmp(bloat_default, "slim") == 0) @@ -1209,7 +1209,7 @@ componentcheck(char const *name, char co static bool namecheck(const char *name) { - char const *cp; + register char const *cp; /* Benign characters in a portable file name. */ static char const benign[] = @@ -1222,7 +1222,7 @@ namecheck(const char *name) static char const printable_and_not_benign[] = " !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~"; - char const *component = name; + register char const *component = name; for (cp = name; *cp; cp++) { unsigned char c = *cp; if (noise && !strchr(benign, c)) { @@ -1253,10 +1253,7 @@ get_rand_u64(void) s = getrandom(entropy_buffer, sizeof entropy_buffer, 0); while (s < 0 && errno == EINTR); - if (s < 0) - nwords = -1; - else - nwords = s / sizeof *entropy_buffer; + nwords = s < 0 ? -1 : (int)(s / sizeof *entropy_buffer); } if (0 < nwords) return entropy_buffer[--nwords]; @@ -1333,7 +1330,7 @@ random_dirent(char const **name, char ** uint_fast64_t unfair_min = - ((UINTMAX_MAX % base__6 + 1) % base__6); if (!dst) { - dst = emalloc(size_sum(dirlen, prefixlen + suffixlen + 1)); + dst = xmalloc(size_sum(dirlen, prefixlen + suffixlen + 1)); memcpy(dst, src, dirlen); memcpy(dst + dirlen, prefix, prefixlen); dst[dirlen + prefixlen + suffixlen] = '\0'; @@ -1405,9 +1402,9 @@ rename_dest(char *tempname, char const * } } -/* Create symlink contents suitable for symlinking FROM to TO, as a - freshly allocated string. FROM should be a relative file name, and - is relative to the global variable DIRECTORY. TO can be either +/* Create symlink contents suitable for symlinking TARGET to LINKNAME, as a + freshly allocated string. TARGET should be a relative file name, and + is relative to the global variable DIRECTORY. LINKNAME can be either relative or absolute. */ static char * relname(char const *target, char const *linkname) @@ -1422,7 +1419,7 @@ relname(char const *target, char const * size_t lenslash = len + (len && directory[len - 1] != '/'); size_t targetsize = strlen(target) + 1; linksize = size_sum(lenslash, targetsize); - f = result = emalloc(linksize); + f = result = xmalloc(linksize); memcpy(result, directory, len); result[len] = '/'; memcpy(result + lenslash, target, targetsize); @@ -1436,7 +1433,7 @@ relname(char const *target, char const * dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1); if (dotdotetcsize <= linksize) { if (!result) - result = emalloc(dotdotetcsize); + result = xmalloc(dotdotetcsize); for (i = 0; i < dotdots; i++) memcpy(result + 3 * i, "../", 3); memmove(result + 3 * dotdots, f + dir_len, taillen + 1); @@ -1444,6 +1441,18 @@ relname(char const *target, char const * return result; } +/* 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 +same_parent_dirs(char const *a, char const *b) +{ + for (; *a == *b; a++, b++) + if (!*a) + return true; + return ! (strchr(a, '/') || strchr(b, '/')); +} + static void dolink(char const *target, char const *linkname, bool staysymlink) { @@ -1451,6 +1460,7 @@ dolink(char const *target, char const *l int link_errno; char *tempname = NULL; char const *outname = linkname; + int targetissym = -2, linknameissym = -2; check_for_signal(); @@ -1472,13 +1482,32 @@ dolink(char const *target, char const *l break; } link_errno = errno; + /* Linux 2.6.16 and 2.6.17 mishandle AT_SYMLINK_FOLLOW. */ + if (link_errno == EINVAL) + link_errno = ENOTSUP; +#if HAVE_LINK + /* If linkat is not supported, fall back on link(A, B). + However, skip this if A is a relative symlink + and A and B might not have the same parent directory. + On some platforms link(A, B) does not follow a symlink A, + and if A is relative it might misbehave elsewhere. */ + if (link_errno == ENOTSUP + && (same_parent_dirs(target, outname) + || 0 <= itssymlink(target, &targetissym))) { + if (link(target, outname) == 0) { + link_errno = 0; + break; + } + link_errno = errno; + } +#endif if (link_errno == EXDEV || link_errno == ENOTSUP) break; if (link_errno == EEXIST) { staysymlink &= !tempname; random_dirent(&outname, &tempname); - if (staysymlink && itssymlink(linkname)) + if (staysymlink && itssymlink(linkname, &linknameissym)) break; } else if (link_errno == ENOENT && !linkdirs_made) { mkdirs(linkname, true); @@ -1541,12 +1570,17 @@ dolink(char const *target, char const *l rename_dest(tempname, linkname); } -/* Return true if NAME is a symbolic link. */ -static bool -itssymlink(char const *name) +/* Return 1 if NAME is an absolute symbolic link, -1 if it is relative, + 0 if it is not a symbolic link. If *CACHE is not -2, it is the + cached result of a previous call to this function with the same NAME. */ +static int +itssymlink(char const *name, int *cache) { - char c; - return 0 <= readlink(name, &c, 1); + if (*cache == -2) { + char c = '\0'; + *cache = readlink(name, &c, 1) < 0 ? 0 : c == '/' ? 1 : -1; + } + return *cache; } /* @@ -1567,9 +1601,9 @@ rcomp(const void *cp1, const void *cp2) static void associate(void) { - struct zone * zp; - struct rule * rp; - ptrdiff_t i, j, base, out; + register struct zone * zp; + register struct rule * rp; + register ptrdiff_t i, j, base, out; if (1 < nrules) { qsort(rules, (size_t)nrules, sizeof *rules, rcomp); @@ -1671,10 +1705,10 @@ inputline(FILE *fp, char *buf, ptrdiff_t static void infile(int fnum, char const *name) { - FILE * fp; - const struct lookup * lp; - bool wantcont; - lineno num; + register FILE * fp; + register const struct lookup * lp; + register bool wantcont; + register lineno num; if (strcmp(name, "-") == 0) { fp = stdin; @@ -1841,8 +1875,8 @@ inrule(char **fields, int nfields) fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD])) return; - r.r_name = estrdup(fields[RF_NAME]); - r.r_abbrvar = estrdup(fields[RF_ABBRVAR]); + r.r_name = xstrdup(fields[RF_NAME]); + r.r_abbrvar = xstrdup(fields[RF_ABBRVAR]); if (max_abbrvar_len < strlen(r.r_abbrvar)) max_abbrvar_len = strlen(r.r_abbrvar); rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc); @@ -1852,22 +1886,20 @@ inrule(char **fields, int nfields) static bool inzone(char **fields, int nfields) { - ptrdiff_t i; + register ptrdiff_t i; if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) { error(_("wrong number of fields on Zone line")); return false; } if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0) { - error( -_("\"Zone %s\" line and -l option are mutually exclusive"), + error(_("\"Zone %s\" line and -l option are mutually exclusive"), tzdefault); return false; } if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) { - error( -_("\"Zone %s\" line and -p option are mutually exclusive"), - TZDEFRULES); + error(_("\"Zone %s\" line and -p option are mutually exclusive"), + TZDEFRULES); return false; } for (i = 0; i < nzones; ++i) @@ -1896,14 +1928,14 @@ inzcont(char **fields, int nfields) static bool inzsub(char **fields, int nfields, bool iscont) { - char * cp; + register char * cp; char * cp1; struct zone z; int format_len; - int i_stdoff, i_rule, i_format; - int i_untilyear, i_untilmonth; - int i_untilday, i_untiltime; - bool hasuntil; + register int i_stdoff, i_rule, i_format; + register int i_untilyear, i_untilmonth; + register int i_untilday, i_untiltime; + register bool hasuntil; if (iscont) { i_stdoff = ZFC_STDOFF; @@ -1927,7 +1959,8 @@ inzsub(char **fields, int nfields, bool z.z_filenum = filenum; z.z_linenum = linenum; z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset")); - if ((cp = strchr(fields[i_format], '%')) != 0) { + cp = strchr(fields[i_format], '%'); + if (cp) { if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%') || strchr(fields[i_format], '/')) { error(_("invalid abbreviation format")); @@ -1960,15 +1993,14 @@ inzsub(char **fields, int nfields, bool 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" - )); + error(_("Zone continuation line end time is" + " not after end time of previous line")); return false; } } - z.z_name = iscont ? NULL : estrdup(fields[ZF_NAME]); - z.z_rule = estrdup(fields[i_rule]); - z.z_format = cp1 = estrdup(fields[i_format]); + z.z_name = iscont ? NULL : xstrdup(fields[ZF_NAME]); + z.z_rule = xstrdup(fields[i_rule]); + z.z_format = cp1 = xstrdup(fields[i_format]); if (z.z_format_specifier == 'z') { cp1[cp - fields[i_format]] = 's'; if (noise) @@ -1987,9 +2019,9 @@ inzsub(char **fields, int nfields, bool static zic_t getleapdatetime(char **fields, bool expire_line) { - const char * cp; - const struct lookup * lp; - zic_t i, j; + register const char * cp; + register const struct lookup * lp; + register zic_t i, j; zic_t year; int month, day; zic_t dayoff, tod; @@ -2111,8 +2143,8 @@ inlink(char **fields, int nfields) return; l.l_filenum = filenum; l.l_linenum = linenum; - l.l_target = estrdup(fields[LF_TARGET]); - l.l_linkname = estrdup(fields[LF_LINKNAME]); + l.l_target = xstrdup(fields[LF_TARGET]); + l.l_linkname = xstrdup(fields[LF_LINKNAME]); links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc); links[nlinks++] = l; } @@ -2122,10 +2154,10 @@ rulesub(struct rule *rp, const char *loy const char *typep, const char *monthp, const char *dayp, const char *timep) { - const struct lookup * lp; - const char * cp; - char * dp; - char * ep; + register const struct lookup * lp; + register const char * cp; + register char * dp; + register char * ep; char xs; if ((lp = byword(monthp, mon_names)) == NULL) { @@ -2135,7 +2167,7 @@ rulesub(struct rule *rp, const char *loy rp->r_month = lp->l_value; rp->r_todisstd = false; rp->r_todisut = false; - dp = estrdup(timep); + dp = xstrdup(timep); if (*dp != '\0') { ep = dp + strlen(dp) - 1; switch (lowerit(*ep)) { @@ -2210,19 +2242,23 @@ rulesub(struct rule *rp, const char *loy ** Sun<=20 ** Sun>=7 */ - dp = estrdup(dayp); + dp = xstrdup(dayp); if ((lp = byword(dp, lasts)) != NULL) { rp->r_dycode = DC_DOWLEQ; rp->r_wday = lp->l_value; rp->r_dayofmonth = len_months[1][rp->r_month]; } else { - if ((ep = strchr(dp, '<')) != 0) - rp->r_dycode = DC_DOWLEQ; - else if ((ep = strchr(dp, '>')) != 0) - rp->r_dycode = DC_DOWGEQ; + ep = strchr(dp, '<'); + if (ep) + rp->r_dycode = DC_DOWLEQ; else { + ep = strchr(dp, '>'); + if (ep) + rp->r_dycode = DC_DOWGEQ; + else { ep = dp; rp->r_dycode = DC_DOM; + } } if (rp->r_dycode != DC_DOM) { *ep++ = 0; @@ -2253,8 +2289,8 @@ rulesub(struct rule *rp, const char *loy static void convert(uint_fast32_t val, char *buf) { - int i; - int shift; + register int i; + register int shift; unsigned char *const b = (unsigned char *) buf; for (i = 0, shift = 24; i < 4; ++i, shift -= 8) @@ -2264,8 +2300,8 @@ convert(uint_fast32_t val, char *buf) static void convert64(uint_fast64_t val, char *buf) { - int i; - int shift; + register int i; + register int shift; unsigned char *const b = (unsigned char *) buf; for (i = 0, shift = 56; i < 8; ++i, shift -= 8) @@ -2355,16 +2391,16 @@ static void writezone(const char *const name, const char *const string, char version, int defaulttype) { - FILE * fp; - ptrdiff_t i, j; - int pass; + register FILE * fp; + register ptrdiff_t i, j; + register int pass; char *tempname = NULL; char const *outname = name; /* Allocate the ATS and TYPES arrays via a single malloc, as this is a bit faster. Do not malloc(0) if !timecnt, as that might return NULL even on success. */ - zic_t *ats = emalloc(align_to(size_product(timecnt + !timecnt, + zic_t *ats = xmalloc(align_to(size_product(timecnt + !timecnt, sizeof *ats + 1), alignof(zic_t))); void *typesptr = ats + timecnt; @@ -2475,8 +2511,8 @@ writezone(const char *const name, const fp = open_outfile(&outname, &tempname); for (pass = 1; pass <= 2; ++pass) { - ptrdiff_t thistimei, thistimecnt, thistimelim; - int thisleapi, thisleapcnt, thisleaplim; + register ptrdiff_t thistimei, thistimecnt, thistimelim; + register int thisleapi, thisleapcnt, thisleaplim; struct tzhead tzh; int pretranstype = -1, thisdefaulttype; bool locut, hicut, thisleapexpiry; @@ -2567,7 +2603,7 @@ writezone(const char *const name, const ** set correctly). */ if (want_bloat()) { - int mrudst, mrustd, hidst, histd, type; + register int mrudst, mrustd, hidst, histd, type; hidst = histd = mrudst = mrustd = -1; if (0 <= pretranstype) { @@ -2625,7 +2661,7 @@ writezone(const char *const name, const indmap[i] = -1; thischarcnt = stdcnt = utcnt = 0; for (i = old0; i < typecnt; i++) { - char * thisabbr; + register char * thisabbr; if (omittype[i]) continue; @@ -2714,7 +2750,7 @@ writezone(const char *const name, const (size_t) thischarcnt, fp); thisleaplim = thisleapi + thisleapcnt; for (i = thisleapi; i < thisleaplim; ++i) { - zic_t todo; + register zic_t todo; if (roll[i]) { if (timecnt == 0 || trans[i] < ats[0]) { @@ -2739,7 +2775,7 @@ writezone(const char *const name, const if (thisleapexpiry) { /* Append a no-op leap correction indicating when the leap second table expires. Although this does not conform to - Internet RFC 8536, most clients seem to accept this and + Internet RFC 9636, most clients seem to accept this and the plan is to amend the RFC to allow this in version 4 TZif files. */ puttzcodepass(leapexpires, fp, pass); @@ -2802,10 +2838,10 @@ static ptrdiff_t doabbr(char *abbr, size_t abbrlen, struct zone const *zp, const char *letters, bool isdst, zic_t save, bool doquotes) { - char * cp; - const char *slashp; + register char * cp; + register const char *slashp; ptrdiff_t len; - const char *format = zp->z_format; + char const *format = zp->z_format; slashp = strchr(format, '/'); if (slashp == NULL) { @@ -2849,9 +2885,9 @@ updateminmax(const zic_t x) static int stringoffset(char *result, int resultlen, zic_t offset) { - int hours; - int minutes; - int seconds; + register int hours; + register int minutes; + register int seconds; bool negative = offset < 0; int len = negative; @@ -2882,8 +2918,8 @@ stringoffset(char *result, int resultlen static int stringrule(char *result, int resultlen, struct rule *const rp, zic_t save, const zic_t stdoff) { - zic_t tod = rp->r_tod; - int compat = 0, len = 0; + register zic_t tod = rp->r_tod; + register int compat = 0, len = 0; if (rp->r_dycode == DC_DOM) { int month, total; @@ -2899,9 +2935,9 @@ stringrule(char *result, int resultlen, else len += snprintf(result + len, resultlen - len, "J%d", total + rp->r_dayofmonth); } else { - int week; - int wday = rp->r_wday; - int wdayoff; + register int week; + register int wday = rp->r_wday; + register int wdayoff; if (rp->r_dycode == DC_DOWGEQ) { wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK; @@ -2971,13 +3007,13 @@ static int stringzone(char *result, int resultlen, const struct zone *const zpfirst, const int zonecount) { - const struct zone * zp; - struct rule * rp; - struct rule * stdrp; - struct rule * dstrp; - ptrdiff_t i; - int compat = 0; - int c; + register const struct zone * zp; + register struct rule * rp; + register struct rule * stdrp; + register struct rule * dstrp; + register ptrdiff_t i; + register int compat = 0; + register int c; int offsetlen; struct rule stdr, dstr; ptrdiff_t len; @@ -2989,7 +3025,7 @@ stringzone(char *result, int resultlen, result[0] = '\0'; - /* Internet RFC 8536 section 5.1 says to use an empty TZ string if + /* Internet RFC 9636 section 6.1 says to use an empty TZ string if future timestamps are truncated. */ if (hi_time < max_time) return -1; @@ -3095,19 +3131,20 @@ stringzone(char *result, int resultlen, static void outzone(const struct zone *zpfirst, ptrdiff_t zonecount) { - ptrdiff_t i, j; - zic_t starttime, untiltime; - bool startttisstd; - bool startttisut; - char * startbuf; - char * ab; - char * envvar; - size_t max_abbr_len; - size_t max_envvar_len; - int compat; - bool do_extend; - char version; - ptrdiff_t lastatmax = -1; + register ptrdiff_t i, j; + register zic_t starttime, untiltime; + register bool startttisstd; + register bool startttisut; + register char * startbuf; + register char * ab; + register char * envvar; + register size_t max_abbr_len; + register size_t max_envvar_len; + register int compat; + register bool do_extend; + register char version; + zic_t nonTZlimtime = ZIC_MIN; + int nonTZlimtype = -1; zic_t max_year0; int defaulttype = -1; @@ -3117,9 +3154,9 @@ outzone(const struct zone *zpfirst, ptrd max_abbr_len = 2 + max_format_len + max_abbrvar_len; max_envvar_len = 2 * max_abbr_len + 5 * 9; - startbuf = zic_malloc(max_abbr_len + 1); - ab = zic_malloc(max_abbr_len + 1); - envvar = zic_malloc(max_envvar_len + 1); + startbuf = xmalloc(max_abbr_len + 1); + ab = xmalloc(max_abbr_len + 1); + envvar = xmalloc(max_envvar_len + 1); INITIALIZE(untiltime); INITIALIZE(starttime); /* @@ -3194,7 +3231,6 @@ outzone(const struct zone *zpfirst, ptrd unspecifiedtype = addtype(0, "-00", false, false, false); for (i = 0; i < zonecount; ++i) { - struct rule *prevrp = NULL; /* ** A guess that may well be corrected later. */ @@ -3204,8 +3240,6 @@ outzone(const struct zone *zpfirst, ptrd bool useuntil = i < (zonecount - 1); zic_t stdoff = zp->z_stdoff; zic_t startoff = stdoff; - zic_t prevktime; - INITIALIZE(prevktime); if (useuntil && zp->z_untiltime <= min_time) continue; eat(zp->z_filenum, zp->z_linenum); @@ -3220,6 +3254,10 @@ outzone(const struct zone *zpfirst, ptrd startttisut); if (usestart) { addtt(starttime, type); + if (useuntil && nonTZlimtime < starttime) { + nonTZlimtime = starttime; + nonTZlimtype = type; + } usestart = false; } else defaulttype = type; @@ -3249,9 +3287,9 @@ outzone(const struct zone *zpfirst, ptrd } } for ( ; ; ) { - ptrdiff_t k; - zic_t jtime, ktime; - zic_t offset; + register ptrdiff_t k; + register zic_t jtime, ktime; + register zic_t offset; struct rule *rp; int type; @@ -3351,23 +3389,16 @@ outzone(const struct zone *zpfirst, ptrd doabbr(ab, max_abbr_len + 1, zp, rp->r_abbrvar, rp->r_isdst, rp->r_save, false); offset = oadd(zp->z_stdoff, rp->r_save); - if (!want_bloat() && !useuntil && !do_extend - && prevrp && lo_time <= prevktime - && redundant_time <= ktime - && rp->r_hiyear == ZIC_MAX - && prevrp->r_hiyear == ZIC_MAX) - break; type = addtype(offset, ab, rp->r_isdst, rp->r_todisstd, rp->r_todisut); if (defaulttype < 0 && !rp->r_isdst) defaulttype = type; - if (rp->r_hiyear == ZIC_MAX - && ! (0 <= lastatmax - && ktime < attypes[lastatmax].at)) - lastatmax = timecnt; addtt(ktime, type); - prevrp = rp; - prevktime = ktime; + if (nonTZlimtime < ktime + && (useuntil || rp->r_hiyear != ZIC_MAX)) { + nonTZlimtime = ktime; + nonTZlimtype = type; + } } } } @@ -3379,7 +3410,8 @@ outzone(const struct zone *zpfirst, ptrd isdst, save, false); eat(zp->z_filenum, zp->z_linenum); if (*startbuf == '\0') -error(_("can't determine time zone abbreviation to use just after until time")); + error(_("can't determine time zone abbreviation" + " to use just after until time")); else { int type = addtype(startoff, startbuf, isdst, startttisstd, startttisut); @@ -3403,8 +3435,34 @@ error(_("can't determine time zone abbre } if (defaulttype < 0) defaulttype = 0; - if (0 <= lastatmax) - attypes[lastatmax].dontmerge = true; + if (!do_extend && !want_bloat()) { + /* Keep trailing transitions that are no greater than this. */ + zic_t keep_at_max; + + /* The earliest transition into a time governed by the TZ string. */ + zic_t TZstarttime = ZIC_MAX; + for (i = 0; i < timecnt; i++) { + zic_t at = attypes[i].at; + if (nonTZlimtime < at && at < TZstarttime) + TZstarttime = at; + } + if (TZstarttime == ZIC_MAX) + TZstarttime = nonTZlimtime; + + /* Omit trailing transitions deducible from the TZ string, + and not needed for -r or -R. */ + keep_at_max = max(TZstarttime, redundant_time); + for (i = j = 0; i < timecnt; i++) + if (attypes[i].at <= keep_at_max) { + attypes[j].at = attypes[i].at; + attypes[j].dontmerge = (attypes[i].at == TZstarttime + && (nonTZlimtype != attypes[i].type + || strchr(envvar, ','))); + attypes[j].type = attypes[i].type; + j++; + } + timecnt = j; + } if (do_extend) { /* ** If we're extending the explicitly listed observations for @@ -3492,7 +3550,7 @@ addtype(zic_t utoff, char const *abbr, b static void leapadd(zic_t t, int correction, int rolling) { - int i; + register int i; if (TZ_MAX_LEAPS <= leapcnt) { error(_("too many leap seconds")); @@ -3517,9 +3575,9 @@ leapadd(zic_t t, int correction, int rol static void adjleap(void) { - int i; - zic_t last = 0; - zic_t prevtrans = 0; + register int i; + register zic_t last = 0; + register zic_t prevtrans = 0; /* ** propagate leap seconds forward @@ -3595,7 +3653,7 @@ lowerit(char a) /* case-insensitive equality */ ATTRIBUTE_PURE_114833 static bool -ciequal(const char *ap, const char *bp) +ciequal(register const char *ap, register const char *bp) { while (lowerit(*ap) == lowerit(*bp++)) if (*ap++ == '\0') @@ -3604,7 +3662,7 @@ ciequal(const char *ap, const char *bp) } ATTRIBUTE_PURE_114833 static bool -itsabbr(const char *abbr, const char *word) +itsabbr(register const char *abbr, register const char *word) { if (lowerit(*abbr) != lowerit(*word)) return false; @@ -3633,8 +3691,8 @@ ciprefix(char const *abbr, char const *w static const struct lookup * byword(const char *word, const struct lookup *table) { - const struct lookup * foundlp; - const struct lookup * lp; + register const struct lookup * foundlp; + register const struct lookup * lp; if (word == NULL || table == NULL) return NULL; @@ -3688,8 +3746,8 @@ byword(const char *word, const struct lo static int getfields(char *cp, char **array, int arrayelts) { - char * dp; - int nsubs; + register char * dp; + register int nsubs; nsubs = 0; for ( ; ; ) { @@ -3767,9 +3825,9 @@ tadd(zic_t t1, zic_t t2) static zic_t rpytime(const struct rule *rp, zic_t wantedy) { - int m, i; - zic_t dayoff; /* with a nod to Margaret O. */ - zic_t t, y; + register int m, i; + register zic_t dayoff; /* with a nod to Margaret O. */ + register zic_t t, y; int yrem; if (wantedy == ZIC_MIN) @@ -3844,10 +3902,10 @@ will not work with pre-2004 versions of static void newabbr(const char *string) { - int i; + register int i; if (strcmp(string, GRANDPARENTED) != 0) { - const char * cp; + register const char * cp; const char * mp; cp = string; @@ -3880,7 +3938,7 @@ mp = _("time zone abbreviation differs f static void mkdirs(char const *argname, bool ancestors) { - char *name = estrdup(argname); + char *name = xstrdup(argname); char *cp = name; /* On MS-Windows systems, do not worry about drive letters or