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(&amp;clock)-&gt;tm_zone</code>
     (if <code>TM_ZONE</code> is defined) or
-    <code>tzname[localtime(&amp;clock)-&gt;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&ndash;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";>&sect;3.7.1 of
+<a href="https://www.rfc-editor.org/rfc/rfc8633#section-3.7.1";>&sect;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 &ndash; Part 1: Basic rules<
 <a href="https://www.w3.org/TR/xmlschema/#dateTime";><abbr>XML</abbr>
 Schema: Datatypes &ndash; 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";>&sect;3.3 of
+<li><a href="https://www.rfc-editor.org/rfc/rfc5322#section-3.3";>&sect;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

Reply via email to