From 7abdc14abd46937b4a3920723f3dd702ba617e61 Mon Sep 17 00:00:00 2001
From: Stuart Bishop <stuart@stuartbishop.net>
Date: Fri, 31 Jan 2025 12:02:28 +1100
Subject: [PATCH] Squashed 'tz/' changes from 6903dde39e..5ad5cfba5b
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

a8e2fcd87b Release 2025a
4ab8692704 Fix bugs in -Wcast-qual pacification
1ee9daa918 Pacify gcc -Wcast-qual
e8920e76fc Rename emalloc to xmalloc.
cda7ec0702 Alias asctime, ctime result
edbabecc14 Fix ctime conformace bug
7db03d5b98 Add missing zdump.o dependency
e8e1a3d25b NetBSD defines STD_INSPIRED functions
d89a7468e3 newctime doc improvements
da932ff7ec Stop using \*- in man pages
b6adb83ce3 Warn about 2-digit years in strftime man page
562a1f1631 Modernize date man page
9ab3f52d34 No leap second on 2025-06-30
e5aaf1c7fe For timestamps compare to HEAD not to index
aa8a059d13 State the duplicate guideline more clearly
cb537f2001 Suggest -Dssize_t=int, not long
352dcdf9e3 Port S_ISREG to ancient UNIX, recent MS-Windows
3411494cc7 Define _CRT_DECLARE_NONSTDC_NAMES for MS-Windows
7d776e798c Port ‘utc’ to MS-Windows
7c90916644 Define NOMINMAX for MS-Windows
94931e3006 Fix asctime.o, strftime.o dependencies
d23b9bf5fd Fix to2050.tzs timestamp in distributed tarball
24a4d97fc0 'zdump -' now reads from stdin
6d77c92872 Bring back zdump on a pipe
f5d6c1d7d9 Invalid TZ now abbreviates as "-00" not "UTC"
a7ad244f30 Check for TZ naming a device
43cc9b6149 Fix unlikely multithreaded file descriptor leak
b208d5841e Etc/Unknown is now reserved
13bc796952 Update USDOT map description
de6dab1b23 Fix checknow confusing diagnostics
5e95797d9a * tz-link.html: Mention DoT geodata (thanks to Roozbeh Pournader).
3862447e2d Optimize asctime snprintf calls
161563657b Port recent asctime snprintf changes to NetBSD
e6d6bc3e45 Pacify gcc -Wsuggest-attribute=format sans snprintf in zdump
9955786265 TZNAME_MAXIMUM defaults to 254, not 255
fe5be99d8d Be more consistent about macro true/false vs 1/0
3d5e7acb88 Port asctime_r to POSIX.1-2017 and earlier
31f483a149 Remove dependency of asctime on strftime
7ef7ed06b2 Simplify timeoff redefinition
1bd67a4b75 Move MKTIME_MIGHT_OVERFLOW definition
67f7e8ab9c Pacify GCC 15ish -Wzero-as-null-pointer-constant
535a4e8b25 Pacify GCC 15ish -Wleading-whitespace=blanks
59ae22db9b Revert zone.tab changes for Concordia, EBO
abb83041a2 Fix mktime/timeoff overflow bug
0706ef0bf8 Move iinntt definition
fa004d323c Add two research stations to zone*.tab
38eea0e263 Reorder australia comments
77820eb701 Mention the Eyre Bird Observatory
d9e7a42f11 Avoid time_t + int overflow
ea814e998d strftime %s no longer is limited to time_t range
b2ee68e092 Simplify AmigaOS support
41e5344e6f Fix bug near the year 2**31 - 1 - 1900
4e1de2496e Pacify gcc -Wsuggest-attribute=const
ebd2ed9235 Don’t define _FILE_OFFSET_BITS if _TIME_BITS
26a649a19e Improve zdump overflow checking
81bab74a7f * northamerica: Also mention Sandford Fleming and Cleveland Abbe.
cfacf5ea70 strftime now outputs unknown conversions as-is
9c8221d79c * private.h: Fix timeoff comment.
705dc023c2 Remove now-dead strftime code
d8b6fe65c9 Fix Asia/Manila post-1990 typo
3a83f10003 * NEWS, europe: Belated thanks for Ittoqqortoormiit 2024 fix
17fbd40e5c * NEWS: Belated thanks for Palestine 2072-2075 fix
493df554af Prefer www.rfc-editor.org for RFCs
9db906a00f Switch from RFC 8536 to 9636 for documentation
be62d59182 Talk a bit more about tm_isdst's obsolescence
258a3775c0 tzfile man page editorial changes
af54a9e896 Port better to glibc when used internally there
486e1e890e Paraguay tm_isdst flag goes to 0 today
636e6f983b Paraguay adopts permanent -03 starting spring 2024
e6258faabc Concordia can use Asia/Singapore
19b35d7db2 More changes for global-tz convenience
07731a9f6a "j" could mean June, too
66183d1e25 Fix "many" typo in NEWS
96fa7b7dd4 Document month, weekday names better
7b6fb155ca Improve style checks for months
926b507fa5 "Apr", not "April", in IN column
d4c65d53b9 Improve historical data for the Philippines

git-subtree-dir: tz
git-subtree-split: 5ad5cfba5b092fe6abdc6870438cc09bcffbdd4b
---
 Makefile          |  32 +--
 NEWS              |  84 +++++++-
 antarctica        |   2 +
 asctime.c         | 118 ++++++-----
 asia              | 113 ++++++++---
 australasia       | 113 ++++++-----
 checknow.awk      |   2 +-
 checktab.awk      |  39 +++-
 date.1            | 122 ++---------
 etcetera          |   4 +
 europe            |   2 +-
 factory           |  10 +
 leap-seconds.list |   8 +-
 localtime.c       | 507 +++++++++++++++++++++++++++++-----------------
 newctime.3        | 152 +++++++-------
 newstrftime.3     |  86 ++++----
 newtzset.3        |  24 +--
 northamerica      |   9 +-
 private.h         | 119 +++++++----
 southamerica      |  27 ++-
 strftime.c        |  83 ++++++--
 theory.html       |  40 ++--
 time2posix.3      |   4 +-
 tz-link.html      |  37 ++--
 tzfile.5          |  72 ++++---
 tzfile.h          |   2 +-
 tzselect.8        |  26 ++-
 zdump.8           |  41 ++--
 zdump.c           |  62 +++---
 zic.8             | 153 +++++++-------
 zic.c             |  61 +++---
 zone.tab          |   2 +-
 zone1970.tab      |   6 +-
 zonenow.tab       |   9 +-
 34 files changed, 1288 insertions(+), 883 deletions(-)

diff --git a/Makefile b/Makefile
index 0087b459..2130582c 100644
--- a/Makefile
+++ b/Makefile
@@ -137,7 +137,7 @@ TIME_T_ALTERNATIVES_TAIL = int_least32_t.ck uint_least32_t.ck \
   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
diff --git a/NEWS b/NEWS
index 83b8b8c8..a5d7ea89 100644
--- a/NEWS
+++ b/NEWS
@@ -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 -0700
   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 -0800
     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
 
diff --git a/antarctica b/antarctica
index 8d5d6cd1..2e90a5e0 100644
--- a/antarctica
+++ b/antarctica
@@ -174,6 +174,8 @@ Zone Antarctica/Mawson	0	-	-00	1954 Feb 13
 
 # France & Italy - year-round base
 # Concordia, -750600+1232000, since 2005
+# https://en.wikipedia.org/wiki/Concordia_Station
+# Can use Asia/Singapore, which it has agreed with since inception.
 
 # Germany - year-round base
 # Neumayer III, -704080-0081602, since 2009
diff --git a/asctime.c b/asctime.c
index f75ec868..491d23bf 100644
--- a/asctime.c
+++ b/asctime.c
@@ -7,6 +7,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.
 */
@@ -16,27 +17,6 @@
 #include "private.h"
 #include <stdio.h>
 
-/*
-** 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 };
 /*
 ** Big enough for something such as
@@ -50,14 +30,24 @@ enum { STD_ASCTIME_BUF_SIZE = 26 };
 */
 static char buf_asctime[2*3 + 5*INT_STRLEN_MAXIMUM(int) + 7 + 2 + 1 + 1];
 
-/* 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_ctime buf_asctime
-#else
-static char buf_ctime[sizeof buf_asctime];
+/* 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
 
 /* Publish asctime_r and ctime_r only when supporting older POSIX.  */
@@ -84,12 +74,17 @@ asctime_r(struct tm const *restrict timeptr, char *restrict buf)
 	};
 	register const char *	wn;
 	register const char *	mn;
-	char			year[INT_STRLEN_MAXIMUM(int) + 2];
-	char result[sizeof buf_asctime];
+	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 = "???";
@@ -97,25 +92,41 @@ asctime_r(struct tm const *restrict timeptr, char *restrict buf)
 	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".
-	*/
-	strftime(year, sizeof year, "%Y", timeptr);
-	/*
-	** We avoid using snprintf since it's not available on all systems.
-	*/
-	sprintf(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 ((year <= LONG_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;
@@ -140,5 +151,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;
 }
diff --git a/asia b/asia
index a2480b02..d4eb0580 100644
--- a/asia
+++ b/asia
@@ -3665,21 +3665,70 @@ Zone	Asia/Hebron	2:20:23	-	LMT	1900 Oct
 # be immediately followed by 1845-01-01; see R.H. van Gent's
 # History of the International Date Line
 # https://webspace.science.uu.nl/~gent0113/idl/idl_philippines.htm
-# The rest of the data entries are from Shanks & Pottenger.
-
-# From Jesper Nørgaard Welen (2006-04-26):
-# ... claims that Philippines had DST last time in 1990:
-# http://story.philippinetimes.com/p.x/ct/9/id/145be20cc6b121c0/cid/3e5bbccc730d258c/
-# [a story dated 2006-04-25 by Cris Larano of Dow Jones Newswires,
-# but no details]
-
-# From Paul Eggert (2014-08-14):
-# The following source says DST may be instituted November-January and again
-# March-June, but this is not definite.  It also says DST was last proclaimed
-# during the Ramos administration (1992-1998); but again, no details.
-# Carcamo D. PNoy urged to declare use of daylight saving time.
-# Philippine Star 2014-08-05
-# http://www.philstar.com/headlines/2014/08/05/1354152/pnoy-urged-declare-use-daylight-saving-time
+
+# From P Chan (2021-05-10):
+# Here's a fairly comprehensive article in Japanese:
+#	https://wiki.suikawiki.org/n/Philippine%20Time
+# (2021-05-16):
+# According to the references listed in the article,
+# the periods that the Philippines (Manila) observed DST or used +9 are:
+#
+# 1936-10-31 24:00 to 1937-01-15 24:00
+#	(Proclamation No. 104, Proclamation No. 126)
+# 1941-12-15 24:00 to 1945-11-30 24:00
+#	(Proclamation No. 789, Proclamation No. 20)
+# 1954-04-11 24:00 to 1954-06-04 24:00
+#	(Proclamation No. 13, Proclamation No. 33)
+# 1977-03-27 24:00 to 1977-09-21 24:00
+#	(Proclamation No. 1629, Proclamation No. 1641)
+# 1990-05-21 00:00 to 1990-07-28 24:00
+#	(National Emergency Memorandum Order No. 17, Executive Order No. 415)
+#
+# Proclamation No. 104 ... October 30, 1936
+#  https://www.officialgazette.gov.ph/1936/10/30/proclamation-no-104-s-1936/
+# Proclamation No. 126 ... January 15, 1937
+#  https://www.officialgazette.gov.ph/1937/01/15/proclamation-no-126-s-1937/
+# Proclamation No. 789 ... December 13, 1941
+#  https://www.officialgazette.gov.ph/1941/12/13/proclamation-no-789-s-1941/
+# Proclamation No. 20 ... November 11, 1945
+#  https://www.officialgazette.gov.ph/1945/11/11/proclamation-no-20-s-1945/
+# Proclamation No. 13 ... April 6, 1954
+#  https://www.officialgazette.gov.ph/1954/04/06/proclamation-no-13-s-1954/
+# Proclamation No. 33 ... June 3, 1954
+#  https://www.officialgazette.gov.ph/1954/06/03/proclamation-no-33-s-1954/
+# Proclamation No. 1629 ... March 25, 1977
+#  https://www.officialgazette.gov.ph/1977/03/25/proclamation-no-1629-s-1977/
+# Proclamation No. 1641 ...May 26, 1977
+#  https://www.officialgazette.gov.ph/1977/05/26/proclamation-no-1641-s-1977/
+# National Emergency Memorandum Order No. 17 ... May 2, 1990
+#  https://www.officialgazette.gov.ph/1990/05/02/national-emergency-memorandum-order-no-17-s-1990/
+# Executive Order No. 415 ... July 20, 1990
+#  https://www.officialgazette.gov.ph/1990/07/20/executive-order-no-415-s-1990/
+#
+# During WWII, Proclamation No. 789 fixed two periods of DST. The first period
+# was set to continue only until January 31, 1942. But Manila was occupied by
+# the Japanese earlier in the month....
+#
+# For the date of the adoption of standard time, Shank[s] gives 1899-05-11.
+# The article is not able to state the basis of that. I guess it was based on
+# a US War Department Circular issued on that date.
+#	https://books.google.com/books?id=JZ1PAAAAYAAJ&pg=RA3-PA8
+#
+# However, according to other sources, standard time was adopted on
+# 1899-09-06.  Also, the LMT was GMT+8:03:52
+#	https://books.google.com/books?id=MOYIAQAAIAAJ&pg=PA521
+#	https://books.google.com/books?id=lSnqqatpYikC&pg=PA21
+#
+# From Paul Eggert (2024-09-05):
+# The penultimate URL in P Chan's email refers to page 521 of
+# Selga M, The Time Service in the Philippines.
+# Proc Pan-Pacific Science Congress. Vol. 1 (1923), 519-532.
+# It says, "The change from the meridian 120° 58' 04" to the 120th implied a
+# change of 3 min. 52s.26 in time; consequently on 6th September, 1899,
+# Manila Observatory gave the noon signal 3 min. 52s.26 later than before".
+#
+# Wikipedia says the US declared Manila liberated on March 4, 1945;
+# this doesn't affect clocks, just our time zone abbreviation and DST flag.
 
 # From Paul Goyette (2018-06-15) with URLs updated by Guy Harris (2024-02-15):
 # In the Philippines, there is a national law, Republic Act No. 10535
@@ -3697,24 +3746,26 @@ Zone	Asia/Hebron	2:20:23	-	LMT	1900 Oct
 # influence of the sources.  There is no current abbreviation for DST,
 # so use "PDT", the usual American style.
 
-# From P Chan (2021-05-10):
-# Here's a fairly comprehensive article in Japanese:
-# https://wiki.suikawiki.org/n/Philippine%20Time
-# From Paul Eggert (2021-05-10):
-# The info in the Japanese table has not been absorbed (yet) below.
-
 # Rule	NAME	FROM	TO	-	IN	ON	AT	SAVE	LETTER/S
-Rule	Phil	1936	only	-	Nov	1	0:00	1:00	D
-Rule	Phil	1937	only	-	Feb	1	0:00	0	S
-Rule	Phil	1954	only	-	Apr	12	0:00	1:00	D
-Rule	Phil	1954	only	-	Jul	1	0:00	0	S
-Rule	Phil	1978	only	-	Mar	22	0:00	1:00	D
-Rule	Phil	1978	only	-	Sep	21	0:00	0	S
+Rule	Phil	1936	only	-	Oct	31	24:00	1:00	D
+Rule	Phil	1937	only	-	Jan	15	24:00	0	S
+Rule	Phil	1941	only	-	Dec	15	24:00	1:00	D
+# The following three rules were canceled by Japan:
+#Rule	Phil	1942	only	-	Jan	31	24:00	0	S
+#Rule	Phil	1942	only	-	Mar	 1	 0:00	1:00	D
+#Rule	Phil	1942	only	-	Jun	30	24:00	0	S
+Rule	Phil	1945	only	-	Nov	30	24:00	0	S
+Rule	Phil	1954	only	-	Apr	11	24:00	1:00	D
+Rule	Phil	1954	only	-	Jun	 4	24:00	0	S
+Rule	Phil	1977	only	-	Mar	27	24:00	1:00	D
+Rule	Phil	1977	only	-	Sep	21	24:00	0	S
+Rule	Phil	1990	only	-	May	21	 0:00	1:00	D
+Rule	Phil	1990	only	-	Jul	28	24:00	0	S
 # Zone	NAME		STDOFF	RULES	FORMAT	[UNTIL]
-Zone	Asia/Manila	-15:56:00 -	LMT	1844 Dec 31
-			8:04:00 -	LMT	1899 May 11
-			8:00	Phil	P%sT	1942 May
-			9:00	-	JST	1944 Nov
+Zone	Asia/Manila	-15:56:08 -	LMT	1844 Dec 31
+			8:03:52 -	LMT	1899 Sep  6  4:00u
+			8:00	Phil	P%sT	1942 Feb 11 24:00
+			9:00	-	JST	1945 Mar  4
 			8:00	Phil	P%sT
 
 # Bahrain
diff --git a/australasia b/australasia
index 359f9c1f..40594453 100644
--- a/australasia
+++ b/australasia
@@ -1239,10 +1239,10 @@ Zone	Pacific/Efate	11:13:16 -	LMT	1912 Jan 13 # Vila
 # The 1992 ending date used in the rules is a best guess;
 # it matches what was used in the past.
 
-# The Australian Bureau of Meteorology FAQ
-# http://www.bom.gov.au/faq/faqgen.htm
-# (1999-09-27) writes that Giles Meteorological Station uses
-# South Australian time even though it's located in Western Australia.
+# From Christopher Hunt (2006-11-21), after an advance warning
+# from Jesper Nørgaard Welen (2006-11-01):
+# WA are trialing DST for three years.
+# http://www.parliament.wa.gov.au/parliament/bills.nsf/9A1B183144403DA54825721200088DF1/$File/Bill175-1B.pdf
 
 # From Paul Eggert (2018-04-01):
 # The Guardian Express of Perth, Australia reported today that the
@@ -1254,54 +1254,10 @@ Zone	Pacific/Efate	11:13:16 -	LMT	1912 Jan 13 # Vila
 # https://www.communitynews.com.au/guardian-express/news/exclusive-daylight-savings-coming-wa-summer-2018/
 # [The article ends with "Today's date is April 1."]
 
-# Queensland
-
-# From Paul Eggert (2018-02-26):
-# I lack access to the following source for Queensland DST:
-# Pearce C. History of daylight saving time in Queensland.
-# Queensland Hist J. 2017 Aug;23(6):389-403
-# https://search.informit.com.au/documentSummary;dn=994682348436426;res=IELHSS
-
-# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
-# #   The state of QUEENSLAND.. [ Courtesy Qld. Dept Premier Econ&Trade Devel ]
-# #						[ Dec 1990 ]
-# ...
-# Zone	Australia/Queensland	10:00	AQ	%sST
-# ...
-# Rule	AQ	1971	only	-	Oct	lastSun	2:00	1:00	D
-# Rule	AQ	1972	only	-	Feb	lastSun	3:00	0	E
-# Rule	AQ	1989	max	-	Oct	lastSun	2:00	1:00	D
-# Rule	AQ	1990	max	-	Mar	Sun>=1	3:00	0	E
-
-# From Bradley White (1989-12-24):
-# "Australia/Queensland" now observes daylight time (i.e. from
-# October 1989).
-
-# From Bradley White (1991-03-04):
-# A recent excerpt from an Australian newspaper...
-# ...Queensland...[has] agreed to end daylight saving
-# at 3am tomorrow (March 3)...
-
-# From John Mackin (1991-03-06):
-# I can certainly confirm for my part that Daylight Saving in NSW did in fact
-# end on Sunday, 3 March.  I don't know at what hour, though.  (It surprised
-# me.)
-
-# From Bradley White (1992-03-08):
-# ...there was recently a referendum in Queensland which resulted
-# in the experimental daylight saving system being abandoned. So, ...
-# ...
-# Rule	QLD	1989	1991	-	Oct	lastSun	2:00	1:00	D
-# Rule	QLD	1990	1992	-	Mar	Sun>=1	3:00	0	S
-# ...
-
-# From Arthur David Olson (1992-03-08):
-# The chosen rules the union of the 1971/1972 change and the 1989-1992 changes.
-
-# From Christopher Hunt (2006-11-21), after an advance warning
-# from Jesper Nørgaard Welen (2006-11-01):
-# WA are trialing DST for three years.
-# http://www.parliament.wa.gov.au/parliament/bills.nsf/9A1B183144403DA54825721200088DF1/$File/Bill175-1B.pdf
+# The Australian Bureau of Meteorology FAQ
+# http://www.bom.gov.au/faq/faqgen.htm
+# (1999-09-27) writes that Giles Meteorological Station uses
+# South Australian time even though it's located in Western Australia.
 
 # From Rives McDow (2002-04-09):
 # The most interesting region I have found consists of three towns on the
@@ -1359,6 +1315,59 @@ Zone	Pacific/Efate	11:13:16 -	LMT	1912 Jan 13 # Vila
 # For lack of better info, assume the tradition dates back to the
 # introduction of standard time in 1895.
 
+# From Stuart Bishop (2024-11-12):
+# An article discussing the in-use but technically unofficial timezones
+# in the Western Australian portion of the Nullarbor Plain.
+# https://www.abc.net.au/news/2024-11-22/outback-wa-properties-strange-time-zones/104542494
+# From Paul Eggert (2024-11-12):
+# As the article says, the Eyre Bird Observatory and nearby sheep stations
+# can use Tokyo time.  Other possibilities include Asia/Chita, Asia/Seoul,
+# and Asia/Jayapura.
+
+# Queensland
+
+# From Paul Eggert (2018-02-26):
+# I lack access to the following source for Queensland DST:
+# Pearce C. History of daylight saving time in Queensland.
+# Queensland Hist J. 2017 Aug;23(6):389-403
+# https://search.informit.com.au/documentSummary;dn=994682348436426;res=IELHSS
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# #   The state of QUEENSLAND.. [ Courtesy Qld. Dept Premier Econ&Trade Devel ]
+# #						[ Dec 1990 ]
+# ...
+# Zone	Australia/Queensland	10:00	AQ	%sST
+# ...
+# Rule	AQ	1971	only	-	Oct	lastSun	2:00	1:00	D
+# Rule	AQ	1972	only	-	Feb	lastSun	3:00	0	E
+# Rule	AQ	1989	max	-	Oct	lastSun	2:00	1:00	D
+# Rule	AQ	1990	max	-	Mar	Sun>=1	3:00	0	E
+
+# From Bradley White (1989-12-24):
+# "Australia/Queensland" now observes daylight time (i.e. from
+# October 1989).
+
+# From Bradley White (1991-03-04):
+# A recent excerpt from an Australian newspaper...
+# ...Queensland...[has] agreed to end daylight saving
+# at 3am tomorrow (March 3)...
+
+# From John Mackin (1991-03-06):
+# I can certainly confirm for my part that Daylight Saving in NSW did in fact
+# end on Sunday, 3 March.  I don't know at what hour, though.  (It surprised
+# me.)
+
+# From Bradley White (1992-03-08):
+# ...there was recently a referendum in Queensland which resulted
+# in the experimental daylight saving system being abandoned. So, ...
+# ...
+# Rule	QLD	1989	1991	-	Oct	lastSun	2:00	1:00	D
+# Rule	QLD	1990	1992	-	Mar	Sun>=1	3:00	0	S
+# ...
+
+# From Arthur David Olson (1992-03-08):
+# The chosen rules the union of the 1971/1972 change and the 1989-1992 changes.
+
 
 # southeast Australia
 #
diff --git a/checknow.awk b/checknow.awk
index 8b7881d2..450490ee 100644
--- a/checknow.awk
+++ b/checknow.awk
@@ -44,7 +44,7 @@ BEGIN {
 END {
  for (zone in zone_data) {
     data = zone_data[zone]
-    if (!zonenow[data]) {
+    if (data && !zonenow[data]) {
       printf "Zone table should have one of:%s\n", zones[data]
       zonenow[data] = zone # This suppresses duplicate diagnostics.
       status = 1
diff --git a/checktab.awk b/checktab.awk
index 9a26e465..5fa60556 100644
--- a/checktab.awk
+++ b/checktab.awk
@@ -9,6 +9,19 @@ BEGIN {
 	if (!zone_table) zone_table = "zone1970.tab"
 	if (!want_warnings) want_warnings = -1
 
+	monthabbr["Jan"] = 1
+	monthabbr["Feb"] = 1
+	monthabbr["Mar"] = 1
+	monthabbr["Apr"] = 1
+	monthabbr["May"] = 1
+	monthabbr["Jun"] = 1
+	monthabbr["Jul"] = 1
+	monthabbr["Aug"] = 1
+	monthabbr["Sep"] = 1
+	monthabbr["Oct"] = 1
+	monthabbr["Nov"] = 1
+	monthabbr["Dec"] = 1
+
 	while (getline <iso_table) {
 		iso_NR++
 		if ($0 ~ /^#/) continue
@@ -65,6 +78,12 @@ BEGIN {
 		split(ccs, cca, /,/)
 		cc = cca[1]
 
+		if (tztab[tz]) {
+		  printf "%s:%d: %s: duplicate Zone from line %d\n", \
+		    zone_table, zone_NR, tz, tz2NR[tz]
+		  status = 1
+		}
+
 		# Don't complain about a special case for Crimea in zone.tab.
 		# FIXME: zone.tab should be removed, since it is obsolete.
 		# Or at least put just "XX" in its country-code column.
@@ -128,12 +147,14 @@ BEGIN {
 $1 ~ /^#/ { next }
 
 {
-	tz = rules = ""
+	tz = rules = stdoff = ""
 	if ($1 == "Zone") {
 		tz = $2
+		stdoff = $3
 		ruleUsed[$4] = 1
 		if ($5 ~ /%/) rulePercentUsed[$4] = 1
-	} else if ($1 == "Link" && zone_table == "zone.tab") {
+	} else if ($1 == "Link") {
+	    if (zone_table == "zone.tab") {
 		# Ignore Link commands if source and destination basenames
 		# are identical, e.g. Europe/Istanbul versus Asia/Istanbul.
 		src = $2
@@ -141,13 +162,27 @@ $1 ~ /^#/ { next }
 		while ((i = index(src, "/"))) src = substr(src, i+1)
 		while ((i = index(dst, "/"))) dst = substr(dst, i+1)
 		if (src != dst) tz = $3
+	    }
 	} else if ($1 == "Rule") {
 		ruleDefined[$2] = 1
 		if ($10 != "-") ruleLetters[$2] = 1
+		if (!monthabbr[$6]) {
+		  printf "%s:%d: tricky month: %s\n", FILENAME, FNR, $6 \
+			  >>"/dev/stderr"
+		  status = 1
+		}
 	} else {
+		stdoff = $1
 		ruleUsed[$2] = 1
 		if ($3 ~ /%/) rulePercentUsed[$2] = 1
 	}
+
+	if (stdoff && stdoff !~ /^\-?1?[0-9](:[0-5][0-9](:[0-5][0-9])?)?$/) {
+		printf "%s:%d: unlikely STDOFF: %s\n", FILENAME, FNR, stdoff \
+			>>"/dev/stderr"
+		status = 1
+	}
+
 	if (tz && tz ~ /\// && tz !~ /^Etc\//) {
 		if (!tztab[tz] && FILENAME != "backward" \
 		    && zone_table != "zonenow.tab") {
diff --git a/date.1 b/date.1
index 01907bc7..3a02e7c2 100644
--- a/date.1
+++ b/date.1
@@ -6,15 +6,13 @@ date \- show and set date and time
 .SH SYNOPSIS
 .if n .nh
 .if n .na
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 .B date
 [
-.B \*-u
+.B \-u
 ] [
-.B \*-c
+.B \-c
 ] [
-.B \*-r
+.B \-r
 .I seconds
 ] [
 .BI + format
@@ -35,7 +33,7 @@ command
 without arguments writes the date and time to the standard output in
 the form
 .ce 1
-Wed Mar  8 14:54:40 EST 1989
+Sat Mar  8 14:54:40 EST 2025
 .br
 with
 .B EST
@@ -49,99 +47,24 @@ If a command-line argument starts with a plus sign (\c
 .q "\fB+\fP" ),
 the rest of the argument is used as a
 .I format
-that controls what appears in the output.
-In the format, when a percent sign (\c
-.q "\fB%\fP"
-appears,
-it and the character after it are not output,
-but rather identify part of the date or time
-to be output in a particular way
-(or identify a special character to output):
-.nf
-.sp
-.if t .in +.5i
-.if n .in +2
-.ta \w'%M\0\0'u +\w'Wed Mar  8 14:54:40 EST 1989\0\0'u
-	Sample output	Explanation
-%a	Wed	Abbreviated weekday name*
-%A	Wednesday	Full weekday name*
-%b	Mar	Abbreviated month name*
-%B	March	Full month name*
-%c	Wed Mar 08 14:54:40 1989	Date and time*
-%C	19	Century
-%d	08	Day of month (always two digits)
-%D	03/08/89	Month/day/year (eight characters)
-%e	 8	Day of month (leading zero blanked)
-%h	Mar	Abbreviated month name*
-%H	14	24-hour-clock hour (two digits)
-%I	02	12-hour-clock hour (two digits)
-%j	067	Julian day number (three digits)
-%k	 2	12-hour-clock hour (leading zero blanked)
-%l	14	24-hour-clock hour (leading zero blanked)
-%m	03	Month number (two digits)
-%M	54	Minute (two digits)
-%n	\\n	newline character
-%p	PM	AM/PM designation
-%r	02:54:40 PM	Hour:minute:second AM/PM designation
-%R	14:54	Hour:minute
-%S	40	Second (two digits)
-%t	\\t	tab character
-%T	14:54:40	Hour:minute:second
-%U	10	Sunday-based week number (two digits)
-%w	3	Day number (one digit, Sunday is 0)
-%W	10	Monday-based week number (two digits)
-%x	03/08/89	Date*
-%X	14:54:40	Time*
-%y	89	Last two digits of year
-%Y	1989	Year in full
-%z	-0500	Numeric time zone
-%Z	EST	Time zone abbreviation
-%+	Wed Mar  8 14:54:40 EST 1989	Default output format*
-.if t .in -.5i
-.if n .in -2
-* The exact output depends on the locale.
-.sp
-.fi
-If a character other than one of those shown above appears after
-a percent sign in the format,
-that following character is output.
-All other characters in the format are copied unchanged to the output;
-a newline character is always added at the end of the output.
-.PP
-In Sunday-based week numbering,
-the first Sunday of the year begins week 1;
-days preceding it are part of
-.q "week 0" .
-In Monday-based week numbering,
-the first Monday of the year begins week 1.
-.PP
-To set the date, use a command line argument with one of the following forms:
-.nf
-.if t .in +.5i
-.if n .in +2
-.ta \w'198903081454\0'u
-1454	24-hour-clock hours (first two digits) and minutes
-081454	Month day (first two digits), hours, and minutes
-03081454	Month (two digits, January is 01), month day, hours, minutes
-8903081454	Year, month, month day, hours, minutes
-0308145489	Month, month day, hours, minutes, year
-	(on System V-compatible systems)
-030814541989	Month, month day, hours, minutes, four-digit year
-198903081454	Four-digit year, month, month day, hours, minutes
-.if t .in -.5i
-.if n .in -2
-.fi
-If the century, year, month, or month day is not given,
-the current value is used.
-Any of the above forms may be followed by a period and two digits that give
-the seconds part of the new time; if no seconds are given, zero is assumed.
+that is processed by
+.BR strftime (3)
+to determine what to output;
+a newline character is appended.
+For example, the shell command:
+.ce 1
+date +"%Y\-%m\-%d %H:%M:%S %z"
+.br
+outputs a line like
+.q "2025\-03\-08 14:54:40 \-0500"
+instead.
 .PP
 These options are available:
 .TP
-.BR \*-u " or " \*-c
+.BR \-u " or " \-c
 Use Universal Time when setting and showing the date and time.
 .TP
-.BI "\*-r " seconds
+.BI "\-r " seconds
 Output the date that corresponds to
 .I seconds
 past the epoch of 1970-01-01 00:00:00 UTC, where
@@ -149,16 +72,13 @@ past the epoch of 1970-01-01 00:00:00 UTC, where
 should be an integer, either decimal, octal (leading 0), or
 hexadecimal (leading 0x), preceded by an optional sign.
 .SH FILES
-.ta \w'/usr/share/zoneinfo/posixrules\0\0'u
+.ta \w'/usr/share/zoneinfo/Etc/UTC\0\0'u
 /etc/localtime	local timezone file
 .br
 /usr/lib/locale/\f2L\fP/LC_TIME	description of time locale \f2L\fP
 .br
 /usr/share/zoneinfo	timezone directory
 .br
-/usr/share/zoneinfo/posixrules	default DST rules (obsolete)
-.br
-/usr/share/zoneinfo/GMT	for UTC leap seconds
-.PP
-If /usr/share/zoneinfo/GMT is absent,
-UTC leap seconds are loaded from /usr/share/zoneinfo/GMT0 if present.
+/usr/share/zoneinfo/Etc/UTC	for UTC leap seconds
+.SH SEE ALSO
+.BR strftime (3).
diff --git a/etcetera b/etcetera
index a5ecd6de..948531c8 100644
--- a/etcetera
+++ b/etcetera
@@ -51,6 +51,10 @@ Link	Etc/GMT				GMT
 # so we moved the names into the Etc subdirectory.
 # Also, the time zone abbreviations are now compatible with %z.
 
+# There is no "Etc/Unknown" entry, as CLDR says that "Etc/Unknown"
+# corresponds to an unknown or invalid time zone, and things would get
+# confusing if Etc/Unknown were made valid here.
+
 Zone	Etc/GMT-14	14	-	%z
 Zone	Etc/GMT-13	13	-	%z
 Zone	Etc/GMT-12	12	-	%z
diff --git a/europe b/europe
index f9063949..df334fc2 100644
--- a/europe
+++ b/europe
@@ -1147,7 +1147,7 @@ Zone Atlantic/Faroe	-0:27:04 -	LMT	1908 Jan 11 # Tórshavn
 # However, Greenland will change to Daylight Saving Time again in 2024
 # and onwards.
 
-# From a contributor who wishes to remain anonymous for now (2023-10-29):
+# From Jule Dabars (2023-10-29):
 # https://www.dr.dk/nyheder/seneste/i-nat-skal-uret-stilles-en-time-tilbage-men-foerste-gang-sker-det-ikke-i-groenland
 # with a link to that page:
 # https://naalakkersuisut.gl/Nyheder/2023/10/2710_sommertid
diff --git a/factory b/factory
index 9f5fc330..433a6721 100644
--- a/factory
+++ b/factory
@@ -8,5 +8,15 @@
 # time zone abbreviation "-00", indicating that the actual time zone
 # is unknown.
 
+# TZ="Factory" was added to TZDB in 1989, and in 2016 its abbreviation
+# was changed to "-00" from a longish English-language error message.
+# Around 2010, CLDR added "Etc/Unknown" for use with TZDB, to stand
+# for an unknown or invalid time zone.  These two notions differ:
+# TZ="Factory" is a valid timezone, so tzalloc("Factory") succeeds, whereas
+# TZ="Etc/Unknown" is invalid and tzalloc("Etc/Unknown") fails.
+# Also, a downstream distributor could modify Factory to be a
+# default timezone suitable for the devices it manufactures,
+# whereas that cannot happen for Etc/Unknown.
+
 # Zone	NAME	STDOFF	RULES	FORMAT
 Zone	Factory	0	-	-00
diff --git a/leap-seconds.list b/leap-seconds.list
index da0efc8c..6f861c88 100644
--- a/leap-seconds.list
+++ b/leap-seconds.list
@@ -60,15 +60,15 @@
 #
 #	The following line shows the last update of this file in NTP timestamp:
 #
-#$	3929093563
+#$	3945196800
 #
 #	2) Expiration date of the file given on a semi-annual basis: last June or last December
 #
-#	File expires on 28 June 2025
+#	File expires on 28 December 2025
 #
 #	Expire date in NTP timestamp:
 #
-#@	3960057600
+#@	3975868800
 #
 #
 #	LIST OF LEAP SECONDS
@@ -117,4 +117,4 @@
 #	please see the readme file in the 'source' directory :
 #	https://hpiers.obspm.fr/iers/bul/bulc/ntp/sources/README
 #
-#h	be738595 57b0cf1b b0218343 fb77062f 5a775e7
+#h	848434d5 570f7ea8 d79ba227 a00fc821 f608e2d4
diff --git a/localtime.c b/localtime.c
index 7ae9ce5e..96737ca6 100644
--- a/localtime.c
+++ b/localtime.c
@@ -19,6 +19,14 @@
 #include "tzfile.h"
 #include <fcntl.h>
 
+#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
+
 #if defined THREAD_SAFE && THREAD_SAFE
 # include <pthread.h>
 static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER;
@@ -29,6 +37,73 @@ static int lock(void) { return 0; }
 static void unlock(void) { }
 #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
+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 :+-._"
@@ -38,12 +113,23 @@ static void unlock(void) { }
 # 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
@@ -72,7 +158,10 @@ static void unlock(void) { }
 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.
@@ -84,10 +173,31 @@ static char const *utc = etc_utc + sizeof "Etc/" - 1;
 # define TZDEFRULESTRING ",M3.2.0,M11.1.0"
 #endif
 
+/* 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 */
 };
@@ -106,12 +216,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
-
 /* A representation of the contents of a TZif file.  Ideally this
    would have no size limits; the following sizes should suffice for
    practical use.  This struct should not be too large, as instances
@@ -151,7 +255,6 @@ static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t,
 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 *);
@@ -172,8 +275,10 @@ static struct state *const gmtptr = &gmtmem;
 # 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
 
 /*
 ** Section 4.12.3 of X3.159-1989 requires that
@@ -187,27 +292,29 @@ static int		lcl_is_set;
 ** trigger latent bugs in programs.
 */
 
-#if SUPPORT_C89
+#if !USE_TIMEX_T
+
+# if SUPPORT_C89
 static struct tm	tm;
 #endif
 
-#if 2 <= HAVE_TZNAME + TZ_TIME_T
-char *			tzname[2] = {
-	(char *) wildabbr,
-	(char *) wildabbr
-};
-#endif
-#if 2 <= USG_COMPAT + TZ_TIME_T
+# if 2 <= HAVE_TZNAME + TZ_TIME_T
+char *tzname[2] = { UNCONST(wildabbr), UNCONST(wildabbr) };
+# endif
+# if 2 <= USG_COMPAT + TZ_TIME_T
 long			timezone;
 int			daylight;
-#endif
-#if 2 <= ALTZONE + TZ_TIME_T
+# endif
+# if 2 <= ALTZONE + TZ_TIME_T
 long			altzone;
+# endif
+
 #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;
@@ -271,20 +378,22 @@ detzcode64(const char *const codep)
 	return result;
 }
 
+#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] = (char *) &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,
@@ -315,18 +424,18 @@ settzname(void)
 	   When STDDST_MASK becomes zero we can stop looking.  */
 	int stddst_mask = 0;
 
-#if HAVE_TZNAME
-	tzname[0] = tzname[1] = (char *) (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. . .
 	*/
@@ -336,9 +445,9 @@ settzname(void)
 	  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 = stddst_mask >> 1 ^ 1;
-#endif
+# endif
 }
 
 /* Replace bogus characters in time zone abbreviations.
@@ -365,6 +474,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.  */
@@ -399,11 +510,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;
@@ -454,9 +569,25 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 
 		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;
+#ifdef S_ISREG
+	  {
+	    struct stat st;
+	    if (stat(name, &st) < 0)
+	      return errno;
+	    if (!S_ISREG(st.st_mode))
+	      return EINVAL;
+	  }
+#endif
+	}
+	fid = open(name, (O_RDONLY | O_BINARY | O_CLOEXEC | O_CLOFORK
+			  | O_IGNORE_CTTY | O_NOCTTY));
 	if (fid < 0)
 	  return errno;
 
@@ -583,7 +714,7 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 		     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
@@ -634,7 +765,7 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 	    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;
@@ -709,23 +840,23 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 	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 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, doextend, &ls);
+  return tzloadbody(name, sp, tzloadflags, &ls);
 #endif
 }
 
@@ -1066,7 +1197,7 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
 	  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.  */
 	}
@@ -1303,14 +1434,17 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
 static void
 gmtload(struct state *const sp)
 {
-	if (tzload(etc_utc, sp, true) != 0)
+	if (tzload(etc_utc, sp, TZLOAD_TZSTRING) != 0)
 	  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]) {
     /*
@@ -1325,7 +1459,7 @@ zoneinit(struct state *sp, char const *name)
     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)
@@ -1344,13 +1478,15 @@ tzset_unlocked(void)
       ? lcl_is_set < 0
       : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0)
     return;
-#ifdef ALL_STATE
+# ifdef ALL_STATE
   if (! sp)
     lclptr = sp = malloc(sizeof *lclptr);
-#endif /* defined ALL_STATE */
+# 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);
   }
@@ -1358,6 +1494,9 @@ tzset_unlocked(void)
   lcl_is_set = lcl;
 }
 
+#endif
+
+#if !USE_TIMEX_T
 void
 tzset(void)
 {
@@ -1366,6 +1505,7 @@ tzset(void)
   tzset_unlocked();
   unlock();
 }
+#endif
 
 static void
 gmtcheck(void)
@@ -1384,14 +1524,14 @@ gmtcheck(void)
   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;
@@ -1420,6 +1560,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
@@ -1474,14 +1616,14 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 					return NULL;	/* "cannot happen" */
 			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;
@@ -1491,7 +1633,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 				if (! (INT_MIN <= newy && newy <= INT_MAX))
 					return NULL;
 				result->tm_year = newy;
-#endif
+# endif
 			}
 			return result;
 	}
@@ -1520,25 +1662,26 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
 	result = timesub(&t, ttisp->tt_utoff, sp, tmp);
 	if (result) {
 	  result->tm_isdst = ttisp->tt_isdst;
-#ifdef TM_ZONE
-	  result->TM_ZONE = (char *) &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)
@@ -1558,9 +1701,9 @@ localtime_tzset(time_t const *timep, struct tm *tmp, bool setname)
 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);
 }
 
@@ -1569,6 +1712,7 @@ localtime_r(const time_t *restrict timep, struct tm *restrict tmp)
 {
   return localtime_tzset(timep, tmp, false);
 }
+#endif
 
 /*
 ** gmtsub is to gmtime as localsub is to localtime.
@@ -1587,12 +1731,14 @@ gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep,
 	** "+xx" or "-xx" if offset is non-zero,
 	** but this is no time for a treasure hunt.
 	*/
-	tmp->TM_ZONE = ((char *)
-			(offset ? wildabbr : gmtptr ? gmtptr->chars : utc));
+	tmp->TM_ZONE = UNCONST(offset ? wildabbr
+			       : gmtptr ? gmtptr->chars : utc);
 #endif /* defined TM_ZONE */
 	return result;
 }
 
+#if !USE_TIMEX_T
+
 /*
 * Re-entrant version of gmtime.
 */
@@ -1607,13 +1753,13 @@ gmtime_r(time_t const *restrict timep, struct tm *restrict tmp)
 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.  */
@@ -1623,12 +1769,13 @@ offtime(const time_t *timep, long offset)
 {
   gmtcheck();
 
-#if !SUPPORT_C89
+#  if !SUPPORT_C89
   static struct tm tm;
-#endif
+#  endif
   return gmtsub(gmtptr, timep, offset, &tm);
 }
 
+# endif
 #endif
 
 /*
@@ -1687,7 +1834,7 @@ timesub(const time_t *timep, int_fast32_t offset,
 	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.  */
@@ -1804,17 +1951,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
 }
 
@@ -1838,30 +1985,6 @@ increment_overflow_time(time_t *tp, int_fast32_t j)
 #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)
@@ -1906,11 +2029,9 @@ 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;
-	int_fast32_t			y;
+	iinntt y, mday, hour, min, saved_seconds;
 	time_t				newt;
 	time_t				t;
 	struct tm			yourtm, mytm;
@@ -1918,36 +2039,57 @@ time2sub(struct tm *const tmp,
 	*okayp = false;
 	mktmcpy(&yourtm, tmp);
 
+	min = yourtm.tm_min;
 	if (do_norm_secs) {
-		if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
-			SECSPERMIN))
-				return WRONG;
+	  min += yourtm.tm_sec / SECSPERMIN;
+	  yourtm.tm_sec %= SECSPERMIN;
+	  if (yourtm.tm_sec < 0) {
+	    yourtm.tm_sec += SECSPERMIN;
+	    min--;
+	  }
 	}
-	if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
-		return WRONG;
-	if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
-		return WRONG;
+
+	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--;
+	}
+
 	y = yourtm.tm_year;
-	if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR))
-		return WRONG;
+	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))
-		return WRONG;
-	while (yourtm.tm_mday <= 0) {
-		if (increment_overflow32(&y, -1))
-			return WRONG;
-		li = y + (1 < yourtm.tm_mon);
-		yourtm.tm_mday += year_lengths[isleap(li)];
+	y += TM_YEAR_BASE;
+
+	while (mday <= 0) {
+	  iinntt li = y - (yourtm.tm_mon <= 1);
+	  mday += year_lengths[isleap(li)];
+	  y--;
 	}
-	while (yourtm.tm_mday > DAYSPERLYEAR) {
-		li = y + (1 < yourtm.tm_mon);
-		yourtm.tm_mday -= year_lengths[isleap(li)];
-		if (increment_overflow32(&y, 1))
-			return WRONG;
+	while (DAYSPERLYEAR < mday) {
+	  iinntt li = y + (1 < yourtm.tm_mon);
+	  mday -= year_lengths[isleap(li)];
+	  y++;
 	}
+	yourtm.tm_mday = mday;
 	for ( ; ; ) {
 		i = mon_lengths[isleap(y)][yourtm.tm_mon];
 		if (yourtm.tm_mday <= i)
@@ -1955,16 +2097,14 @@ time2sub(struct tm *const tmp,
 		yourtm.tm_mday -= i;
 		if (++yourtm.tm_mon >= MONSPERYEAR) {
 			yourtm.tm_mon = 0;
-			if (increment_overflow32(&y, 1))
-				return WRONG;
+			y++;
 		}
 	}
 #ifdef ckd_add
 	if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE))
 	  return WRONG;
 #else
-	if (increment_overflow32(&y, -TM_YEAR_BASE))
-		return WRONG;
+	y -= TM_YEAR_BASE;
 	if (! (INT_MIN <= y && y <= INT_MAX))
 		return WRONG;
 	yourtm.tm_year = y;
@@ -1980,9 +2120,8 @@ time2sub(struct tm *const tmp,
 		** 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))
-			return WRONG;
 		saved_seconds = yourtm.tm_sec;
+		saved_seconds -= SECSPERMIN - 1;
 		yourtm.tm_sec = SECSPERMIN - 1;
 	} else {
 		saved_seconds = yourtm.tm_sec;
@@ -2091,10 +2230,8 @@ time2sub(struct tm *const tmp,
 		return WRONG;
 	}
 label:
-	newt = t + saved_seconds;
-	if ((newt < t) != (saved_seconds < 0))
+	if (increment_overflow_time_iinntt(&t, saved_seconds))
 		return WRONG;
-	t = newt;
 	if (funcp(sp, &t, offset, tmp))
 		*okayp = true;
 	return t;
@@ -2191,6 +2328,8 @@ time1(struct tm *const tmp,
 	return WRONG;
 }
 
+#if !defined TM_GMTOFF || !USE_TIMEX_T
+
 static time_t
 mktime_tzname(struct state *sp, struct tm *tmp, bool setname)
 {
@@ -2202,16 +2341,9 @@ mktime_tzname(struct state *sp, struct tm *tmp, bool setname)
   }
 }
 
-#if NETBSD_INSPIRED
-
-time_t
-mktime_z(struct state *restrict sp, struct tm *restrict tmp)
-{
-  return mktime_tzname(sp, tmp, false);
-}
-
-#endif
-
+# if USE_TIMEX_T
+static
+# endif
 time_t
 mktime(struct tm *tmp)
 {
@@ -2227,7 +2359,17 @@ mktime(struct tm *tmp)
   return t;
 }
 
-#if STD_INSPIRED
+#endif
+
+#if NETBSD_INSPIRED && !USE_TIMEX_T
+time_t
+mktime_z(struct state *restrict sp, struct tm *restrict tmp)
+{
+  return mktime_tzname(sp, tmp, false);
+}
+#endif
+
+#if STD_INSPIRED && !USE_TIMEX_T
 /* This function is obsolescent and may disappear in future releases.
    Callers can instead use mktime.  */
 time_t
@@ -2239,12 +2381,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.  */
@@ -2256,7 +2400,9 @@ timeoff(struct tm *tmp, long offset)
   gmtcheck();
   return time1(tmp, gmtsub, gmtptr, offset);
 }
+#endif
 
+#if !USE_TIMEX_T
 time_t
 timegm(struct tm *tmp)
 {
@@ -2269,6 +2415,7 @@ timegm(struct tm *tmp)
     *tmp = tmcpy;
   return t;
 }
+#endif
 
 static int_fast32_t
 leapcorr(struct state const *sp, time_t t)
@@ -2289,15 +2436,16 @@ leapcorr(struct state const *sp, time_t t)
 ** XXX--is the below the right way to conditionalize??
 */
 
-#if STD_INSPIRED
+#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
@@ -2374,17 +2522,13 @@ posix2time(time_t t)
   return t;
 }
 
-#endif /* STD_INSPIRED */
+# endif /* STD_INSPIRED */
 
-#if TZ_TIME_T
+# if TZ_TIME_T
 
-# if !USG_COMPAT
-#  define daylight 0
-#  define timezone 0
-# endif
-# if !ALTZONE
-#  define altzone 0
-# endif
+#  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
@@ -2402,9 +2546,9 @@ 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)) {
+    iinntt offset = EPOCH_LOCAL ? timezone : 0;
+    if (offset < IINNTT_MIN + EPOCH_OFFSET
+	|| increment_overflow_time_iinntt(&r, offset - EPOCH_OFFSET)) {
       errno = EOVERFLOW;
       r = -1;
     }
@@ -2414,4 +2558,5 @@ time(time_t *p)
   return r;
 }
 
+# endif
 #endif
diff --git a/newctime.3 b/newctime.3
index d19fd25b..9d09e5a5 100644
--- a/newctime.3
+++ b/newctime.3
@@ -5,43 +5,34 @@
 asctime, ctime, difftime, gmtime, localtime, mktime \- convert date and time
 .SH SYNOPSIS
 .nf
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 .B #include <time.h>
 .PP
-.B [[deprecated]] char *ctime(time_t const *clock);
-.PP
-/* Only in POSIX.1-2017 and earlier.  */
-.B char *ctime_r(time_t const *clock, char *buf);
-.PP
-.B double difftime(time_t time1, time_t time0);
-.PP
-.B [[deprecated]] char *asctime(struct tm const *tm);
-.PP
-/* Only in POSIX.1-2017 and earlier.  */
-.B "char *asctime_r(struct tm const *restrict tm,"
-.B "    char *restrict result);"
-.PP
 .B struct tm *localtime(time_t const *clock);
-.PP
 .B "struct tm *localtime_r(time_t const *restrict clock,"
 .B "    struct tm *restrict result);"
-.PP
 .B "struct tm *localtime_rz(timezone_t restrict zone,"
 .B "    time_t const *restrict clock,"
 .B "    struct tm *restrict result);"
 .PP
 .B struct tm *gmtime(time_t const *clock);
-.PP
 .B "struct tm *gmtime_r(time_t const *restrict clock,"
 .B "    struct tm *restrict result);"
 .PP
 .B time_t mktime(struct tm *tm);
-.PP
 .B "time_t mktime_z(timezone_t restrict zone,"
 .B "    struct tm *restrict tm);"
 .PP
-.B cc ... \*-ltz
+.B double difftime(time_t time1, time_t time0);
+.PP
+.B [[deprecated]] char *asctime(struct tm const *tm);
+.B [[deprecated]] char *ctime(time_t const *clock);
+.PP
+/* Only in POSIX.1-2017 and earlier.  */
+.B char *ctime_r(time_t const *clock, char *buf);
+.B "char *asctime_r(struct tm const *restrict tm,"
+.B "    char *restrict result);"
+.PP
+.B cc ... \-ltz
 .fi
 .SH DESCRIPTION
 .ie '\(en'' .ds en \-
@@ -54,82 +45,37 @@ asctime, ctime, difftime, gmtime, localtime, mktime \- convert date and time
 \\$3\*(lq\\$1\*(rq\\$2
 ..
 The
-.B ctime
-function
-converts a long integer, pointed to by
-.IR clock ,
-and returns a pointer to a
-string of the form
-.br
-.ce
-.eo
-Thu Nov 24 18:22:48 1986\n\0
-.br
-.ec
-Years requiring fewer than four characters are padded with leading zeroes.
-For years longer than four characters, the string is of the form
-.br
-.ce
-.eo
-Thu Nov 24 18:22:48     81986\n\0
-.ec
-.br
-with five spaces before the year.
-These unusual formats are 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.
-.PP
-The
-.BI * clock
-timestamp represents the time in seconds since 1970-01-01 00:00:00
-Coordinated Universal Time (UTC).
-The POSIX standard says that timestamps must be nonnegative
-and must ignore leap seconds.
-Many implementations extend POSIX by allowing negative timestamps,
-and can therefore represent timestamps that predate the
-introduction of UTC and are some other flavor of Universal Time (UT).
-Some implementations support leap seconds, in contradiction to POSIX.
-.PP
-The
-.B ctime
-function is deprecated starting in C23.
-Callers can use
-.B localtime_r
-and
-.B strftime
-instead.
-.PP
-The
 .B localtime
 and
 .B gmtime
 functions
+convert an integer, pointed to by
+.IR clock ,
+and
 return pointers to
 .q "tm"
 structures, described below.
+If the integer is out of range for conversion,
+these functions return a null pointer.
 The
 .B localtime
 function
 corrects for the time zone and any time zone adjustments
 (such as Daylight Saving Time in the United States).
-.PP
 The
 .B gmtime
-function
-converts to Coordinated Universal Time.
+function converts to Coordinated Universal Time.
 .PP
 The
-.B asctime
-function
-converts a time value contained in a
-.q "tm"
-structure to a string,
-as shown in the above example,
-and returns a pointer to the string.
-This function is deprecated starting in C23.
-Callers can use
-.B strftime
-instead.
+.BI * clock
+timestamp represents the time in seconds since 1970-01-01 00:00:00
+Coordinated Universal Time (UTC).
+The POSIX standard says that timestamps must be nonnegative
+and must ignore leap seconds.
+Many implementations extend POSIX by allowing negative timestamps,
+and can therefore represent timestamps that predate the
+introduction of UTC and are some other flavor of Universal Time (UT).
+Some implementations support leap seconds, in contradiction to POSIX.
 .PP
 The
 .B mktime
@@ -204,6 +150,52 @@ returns the difference between two calendar times,
 expressed in seconds.
 .PP
 The
+.B asctime
+function
+converts a time value contained in a
+.q "tm"
+structure to a pointer to a
+string of the form
+.br
+.ce
+.eo
+Thu Nov 24 18:22:48 1986\n\0
+.br
+.ec
+Years requiring fewer than four characters are padded with leading zeroes.
+For years longer than four characters, the string is of the form
+.br
+.ce
+.eo
+Thu Nov 24 18:22:48     81986\n\0
+.ec
+.br
+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
+.B strftime
+instead.
+.PP
+The
+.B ctime
+function is equivalent to calliing
+.B localtime
+and then calling
+.B asctime
+on the result.
+Like
+.BR asctime ,
+this function is deprecated starting in C23.
+Callers can use
+.B localtime
+and
+.B strftime
+instead.
+.PP
+The
 .BR ctime_r ,
 .BR localtime_r ,
 .BR gmtime_r ,
diff --git a/newstrftime.3 b/newstrftime.3
index a9997a09..e9a38224 100644
--- a/newstrftime.3
+++ b/newstrftime.3
@@ -40,8 +40,6 @@
 strftime \- format date and time
 .SH SYNOPSIS
 .nf
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 .B #include <time.h>
 .PP
 .B "size_t strftime(char *restrict buf, size_t maxsize,"
@@ -93,7 +91,7 @@ If a bracketed member name is followed by
 .B strftime
 can use the named member even though POSIX.1-2024 does not list it;
 if the name is followed by
-.q \*- ,
+.q \- ,
 .B strftime
 ignores the member even though POSIX.1-2024 lists it
 which means portable code should set it.
@@ -139,11 +137,14 @@ is replaced by the locale's appropriate date and time representation.
 .IR tm_sec ,
 .IR tm_gmtoff ,
 .IR tm_zone ,
-.IR tm_isdst \*-].
+.IR tm_isdst \-].
 .TP
 %D
 is equivalent to
 .c %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.
 .RI [ tm_year ,
 .IR tm_mon ,
 .IR tm_mday ]
@@ -167,6 +168,8 @@ is equivalent to
 .TP
 %G
 is replaced by the ISO 8601 year with 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
 .c %V
 conversion specification.
@@ -176,11 +179,7 @@ conversion specification.
 .TP
 %g
 is replaced by the ISO 8601 year without century as a decimal number [00,99].
-This is the year that includes the greater part of the week.
-(Monday as the first day of a week).
-See also the
-.c %V
-conversion specification.
+Since it omits the century, it is ambiguous for dates.
 .RI [ tm_year ,
 .IR tm_yday ,
 .IR tm_wday ]
@@ -249,9 +248,22 @@ of leap seconds.
 is replaced by the number of seconds since the Epoch (see
 .BR ctime (3)).
 Although %s is reliable in this implementation,
-it can have glitches on other platforms (notably platforms lacking
-.IR tm_gmtoff ),
-so portable code should format a
+it can have glitches on other platforms
+(notably obsolescent platforms lacking
+.I tm_gmtoff
+or where
+.B time_t
+is no wider than int), and POSIX allows
+.B strftime
+to set
+.B errno
+to
+.B EINVAL
+or
+.B EOVERFLOW
+and return 0 if the number of seconds would be negative or out of range for
+.BR time_t .
+Portable code should therefore format a
 .B time_t
 value directly via something like
 .B sprintf
@@ -267,7 +279,7 @@ with "%s".
 .IR tm_min ,
 .IR tm_sec ,
 .IR tm_gmtoff +,
-.IR tm_isdst \*-].
+.IR tm_isdst \-].
 .TP
 %T
 is replaced by the time in the format
@@ -284,7 +296,7 @@ is replaced by the week number of the year (Sunday as the first day of
 the week) as a decimal number [00,53].
 .RI [ tm_wday ,
 .IR tm_yday ,
-.IR tm_year \*-]
+.IR tm_year \-]
 .TP
 %u
 is replaced by the weekday (Monday as the first day of the week)
@@ -318,31 +330,33 @@ as a decimal number [0,6].
 .TP
 %X
 is replaced by the locale's appropriate time representation.
-.RI [ tm_year \*-,
-.IR tm_yday \*-,
-.IR tm_mon \*-,
-.IR tm_mday \*-,
-.IR tm_wday \*-,
+.RI [ tm_year \-,
+.IR tm_yday \-,
+.IR tm_mon \-,
+.IR tm_mday \-,
+.IR tm_wday \-,
 .IR tm_hour ,
 .IR tm_min ,
 .IR tm_sec ,
 .IR tm_gmtoff ,
 .IR tm_zone ,
-.IR tm_isdst \*-].
+.IR tm_isdst \-].
 .TP
 %x
 is replaced by the locale's appropriate date representation.
+This format can be ambiguous for dates, e.g.,
+it can generate "01/02/03" in the C locale.
 .RI [ tm_year ,
 .IR tm_yday ,
 .IR tm_mon ,
 .IR tm_mday ,
 .IR tm_wday ,
-.IR tm_hour \*-,
-.IR tm_min \*-,
-.IR tm_sec \*-,
-.IR tm_gmtoff \*-,
-.IR tm_zone \*-,
-.IR tm_isdst \*-].
+.IR tm_hour \-,
+.IR tm_min \-,
+.IR tm_sec \-,
+.IR tm_gmtoff \-,
+.IR tm_zone \-,
+.IR tm_isdst \-].
 .TP
 %Y
 is replaced by the year with century as a decimal number.
@@ -350,28 +364,29 @@ is replaced by the year with century as a decimal number.
 .TP
 %y
 is replaced by the year without century as a decimal number [00,99].
+Since it omits the century, it is ambiguous for dates.
 .RI [ tm_year ]
 .TP
 %Z
 is replaced by the time zone abbreviation,
 or by the empty string if this is not determinable.
 .RI [ tm_zone ,
-.IR tm_isdst \*-]
+.IR tm_isdst \-]
 .TP
 %z
 is replaced by the offset from the Prime Meridian
-in the format +HHMM or \*-HHMM (ISO 8601) as appropriate,
+in the format +HHMM or \-HHMM (ISO 8601) as appropriate,
 with positive values representing locations east of Greenwich,
 or by the empty string if this is not determinable.
-The numeric time zone abbreviation \*-0000 is used when the time is
+The numeric time zone abbreviation \-0000 is used when the time is
 Universal Time
 but local time is indeterminate; by convention this is used for
 locations while uninhabited, and corresponds to a zero offset when the
 time zone abbreviation begins with
-.q "\*-" .
+.q "\-" .
 .RI [ tm_gmtoff ,
 .IR tm_zone +,
-.IR tm_isdst \*-]
+.IR tm_isdst \-]
 .TP
 %%
 is replaced by a single %.
@@ -418,15 +433,6 @@ This function fails if:
 The total number of resulting bytes, including the terminating
 NUL character, is more than
 .IR maxsize .
-.PP
-This function may fail if:
-.TP
-[EOVERFLOW]
-The format includes an
-.c %s
-conversion and the number of seconds since the Epoch cannot be represented
-in a
-.c time_t .
 .SH SEE ALSO
 .BR date (1),
 .BR getenv (3),
diff --git a/newtzset.3 b/newtzset.3
index 661fb25b..db6bfa7f 100644
--- a/newtzset.3
+++ b/newtzset.3
@@ -5,8 +5,6 @@
 tzset \- initialize time conversion information
 .SH SYNOPSIS
 .nf
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 .B #include <time.h>
 .PP
 .BI "timezone_t tzalloc(char const *" TZ );
@@ -23,7 +21,7 @@ tzset \- initialize time conversion information
 .br
 .B extern int daylight;
 .PP
-.B cc ... \*-ltz
+.B cc ... \-ltz
 .fi
 .SH DESCRIPTION
 .ie '\(en'' .ds en \-
@@ -110,7 +108,7 @@ except a leading colon
 digits, comma
 .RB ( , ),
 ASCII minus
-.RB ( \*- ),
+.RB ( \- ),
 ASCII plus
 .RB ( + ),
 and NUL bytes are allowed.
@@ -150,7 +148,7 @@ daylight saving time is assumed to be one hour ahead of standard time.  One or
 more digits may be used; the value is always interpreted as a decimal
 number.  The hour must be between zero and 24, and the minutes (and
 seconds) \*(en if present \*(en between zero and 59.  If preceded by a
-.q "\*-" ,
+.q "\-" ,
 the time zone shall be east of the Prime Meridian; otherwise it shall be
 west (which may be indicated by an optional preceding
 .q "+" .
@@ -239,7 +237,7 @@ values that directly specify the timezone.
 stands for US Eastern Standard
 Time (EST), 5 hours behind UT, without daylight saving.
 .TP
-.B <+12>\*-12<+13>,M11.1.0,M1.2.1/147
+.B <+12>\-12<+13>,M11.1.0,M1.2.1/147
 stands for Fiji time, 12 hours ahead
 of UT, springing forward on November's first Sunday at 02:00, and
 falling back on January's second Monday at 147:00 (i.e., 03:00 on the
@@ -249,34 +247,34 @@ and daylight saving time are
 and
 .q "+13".
 .TP
-.B IST\*-2IDT,M3.4.4/26,M10.5.0
+.B IST\-2IDT,M3.4.4/26,M10.5.0
 stands for Israel Standard Time (IST) and Israel Daylight Time (IDT),
 2 hours ahead of UT, springing forward on March's fourth
 Thursday at 26:00 (i.e., 02:00 on the first Friday on or after March
 23), and falling back on October's last Sunday at 02:00.
 .TP
-.B <\*-04>4<\*-03>,J1/0,J365/25
+.B <\-04>4<\-03>,J1/0,J365/25
 stands for permanent daylight saving time, 3 hours behind UT with
 abbreviation
-.q "\*-03".
+.q "\-03".
 There is a dummy fall-back transition on December 31 at 25:00 daylight
 saving time (i.e., 24:00 standard time, equivalent to January 1 at
 00:00 standard time), and a simultaneous spring-forward transition on
 January 1 at 00:00 standard time, so daylight saving time is in effect
 all year and the initial
-.B <\*-04>
+.B <\-04>
 is a placeholder.
 .TP
-.B <\*-03>3<\*-02>,M3.5.0/\*-2,M10.5.0/\*-1
+.B <\-03>3<\-02>,M3.5.0/\-2,M10.5.0/\-1
 stands for time in western Greenland, 3 hours behind UT, where clocks
 follow the EU rules of
 springing forward on March's last Sunday at 01:00 UT (\-02:00 local
 time, i.e., 22:00 the previous day) and falling back on October's last
 Sunday at 01:00 UT (\-01:00 local time, i.e., 23:00 the previous day).
 The abbreviations for standard and daylight saving time are
-.q "\*-03"
+.q "\-03"
 and
-.q "\*-02".
+.q "\-02".
 .PP
 If
 .I TZ
diff --git a/northamerica b/northamerica
index 01f392e0..8d356aa0 100644
--- a/northamerica
+++ b/northamerica
@@ -27,9 +27,12 @@
 # in New York City (1869-10).  His 1870 proposal was based on Washington, DC,
 # but in 1872-05 he moved the proposed origin to Greenwich.
 
-# From Paul Eggert (2018-03-20):
+# From Paul Eggert (2024-11-18):
 # Dowd's proposal left many details unresolved, such as where to draw
-# lines between time zones.  The key individual who made time zones
+# lines between time zones.  Sandford Fleming of the Canadian Pacific Railway
+# argued for Dowd's proposal in 1876, and Cleveland Abbe of the American
+# Meteorology Society published a report in 1879 recommending four US time
+# zones based on GMT.  However, the key individual who made time zones
 # work in the US was William Frederick Allen - railway engineer,
 # managing editor of the Travelers' Guide, and secretary of the
 # General Time Convention, a railway standardization group.  Allen
@@ -2631,7 +2634,7 @@ Zone America/Dawson	-9:17:40 -	LMT	1900 Aug 20
 # http://puentelibre.mx/noticia/ciudad_juarez_cambio_horario_noviembre_2022/
 
 # Rule	NAME	FROM	TO	-	IN	ON	AT	SAVE	LETTER/S
-Rule	Mexico	1931	only	-	April	30	0:00	1:00	D
+Rule	Mexico	1931	only	-	Apr	30	0:00	1:00	D
 Rule	Mexico	1931	only	-	Oct	1	0:00	0	S
 Rule	Mexico	1939	only	-	Feb	5	0:00	1:00	D
 Rule	Mexico	1939	only	-	Jun	25	0:00	0	S
diff --git a/private.h b/private.h
index c3304104..0a546e02 100644
--- a/private.h
+++ b/private.h
@@ -37,6 +37,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
@@ -51,6 +83,7 @@
 #endif
 
 #if __STDC_VERSION__ < 202311
+# undef static_assert
 # define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1]
 #endif
 
@@ -87,11 +120,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
@@ -124,20 +157,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
@@ -149,25 +182,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
 */
@@ -260,6 +274,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
@@ -305,7 +323,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 \
@@ -375,11 +393,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
@@ -459,7 +481,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
@@ -554,13 +576,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
@@ -593,6 +628,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'.  */
@@ -608,9 +649,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
@@ -705,7 +746,7 @@ DEPRECATED_IN_C23 char *ctime(time_t const *);
 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
@@ -727,9 +768,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
@@ -769,7 +810,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 offtime
@@ -875,7 +920,7 @@ ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t);
 		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) };
@@ -922,8 +967,8 @@ static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED
 # 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
diff --git a/southamerica b/southamerica
index c8d9097a..1fcf6514 100644
--- a/southamerica
+++ b/southamerica
@@ -1687,7 +1687,7 @@ Rule	Para	2005	2009	-	Mar	Sun>=8	0:00	0	-
 # and that on the first Sunday of the month of October, it is to be set
 # forward 60 minutes, in all the territory of the Paraguayan Republic.
 # ...
-Rule	Para	2010	max	-	Oct	Sun>=1	0:00	1:00	-
+Rule	Para	2010	2024	-	Oct	Sun>=1	0:00	1:00	-
 Rule	Para	2010	2012	-	Apr	Sun>=8	0:00	0	-
 #
 # From Steffen Thorsen (2013-03-07):
@@ -1706,14 +1706,35 @@ Rule	Para	2010	2012	-	Apr	Sun>=8	0:00	0	-
 # https://www.abc.com.py/politica/2023/07/12/promulgacion-el-cambio-de-hora-sera-por-ley/
 # From Carlos Raúl Perasso (2023-07-27):
 # http://silpy.congreso.gov.py/descarga/ley-144138
-Rule	Para	2013	max	-	Mar	Sun>=22	0:00	0	-
+Rule	Para	2013	2024	-	Mar	Sun>=22	0:00	0	-
+#
+# From Heitor David Pinto (2024-09-24):
+# Today the Congress of Paraguay passed a bill to observe UTC-3 permanently....
+# The text of the bill says that it would enter into force on the first
+# Sunday in October 2024, the same date currently scheduled to start DST....
+# https://silpy.congreso.gov.py/web/expediente/132531
+# (2024-10-14):
+# The president approved the law on 11 October 2024,
+# and it was officially published on 14 October 2024.
+# https://www.gacetaoficial.gov.py/index/detalle_publicacion/89723
+# The text of the law says that it enters into force on the first
+# Sunday in October 2024 (6 October 2024).  But the constitution
+# prohibits retroactive effect, and the civil code says that laws
+# enter into force on the day after their publication or on the day
+# that they specify, and it also says that they don't have retroactive
+# effect.  So I think that the time change on 6 October 2024 should
+# still be considered as DST according to the previous law, and
+# permanently UTC-3 from 15 October 2024 according to the new law....
+# https://www.constituteproject.org/constitution/Paraguay_2011
+# https://www.oas.org/dil/esp/codigo_civil_paraguay.pdf
 
 # Zone	NAME		STDOFF	RULES	FORMAT	[UNTIL]
 Zone America/Asuncion	-3:50:40 -	LMT	1890
 			-3:50:40 -	AMT	1931 Oct 10 # Asunción Mean Time
 			-4:00	-	%z	1972 Oct
 			-3:00	-	%z	1974 Apr
-			-4:00	Para	%z
+			-4:00	Para	%z	2024 Oct 15
+			-3:00	-	%z
 
 # Peru
 #
diff --git a/strftime.c b/strftime.c
index 755d341f..487a5234 100644
--- a/strftime.c
+++ b/strftime.c
@@ -39,8 +39,63 @@
 #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
 
 struct lc_time_T {
@@ -135,10 +190,6 @@ strftime(char *restrict s, size_t maxsize, char const *restrict format,
 
 	tzset();
 	p = _fmt(format, t, s, s + maxsize, &warn);
-	if (!p) {
-	  errno = EOVERFLOW;
-	  return 0;
-	}
 	if (DEPRECATE_TWO_DIGIT_YEARS
 	    && warn != IN_NONE && getenv(YEAR_2000_NAME)) {
 		fprintf(stderr, "\n");
@@ -170,7 +221,12 @@ _fmt(const char *format, const struct tm *t, char *pt,
 		if (*format == '%') {
 label:
 			switch (*++format) {
-			case '\0':
+			default:
+				/* 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.  */
 				--format;
 				break;
 			case 'A':
@@ -327,16 +383,17 @@ _fmt(const char *format, const struct tm *t, char *pt,
 					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.  */
 #ifdef TM_GMTOFF
 					mkt = timeoff(&tm, t->TM_GMTOFF);
 #else
 					tm.tm_isdst = t->tm_isdst;
 					mkt = mktime(&tm);
 #endif
-					/* If mktime fails, %s expands to the
-					   value of (time_t) -1 as a failure
-					   marker; this is better in practice
-					   than strftime failing.  */
 					if (TYPE_SIGNED(time_t)) {
 					  intmax_t n = mkt;
 					  sprintf(buf, "%"PRIdMAX, n);
@@ -590,12 +647,6 @@ _fmt(const char *format, const struct tm *t, char *pt,
 					warnp);
 				continue;
 			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;
 			}
 		}
diff --git a/theory.html b/theory.html
index d3573ede..352a3d87 100644
--- a/theory.html
+++ b/theory.html
@@ -123,8 +123,9 @@ <h2 id="naming">Timezone identifiers</h2>
 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 @@ <h2 id="naming">Timezone identifiers</h2>
   <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 @@ <h2 id="naming">Timezone identifiers</h2>
     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 @@ <h2 id="abbreviations">Time zone abbreviations</h2>
     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 @@ <h3 id="POSIX-extensions">Extensions to POSIX in the
     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 @@ <h3 id="vestigial">POSIX features no longer needed</h3>
     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 @@ <h3 id="other-portability">Other portability notes</h3>
     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>
diff --git a/time2posix.3 b/time2posix.3
index 6644060a..4a6969ed 100644
--- a/time2posix.3
+++ b/time2posix.3
@@ -5,15 +5,13 @@
 time2posix, posix2time \- convert seconds since the Epoch
 .SH SYNOPSIS
 .nf
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 .B #include <time.h>
 .PP
 .B time_t time2posix(time_t t);
 .PP
 .B time_t posix2time(time_t t);
 .PP
-.B cc ... \*-ltz
+.B cc ... \-ltz
 .fi
 .SH DESCRIPTION
 .ie '\(en'' .ds en \-
diff --git a/tz-link.html b/tz-link.html
index be2aae54..f4f76fdd 100644
--- a/tz-link.html
+++ b/tz-link.html
@@ -194,9 +194,9 @@ <h2 id="download">Downloading the <code><abbr>tz</abbr></code> database</h2>
 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 @@ <h2 id="changes">Changes to the <code><abbr>tz</abbr></code> database</h2>
 </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 @@ <h2 id="protocols">Network protocols for <code><abbr>tz</abbr></code> data</h2>
 <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 @@ <h2 id="national">National histories of legal time</h2>
 <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 @@ <h2 id="costs">Costs and benefits of time shifts</h2>
 <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 @@ <h2 id="precision">Precision timekeeping</h2>
 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 @@ <h2 id="precision">Precision timekeeping</h2>
 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 @@ <h2 id="notation">Time notation</h2>
 <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
diff --git a/tzfile.5 b/tzfile.5
index 6e2fd70b..a021859d 100644
--- a/tzfile.5
+++ b/tzfile.5
@@ -11,13 +11,11 @@ tzfile \- timezone information
 .de q
 \\$3\*(lq\\$1\*(rq\\$2
 ..
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 The timezone information files used by
 .BR tzset (3)
 are typically found under a directory with a name like
 .IR /usr/share/zoneinfo .
-These files use the format described in Internet RFC 8536.
+These files use the format described in Internet RFC 9636.
 Each file is a sequence of 8-bit bytes.
 In a file, a binary integer is represented by a sequence of one or
 more bytes in network order (bigendian, or high-order byte first),
@@ -123,7 +121,7 @@ and
 serves as an index into the array of time zone abbreviation bytes
 that follow the
 .B ttinfo
-entries in the file; if the designated string is "\*-00", the
+entries in the file; if the designated string is "\-00", the
 .B ttinfo
 entry is a placeholder indicating that local time is unspecified.
 The
@@ -146,7 +144,7 @@ The encoding of these strings is not specified.
 .IP \(bu
 .B tzh_leapcnt
 pairs of four-byte values, written in network byte order;
-the first value of each pair gives the nonnegative time
+the first value of each pair gives the non-negative time
 (as returned by
 .BR time (2))
 at which a leap second occurs or at which the leap second table expires;
@@ -159,7 +157,7 @@ Each pair denotes one leap second, either positive or negative,
 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 1 for a positive leap second,
@@ -188,7 +186,7 @@ The standard/wall and UT/local indicators were designed for
 transforming a TZif file's transition times into transitions appropriate
 for another time zone specified via
 a proleptic TZ string that lacks rules.
-For example, when TZ="EET\*-2EEST" and there is no TZif file "EET\*-2EEST",
+For example, when TZ="EET\-2EEST" and there is no TZif file "EET\-2EEST",
 the idea was to adapt the transition times from a TZif file with the
 well-known name "posixrules" that is present only for this purpose and
 is a copy of the file "Europe/Brussels", a file with a different UT offset.
@@ -197,7 +195,7 @@ the default rules are installation-dependent, and no implementation
 is known to support this feature for timestamps past 2037,
 so users desiring (say) Greek time should instead specify
 TZ="Europe/Athens" for better historical coverage, falling back on
-TZ="EET\*-2EEST,M3.5.0/3,M10.5.0/4" if POSIX conformance is required
+TZ="EET\-2EEST,M3.5.0/3,M10.5.0/4" if POSIX conformance is required
 and older timestamps need not be handled accurately.
 .PP
 The
@@ -223,7 +221,7 @@ after the last transition time stored in the file
 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
 .q "WET0WEST,M3.5.0/1,M10.5.0"
@@ -238,7 +236,7 @@ the earliest transition time.
 For version-3-format timezone files, a TZ string (see
 .BR newtzset (3))
 may use the following POSIX.1-2024 extensions to POSIX.1-2017:
-First, as in TZ="<\*-02>2<\*-01>,M3.5.0/\*-1,M10.5.0/0",
+First, as in TZ="<\-02>2<\-01>,M3.5.0/\-1,M10.5.0/0",
 the hours part of its transition times may be signed and range from
 \-167 through 167 instead of being limited to unsigned values
 from 0 through 24.
@@ -294,7 +292,7 @@ time did not exist (possibly with an error indication).
 Time zone designations should consist of at least three (3)
 and no more than six (6) ASCII characters from the set of
 alphanumerics,
-.q "\*-",
+.q "\-",
 and
 .q "+".
 This is for compatibility with POSIX requirements for
@@ -318,16 +316,16 @@ through 60 instead of the usual 59; the UTC offset is unaffected.
 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:
 .RS "\w'  'u"
 .IP \(bu "\w'\(bu  'u"
-to help TZif writers output files that avoid common
+TZif writers output files that avoid common
 pitfalls in older or buggy TZif readers,
 .IP \(bu
-to help TZif readers avoid common pitfalls when reading
+TZif readers avoid common pitfalls when reading
 files generated by future TZif writers, and
 .IP \(bu
-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.
 .RE
 .PP
@@ -338,9 +336,9 @@ 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
+workarounds as well as documenting other common bugs in
 readers.
 .PP
 Interoperability problems with TZif include the following:
@@ -373,15 +371,15 @@ for two time zones east, e.g.,
 for a time zone with a never-used standard time (XXX, \-03)
 and negative daylight saving time (EDT, \-04) all year.
 Alternatively,
-as a partial workaround a writer can substitute standard time
+as a partial workaround, a writer can substitute standard time
 for the next time zone east \(en e.g.,
 .q "AST4"
 for permanent
 Atlantic Standard Time (\-04).
 .IP \(bu
-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 9636 reject version 4 files whose leap second
+tables are truncated at the start or end in expiration times.
 .IP \(bu
 Some readers ignore the footer, and instead predict future
 timestamps from the time type of the last transition.
@@ -396,7 +394,7 @@ and even for current timestamps it can fail for settings like
 TZ="Africa/Casablanca".  This corresponds to a TZif file
 containing explicit transitions through the year 2087,
 followed by a footer containing the TZ string
-.q <+01>\*-1 ,
+.q <+01>\-1 ,
 which should be used only for timestamps after the last
 explicit transition.
 .IP \(bu
@@ -407,7 +405,7 @@ As a partial workaround, a writer can output a dummy (no-op)
 first transition at an early time.
 .IP \(bu
 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
@@ -419,7 +417,7 @@ Some readers mishandle a transition if its timestamp has
 the minimum possible signed 64-bit value.
 Timestamps less than \-2**59 are not recommended.
 .IP \(bu
-Some readers mishandle TZ strings that
+Some readers mishandle proleptic TZ strings that
 contain
 .q "<"
 or
@@ -436,9 +434,9 @@ non-ASCII characters.
 These characters are not recommended.
 .IP \(bu
 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,
-.q "\*-",
+.q "\-",
 and
 .q "+".
 These abbreviations are not recommended.
@@ -448,7 +446,7 @@ daylight-saving time UT offsets that are less than the UT
 offsets for the corresponding standard time.
 These readers do not support locations like Ireland, which
 uses the equivalent of the TZ string
-.q "IST\*-1GMT0,M10.5.0,M3.5.0/1",
+.q "IST\-1GMT0,M10.5.0,M3.5.0/1",
 observing standard time
 (IST, +01) in summer and daylight saving time (GMT, +00) in winter.
 As a partial workaround, a writer can output data for the
@@ -461,7 +459,7 @@ abbreviations correctly.
 .IP \(bu
 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
 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
@@ -480,15 +478,15 @@ Developers of distributed applications should keep this
 in mind if they need to deal with pre-1970 data.
 .IP \(bu
 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.
 .IP \(bu
 Some readers mishandle time zone abbreviations like
-.q "\*-08"
+.q "\-08"
 that contain
 .q "+",
-.q "\*-",
+.q "\-",
 or digits.
 .IP \(bu
 Some readers mishandle UT offsets that are out of the
@@ -497,7 +495,7 @@ support locations like Kiritimati that are outside this
 range.
 .IP \(bu
 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
 .q "+00".
 .IP \(bu
@@ -513,10 +511,10 @@ of one hour, or of 15 minutes, or of 1 minute.
 .BR zic (8).
 .PP
 Olson A, Eggert P, Murchison K. The Time Zone Information Format (TZif).
-2019 Feb.
-.UR https://\:datatracker.ietf.org/\:doc/\:html/\:rfc8536
-Internet RFC 8536
+October 2024.
+.UR https://\:www.rfc-editor.org/\:rfc/\:rfc9636
+Internet RFC 9636
 .UE
-.UR https://\:doi.org/\:10.17487/\:RFC8536
-doi:10.17487/RFC8536
+.UR https://\:doi.org/\:10.17487/\:RFC9636
+doi:10.17487/RFC9636
 .UE .
diff --git a/tzfile.h b/tzfile.h
index b1541466..f8c6c8c5 100644
--- a/tzfile.h
+++ b/tzfile.h
@@ -26,7 +26,7 @@
 #endif /* !defined TZDEFRULES */
 
 
-/* See Internet RFC 8536 for more details about the following format.  */
+/* See Internet RFC 9636 for more details about the following format.  */
 
 /*
 ** Each file begins with. . .
diff --git a/tzselect.8 b/tzselect.8
index ee031614..b83f702d 100644
--- a/tzselect.8
+++ b/tzselect.8
@@ -4,8 +4,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
@@ -20,15 +18,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
@@ -40,7 +38,7 @@ The output is suitable as a value for the TZ environment variable.
 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
@@ -70,27 +68,27 @@ seconds, with any trailing fractions represent fractional minutes or
 .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
diff --git a/zdump.8 b/zdump.8
index 38dd8614..9996039e 100644
--- a/zdump.8
+++ b/zdump.8
@@ -18,22 +18,27 @@ zdump \- timezone dumper
 .de q
 \\$3\*(lq\\$1\*(rq\\$2
 ..
-.ie \n(.g .ds - \f(CR-\fP
-.el .ds - \-
 The
 .B zdump
 program prints the current time in each
 .I timezone
 named on the command line.
+A
+.I timezone
+of
+.B \-
+is treated as if it were /dev/stdin;
+this can be used to pipe TZif data into
+.BR zdump .
 .SH OPTIONS
 .TP
-.B \*-\*-version
+.B \-\-version
 Output version information and exit.
 .TP
-.B \*-\*-help
+.B \-\-help
 Output short usage message and exit.
 .TP
-.B \*-i
+.B \-i
 Output a description of time intervals.  For each
 .I timezone
 on the command line, output an interval-format description of the
@@ -41,7 +46,7 @@ timezone.  See
 .q "INTERVAL FORMAT"
 below.
 .TP
-.B \*-v
+.B \-v
 Output a verbose description of time intervals.
 For each
 .I timezone
@@ -67,26 +72,26 @@ if the given local time is known to be
 .I N
 seconds east of Greenwich.
 .TP
-.B \*-V
+.B \-V
 Like
-.BR \*-v ,
+.BR \-v ,
 except omit output concerning extreme time and year values.
 This generates output that is easier to compare to that of
 implementations with different time representations.
 .TP
-.BI "\*-c " \fR[\fIloyear , \fR]\fIhiyear
+.BI "\-c " \fR[\fIloyear , \fR]\fIhiyear
 Cut off interval output at the given year(s).
 Cutoff times are computed using the proleptic Gregorian calendar with year 0
 and with Universal Time (UT) ignoring leap seconds.
 Cutoffs are at the start of each year, where the lower-bound
 timestamp is inclusive and the upper is exclusive; for example,
-.B "\*-c 1970,2070"
+.B "\-c 1970,2070"
 selects transitions on or after 1970-01-01 00:00:00 UTC
 and before 2070-01-01 00:00:00 UTC.
 The default cutoff is
-.BR \*-500,2500 .
+.BR \-500,2500 .
 .TP
-.BI "\*-t " \fR[\fIlotime , \fR]\fIhitime
+.BI "\-t " \fR[\fIlotime , \fR]\fIhitime
 Cut off interval output at the given time(s),
 given in decimal seconds since 1970-01-01 00:00:00
 Coordinated Universal Time (UTC).
@@ -94,7 +99,7 @@ The
 .I timezone
 determines whether the count includes leap seconds.
 As with
-.BR \*-c ,
+.BR \-c ,
 the cutoff's lower bound is inclusive and its upper bound is exclusive.
 .SH "INTERVAL FORMAT"
 The interval format is a compact text representation that is intended
@@ -104,7 +109,7 @@ then a line
 where
 .I string
 is a double-quoted string giving the timezone, a second line
-.q "\*- \*- \fIinterval\fP"
+.q "\- \- \fIinterval\fP"
 describing the time interval before the first transition if any, and
 zero or more following lines
 .q "\fIdate time interval\fP",
@@ -130,11 +135,11 @@ daylight saving time and negative for unknown.
 In times and in UT offsets with absolute value less than 100 hours,
 the seconds are omitted if they are zero, and
 the minutes are also omitted if they are also zero.  Positive UT
-offsets are east of Greenwich.  The UT offset \*-00 denotes a UT
+offsets are east of Greenwich.  The UT offset \-00 denotes a UT
 placeholder in areas where the actual offset is unspecified; by
 convention, this occurs when the UT offset is zero and the time zone
 abbreviation begins with
-.q "\*-"
+.q "\-"
 or is
 .q "zzz".
 .PP
@@ -211,9 +216,9 @@ This works in all real-world cases;
 one can construct artificial time zones for which this fails.
 .PP
 In the
-.B \*-v
+.B \-v
 and
-.B \*-V
+.B \-V
 output,
 .q "UT"
 denotes the value returned by
diff --git a/zdump.c b/zdump.c
index e8178733..8e836e60 100644
--- a/zdump.c
+++ b/zdump.c
@@ -14,10 +14,6 @@
 #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
@@ -148,17 +144,6 @@ sumsize(ptrdiff_t a, ptrdiff_t b)
   size_overflow();
 }
 
-/* Return the size of 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.  */
@@ -266,7 +251,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.  */
@@ -425,7 +410,7 @@ saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
   if (HAVE_LOCALTIME_RZ)
     return ab;
   else {
-    ptrdiff_t absize = xstrsize(ab);
+    ptrdiff_t absize = strlen(ab) + 1;
     if (*bufalloc < absize) {
       free(*buf);
 
@@ -487,6 +472,7 @@ main(int argc, char *argv[])
 	register time_t		cuthitime;
 	time_t			now;
 	bool iflag = false;
+	size_t arglenmax = 0;
 
 	cutlotime = absolute_min_time;
 	cuthitime = absolute_max_time;
@@ -586,15 +572,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);
+	  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;
@@ -695,7 +687,7 @@ yeartot(intmax_t y)
 				return absolute_max_time;
 			seconds = diff400 * SECSPER400YEARS;
 			years = diff400 * 400;
-                } else {
+		} else {
 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
 			years = 1;
 		}
@@ -926,13 +918,10 @@ showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
   }
 }
 
-#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;
@@ -960,6 +949,7 @@ my_snprintf(char *s, size_t size, char const *format, ...)
   va_end(args);
   return n;
 }
+# define snprintf my_snprintf
 #endif
 
 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
@@ -974,10 +964,10 @@ format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
 {
   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
@@ -1012,10 +1002,10 @@ format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
   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.
@@ -1118,7 +1108,7 @@ istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
 	    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;
@@ -1126,7 +1116,7 @@ istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
 	  }
 	  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;
diff --git a/zic.8 b/zic.8
index 00e2536b..4eeb7a46 100644
--- a/zic.8
+++ b/zic.8
@@ -22,14 +22,8 @@ zic \- timezone compiler
 .el .ds < \(la
 .ie '\(ra'' .ds > >
 .el .ds > \(ra
-.ie \n(.g \{\
-.  ds : \:
-.  ds - \f(CR-\fP
-.\}
-.el \{\
-.  ds :
-.  ds - \-
-.\}
+.ie \n(.g .ds : \:
+.el .ds : .
 .ds d " degrees
 .ds m " minutes
 .ds s " seconds
@@ -50,17 +44,17 @@ specified in this input.
 If a
 .I filename
 is
-.q "\*-" ,
+.q "\-" ,
 standard input is read.
 .SH OPTIONS
 .TP
-.B "\*-\*-version"
+.B "\-\-version"
 Output version information and exit.
 .TP
-.B \*-\*-help
+.B \-\-help
 Output short usage message and exit.
 .TP
-.BI "\*-b " bloat
+.BI "\-b " bloat
 Output backward-compatibility data as specified by
 .IR bloat .
 If
@@ -81,14 +75,14 @@ The default is
 as software that mishandles 64-bit data typically
 mishandles timestamps after the year 2038 anyway.
 Also see the
-.B \*-r
+.B \-r
 option for another way to alter output size.
 .TP
-.BI "\*-d " directory
+.BI "\-d " directory
 Create time conversion information files in the named directory rather than
 in the standard directory named below.
 .TP
-.BI "\*-l " timezone
+.BI "\-l " timezone
 Use
 .I timezone
 as local time.
@@ -102,19 +96,19 @@ Link	\fItimezone\fP		localtime
 If
 .I timezone
 is
-.BR \*- ,
+.BR \- ,
 any already-existing link is removed.
 .TP
-.BI "\*-L " leapsecondfilename
+.BI "\-L " leapsecondfilename
 Read leap second information from the file with the given name.
 If this option is not used,
 no leap second information appears in output files.
 .TP
-.BI "\*-p " timezone
+.BI "\-p " timezone
 Use
 .IR timezone 's
 rules when handling nonstandard
-TZ strings like "EET\*-2EEST" that lack transition rules.
+TZ strings like "EET\-2EEST" that lack transition rules.
 .B zic
 will act as if the input contained a link line of the form
 .sp
@@ -124,21 +118,21 @@ Link	\fItimezone\fP		posixrules
 If
 .I timezone
 is
-.q "\*-"
+.q "\-"
 (the default), any already-existing link is removed.
 .sp
 Unless
 .I timezone is
-.q "\*-" ,
+.q "\-" ,
 this option is obsolete and poorly supported.
 Among other things it should not be used for timestamps after the year 2037,
 and it should not be combined with
-.B "\*-b slim"
+.B "\-b slim"
 if
 .IR timezone 's
 transitions are at standard time or Universal Time (UT) instead of local time.
 .TP
-.BR "\*-r " "[\fB@\fP\fIlo\fP][\fB/@\fP\fIhi\fP]"
+.BR "\-r " "[\fB@\fP\fIlo\fP][\fB/@\fP\fIhi\fP]"
 Limit the applicability of output files
 to timestamps in the range from
 .I lo
@@ -152,17 +146,17 @@ are possibly signed decimal counts of seconds since the Epoch
 (1970-01-01 00:00:00 UTC).
 Omitted counts default to extreme values.
 The output files use UT offset 0 and abbreviation
-.q "\*-00"
+.q "\-00"
 in place of the omitted timestamp data.
 For example,
-.q "zic \*-r @0"
+.q "zic \-r @0"
 omits data intended for negative timestamps (i.e., before the Epoch), and
-.q "zic \*-r @0/@2147483648"
+.q "zic \-r @0/@2147483648"
 outputs data intended only for nonnegative timestamps that fit into
 31-bit signed integers.
 On platforms with GNU
 .BR date ,
-.q "zic \*-r @$(date +%s)"
+.q "zic \-r @$(date +%s)"
 omits data intended for past timestamps.
 Although this option typically reduces the output file's size,
 the size can increase due to the need to represent the timestamp range
@@ -173,10 +167,10 @@ causes a TZif file to contain explicit entries for
 transitions rather than concisely representing them
 with a proleptic TZ string.
 Also see the
-.B "\*-b slim"
+.B "\-b slim"
 option for another way to shrink output size.
 .TP
-.BI "\*-R @" hi
+.BI "\-R @" hi
 Generate redundant trailing explicit transitions for timestamps
 that occur less than
 .I hi
@@ -187,11 +181,11 @@ Although it accommodates nonstandard TZif readers
 that ignore the proleptic TZ string,
 it increases the size of the altered output files.
 .TP
-.BI "\*-t " file
+.BI "\-t " file
 When creating local time information, put the configuration link in
 the named file rather than in the standard location.
 .TP
-.B \*-v
+.B \-v
 Be more verbose, and complain about the following situations:
 .RS
 .PP
@@ -259,10 +253,10 @@ before 1970 or after the start of 2038.
 The output contains a truncated leap second table,
 which can cause some older TZif readers to misbehave.
 This can occur if the
-.B "\*-L"
+.B "\-L"
 option is used, and either an Expires line is present or
 the
-.B "\*-r"
+.B "\-r"
 option is also used.
 .PP
 The output file contains more than 1200 transitions,
@@ -276,13 +270,13 @@ POSIX requires at least 3, and requires implementations to support
 at least 6.
 .PP
 An output file name contains a byte that is not an ASCII letter,
-.q "\*-" ,
+.q "\-" ,
 .q "/" ,
 or
 .q "_" ;
 or it contains a file name component that contains more than 14 bytes
 or that starts with
-.q "\*-" .
+.q "\-" .
 .RE
 .SH FILES
 Input files use the format described in this section; output files use
@@ -301,7 +295,7 @@ non-PPCS bytes.  Non-PPCS characters typically occur only in comments:
 although output file names and time zone abbreviations can contain
 nearly any character, other software will work better if these are
 limited to the restricted syntax described under the
-.B \*-v
+.B \-v
 option.
 .PP
 Input lines are made up of fields.
@@ -331,14 +325,14 @@ abbreviation must be unambiguous in context.
 A rule line has the form
 .nf
 .ti +2
-.ta \w'Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'1973\0\0'u +\w'\*-\0\0'u +\w'Apr\0\0'u +\w'lastSun\0\0'u +\w'2:00w\0\0'u +\w'1:00d\0\0'u
+.ta \w'Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'1973\0\0'u +\w'\-\0\0'u +\w'Apr\0\0'u +\w'lastSun\0\0'u +\w'2:00w\0\0'u +\w'1:00d\0\0'u
 .sp
-Rule	NAME	FROM	TO	\*-	IN	ON	AT	SAVE	LETTER/S
+Rule	NAME	FROM	TO	\-	IN	ON	AT	SAVE	LETTER/S
 .sp
 For example:
 .ti +2
 .sp
-Rule	US	1967	1973	\*-	Apr	lastSun	2:00w	1:00d	D
+Rule	US	1967	1973	\-	Apr	lastSun	2:00w	1:00d	D
 .sp
 .fi
 The fields that make up a rule line are:
@@ -347,7 +341,7 @@ The fields that make up a rule line are:
 Gives the name of the rule set that contains this line.
 The name must start with a character that is neither
 an ASCII digit nor
-.q \*-
+.q \-
 nor
 .q + .
 To allow for future extensions,
@@ -375,9 +369,9 @@ may be used to repeat the value of the
 .B FROM
 field.
 .TP
-.B \*-
+.B \-
 Is a reserved field and should always contain
-.q \*-
+.q \-
 for compatibility with older versions of
 .BR zic .
 It was previously known as the
@@ -389,7 +383,15 @@ of years the rule would apply.
 .TP
 .B 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
+.q January ,
+.q JANU
+or
+.q Ja ,
+but not as
+.q j
+which would be ambiguous with both June and July.
 .TP
 .B ON
 Gives the day on which the rule takes effect.
@@ -412,7 +414,12 @@ or a weekday name preceded by
 .q "last"
 (e.g.,
 .BR "lastSunday" )
-may be abbreviated or spelled out in full.
+may be abbreviated as mentioned previously,
+e.g.,
+.q Su
+for Sunday and
+.q lastsa
+for the last Saturday.
 There must be no white space characters within the
 .B ON
 field.
@@ -442,8 +449,8 @@ Recognized forms include:
 15:00	3 PM, 15 hours after 00:00
 24:00	end of day, 24 hours after 00:00
 260:00	260 hours after 00:00
-\*-2:30	2.5 hours before 00:00
-\*-	equivalent to 0
+\-2:30	2.5 hours before 00:00
+\-	equivalent to 0
 .fi
 .in
 .sp
@@ -517,7 +524,7 @@ or
 .q "EDT" )
 of time zone abbreviations to be used when this rule is in effect.
 If this field is
-.q \*- ,
+.q \- ,
 the variable part is null.
 .PP
 A zone line has the form
@@ -564,7 +571,7 @@ field,
 giving the amount of time to be added to local standard time
 and whether the resulting time is standard or daylight saving.
 Standard time applies if this field is
-.B \*-
+.B \-
 or for timestamps occurring before any rule takes effect.
 When an amount of time is given, only the sum of standard time and
 this amount matters.
@@ -600,9 +607,9 @@ To conform to POSIX, a time zone abbreviation should contain only
 alphanumeric ASCII characters,
 .q "+"
 and
-.q "\*-".
+.q "\-".
 By convention, the time zone abbreviation
-.q "\*-00"
+.q "\-00"
 is a placeholder that means local time is unspecified.
 .TP
 .B UNTIL
@@ -661,25 +668,25 @@ For example:
 .ne 7
 .nf
 .in +2
-.ta \w'# Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'2006\0\0'u +\w'\*-\0\0'u +\w'Oct\0\0'u +\w'lastSun\0\0'u +\w'2:00\0\0'u +\w'SAVE\0\0'u
+.ta \w'# Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'2006\0\0'u +\w'\-\0\0'u +\w'Oct\0\0'u +\w'lastSun\0\0'u +\w'2:00\0\0'u +\w'SAVE\0\0'u
 .sp
-# Rule	NAME	FROM	TO	\*-	IN	ON	AT	SAVE	LETTER/S
+# Rule	NAME	FROM	TO	\-	IN	ON	AT	SAVE	LETTER/S
 Rule	US	1967	2006	-	Oct	lastSun	2:00	0	S
 Rule	US	1967	1973	-	Apr	lastSun	2:00	1:00	D
 .ta \w'# Zone\0\0'u +\w'America/Menominee\0\0'u +\w'STDOFF\0\0'u +\w'RULES\0\0'u +\w'FORMAT\0\0'u
 # Zone	NAME	STDOFF	RULES	FORMAT	[UNTIL]
-Zone	America/Menominee	\*-5:00	\*-	EST	1973 Apr 29 2:00
-		\*-6:00	US	C%sT
+Zone	America/Menominee	\-5:00	\-	EST	1973 Apr 29 2:00
+		\-6:00	US	C%sT
 .sp
 .in
 .fi
 Here, an incorrect reading would be there were two clock changes on 1973-04-29,
-the first from 02:00 EST (\*-05) to 01:00 CST (\*-06),
-and the second an hour later from 02:00 CST (\*-06) to 03:00 CDT (\*-05).
+the first from 02:00 EST (\-05) to 01:00 CST (\-06),
+and the second an hour later from 02:00 CST (\-06) to 03:00 CDT (\-05).
 However,
 .B zic
-interprets this more sensibly as a single transition from 02:00 CST (\*-05) to
-02:00 CDT (\*-05).
+interprets this more sensibly as a single transition from 02:00 CST (\-05) to
+02:00 CDT (\-05).
 .PP
 A link line has the form
 .sp
@@ -718,7 +725,7 @@ For example:
 .ta \w'Zone\0\0'u +\w'Greenwich\0\0'u
 Link	Greenwich	G_M_T
 Link	Etc/GMT	Greenwich
-Zone	Etc/GMT\0\00\0\0\*-\0\0GMT
+Zone	Etc/GMT\0\00\0\0\-\0\0GMT
 .sp
 .in
 .fi
@@ -759,7 +766,7 @@ should be
 .q "+"
 if a second was added
 or
-.q "\*-"
+.q "\-"
 if a second was skipped.
 The
 .B R/S
@@ -783,7 +790,7 @@ rolling leap seconds can be useful in specialized applications
 like SMPTE timecodes that may prefer to put leap second
 discontinuities at the end of a local broadcast day.
 However, rolling leap seconds are not supported if the
-.B \*-r
+.B \-r
 option is used.
 .PP
 The expiration line, if present, has the form:
@@ -814,23 +821,23 @@ Here is an extended example of
 input, intended to illustrate many of its features.
 .nf
 .in +2
-.ta \w'# Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'1973\0\0'u +\w'\*-\0\0'u +\w'Apr\0\0'u +\w'lastSun\0\0'u +\w'2:00\0\0'u +\w'SAVE\0\0'u
+.ta \w'# Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'1973\0\0'u +\w'\-\0\0'u +\w'Apr\0\0'u +\w'lastSun\0\0'u +\w'2:00\0\0'u +\w'SAVE\0\0'u
 .sp
-# Rule	NAME	FROM	TO	\*-	IN	ON	AT	SAVE	LETTER/S
-Rule	Swiss	1941	1942	\*-	May	Mon>=1	1:00	1:00	S
-Rule	Swiss	1941	1942	\*-	Oct	Mon>=1	2:00	0	\*-
+# Rule	NAME	FROM	TO	\-	IN	ON	AT	SAVE	LETTER/S
+Rule	Swiss	1941	1942	\-	May	Mon>=1	1:00	1:00	S
+Rule	Swiss	1941	1942	\-	Oct	Mon>=1	2:00	0	\-
 .sp .5
-Rule	EU	1977	1980	\*-	Apr	Sun>=1	1:00u	1:00	S
-Rule	EU	1977	only	\*-	Sep	lastSun	1:00u	0	\*-
-Rule	EU	1978	only	\*-	Oct	 1	1:00u	0	\*-
-Rule	EU	1979	1995	\*-	Sep	lastSun	1:00u	0	\*-
-Rule	EU	1981	max	\*-	Mar	lastSun	1:00u	1:00	S
-Rule	EU	1996	max	\*-	Oct	lastSun	1:00u	0	\*-
+Rule	EU	1977	1980	\-	Apr	Sun>=1	1:00u	1:00	S
+Rule	EU	1977	only	\-	Sep	lastSun	1:00u	0	\-
+Rule	EU	1978	only	\-	Oct	 1	1:00u	0	\-
+Rule	EU	1979	1995	\-	Sep	lastSun	1:00u	0	\-
+Rule	EU	1981	max	\-	Mar	lastSun	1:00u	1:00	S
+Rule	EU	1996	max	\-	Oct	lastSun	1:00u	0	\-
 .sp
 .ta \w'# Zone\0\0'u +\w'Europe/Zurich\0\0'u +\w'0:29:45.50\0\0'u +\w'RULES\0\0'u +\w'FORMAT\0\0'u
 # Zone	NAME	STDOFF	RULES	FORMAT	[UNTIL]
-Zone	Europe/Zurich	0:34:08	\*-	LMT	1853 Jul 16
-		0:29:45.50	\*-	BMT	1894 Jun
+Zone	Europe/Zurich	0:34:08	\-	LMT	1853 Jul 16
+		0:29:45.50	\-	BMT	1894 Jun
 		1:00	Swiss	CE%sT	1981
 		1:00	EU	CE%sT
 .sp
diff --git a/zic.c b/zic.c
index cf8e79df..8a7f83db 100644
--- a/zic.c
+++ b/zic.c
@@ -524,19 +524,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));
 }
@@ -565,7 +565,7 @@ growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems,
 {
   return (nitems < *nitems_alloc
 	  ? ptr
-	  : erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
+	  : xrealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
 }
 
 /*
@@ -1321,7 +1321,7 @@ random_dirent(char const **name, char **namealloc)
   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';
@@ -1410,7 +1410,7 @@ relname(char const *target, char const *linkname)
     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);
@@ -1424,7 +1424,7 @@ relname(char const *target, char const *linkname)
   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);
@@ -1866,8 +1866,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);
@@ -1950,7 +1950,8 @@ inzsub(char **fields, int nfields, bool iscont)
 	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"));
@@ -1988,9 +1989,9 @@ inzsub(char **fields, int nfields, bool iscont)
 		  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)
@@ -2133,8 +2134,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;
 }
@@ -2157,7 +2158,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	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)) {
@@ -2232,19 +2233,23 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	**	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;
@@ -2386,7 +2391,7 @@ writezone(const char *const name, const char *const string, char version,
 	/* 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;
@@ -2761,7 +2766,7 @@ writezone(const char *const name, const char *const string, char version,
 		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);
@@ -3007,7 +3012,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 
 	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;
@@ -3135,9 +3140,9 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	max_abbr_len = 2 + max_format_len + max_abbrvar_len;
 	max_envvar_len = 2 * max_abbr_len + 5 * 9;
 
-	startbuf = emalloc(max_abbr_len + 1);
-	ab = emalloc(max_abbr_len + 1);
-	envvar = emalloc(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);
 	/*
@@ -3912,7 +3917,7 @@ mp = _("time zone abbreviation differs from POSIX standard");
 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
diff --git a/zone.tab b/zone.tab
index bfc0b593..d2be6635 100644
--- a/zone.tab
+++ b/zone.tab
@@ -310,7 +310,7 @@ PF	-0900-13930	Pacific/Marquesas	Marquesas Islands
 PF	-2308-13457	Pacific/Gambier	Gambier Islands
 PG	-0930+14710	Pacific/Port_Moresby	most of Papua New Guinea
 PG	-0613+15534	Pacific/Bougainville	Bougainville
-PH	+1435+12100	Asia/Manila
+PH	+143512+1205804	Asia/Manila
 PK	+2452+06703	Asia/Karachi
 PL	+5215+02100	Europe/Warsaw
 PM	+4703-05620	America/Miquelon
diff --git a/zone1970.tab b/zone1970.tab
index 7726f39a..5ded0565 100644
--- a/zone1970.tab
+++ b/zone1970.tab
@@ -183,7 +183,7 @@ IR	+3540+05126	Asia/Tehran
 IT,SM,VA	+4154+01229	Europe/Rome
 JM	+175805-0764736	America/Jamaica
 JO	+3157+03556	Asia/Amman
-JP	+353916+1394441	Asia/Tokyo
+JP,AU	+353916+1394441	Asia/Tokyo	Eyre Bird Observatory
 KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT	-0117+03649	Africa/Nairobi
 KG	+4254+07436	Asia/Bishkek
 KI,MH,TV,UM,WF	+0125+17300	Pacific/Tarawa	Gilberts, Marshalls, Wake
@@ -246,7 +246,7 @@ PF	-0900-13930	Pacific/Marquesas	Marquesas Islands
 PF	-2308-13457	Pacific/Gambier	Gambier Islands
 PG,AQ,FM	-0930+14710	Pacific/Port_Moresby	Papua New Guinea (most areas), Chuuk, Yap, Dumont d'Urville
 PG	-0613+15534	Pacific/Bougainville	Bougainville
-PH	+1435+12100	Asia/Manila
+PH	+143512+1205804	Asia/Manila
 PK	+2452+06703	Asia/Karachi
 PL	+5215+02100	Europe/Warsaw
 PM	+4703-05620	America/Miquelon
@@ -293,7 +293,7 @@ RU	+6445+17729	Asia/Anadyr	MSK+09 - Bering Sea
 SA,AQ,KW,YE	+2438+04643	Asia/Riyadh	Syowa
 SB,FM	-0932+16012	Pacific/Guadalcanal	Pohnpei
 SD	+1536+03232	Africa/Khartoum
-SG,MY	+0117+10351	Asia/Singapore	peninsular Malaysia
+SG,AQ,MY	+0117+10351	Asia/Singapore	peninsular Malaysia, Concordia
 SR	+0550-05510	America/Paramaribo
 SS	+0451+03137	Africa/Juba
 ST	+0020+00644	Africa/Sao_Tome
diff --git a/zonenow.tab b/zonenow.tab
index 01f536b3..d2c1e485 100644
--- a/zonenow.tab
+++ b/zonenow.tab
@@ -97,9 +97,6 @@ XX	+1828-06954	America/Santo_Domingo	Atlantic Standard ("AST") - eastern Caribbe
 # -04/-03 (Chile DST)
 XX	-3327-07040	America/Santiago	most of Chile
 #
-# -04/-03 (Paraguay DST)
-XX	-2516-05740	America/Asuncion	Paraguay
-#
 # -04/-03 - AST/ADT (North America DST)
 XX	+4439-06336	America/Halifax	Atlantic ("AST/ADT") - Canada; Bermuda
 #
@@ -224,7 +221,7 @@ XX	+1345+10031	Asia/Bangkok	Russia; Indochina; Christmas Island
 XX	-0610+10648	Asia/Jakarta	Indonesia ("WIB")
 #
 # +08
-XX	+0117+10351	Asia/Singapore	Russia; Brunei; Malaysia; Singapore
+XX	+0117+10351	Asia/Singapore	Russia; Brunei; Malaysia; Singapore; Concordia
 #
 # +08 - AWST
 XX	-3157+11551	Australia/Perth	Western Australia ("AWST")
@@ -236,7 +233,7 @@ XX	+3114+12128	Asia/Shanghai	China ("CST")
 XX	+2217+11409	Asia/Hong_Kong	Hong Kong ("HKT")
 #
 # +08 - PHT
-XX	+1435+12100	Asia/Manila	Philippines ("PHT")
+XX	+143512+1205804	Asia/Manila	Philippines ("PHT")
 #
 # +08 - WITA
 XX	-0507+11924	Asia/Makassar	Indonesia ("WITA")
@@ -248,7 +245,7 @@ XX	-3143+12852	Australia/Eucla	Eucla
 XX	+5203+11328	Asia/Chita	Russia; Palau; East Timor
 #
 # +09 - JST
-XX	+353916+1394441	Asia/Tokyo	Japan ("JST")
+XX	+353916+1394441	Asia/Tokyo	Japan ("JST"); Eyre Bird Observatory
 #
 # +09 - KST
 XX	+3733+12658	Asia/Seoul	Korea ("KST")