From 8664ff598b309d9b778730a2982594b62241e0bb Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 31 May 2026 18:22:17 -0400
Subject: [PATCH v1] Sync our copy of the timezone library with IANA release
 tzcode2026b.

This was moderately tedious, because upstream has been busy
since we last did this in 2020.

Notably, they changed the signatures of both tzload() and tzparse(),
which we'd unwisely exposed as API for callers to use.  I concluded
that the best answer was to change them both back to "static" and
instead expose a new API function of our own choosing, pg_tzload().

This change may be a sufficient reason not to back-patch this update,
as I'd normally want to do.  There's probably not a good reason for
extensions to be calling those functions, but on the other hand
there are few pressing reasons for a back-patch.  The one bug we have
run into (a Valgrind uninitialized-data complaint about zic) appears
to have no field-visible consequences.

A few of the files generated by this version of zic are not
byte-for-byte the same as before, but "zdump -v" avers that
they represent the same sets of transitions.
---
 src/bin/initdb/findtimezone.c    |   19 +-
 src/timezone/README              |   20 +-
 src/timezone/localtime.c         | 1302 +++++++------
 src/timezone/pgtz.c              |   44 +-
 src/timezone/pgtz.h              |   73 +-
 src/timezone/private.h           |  252 ++-
 src/timezone/strftime.c          |   41 +-
 src/timezone/tzfile.h            |   55 +-
 src/timezone/zic.c               | 3132 ++++++++++++++++++++----------
 src/tools/pgindent/typedefs.list |    2 +
 10 files changed, 3066 insertions(+), 1874 deletions(-)

diff --git a/src/bin/initdb/findtimezone.c b/src/bin/initdb/findtimezone.c
index 910b97a570b..f7050df4841 100644
--- a/src/bin/initdb/findtimezone.c
+++ b/src/bin/initdb/findtimezone.c
@@ -96,23 +96,10 @@ pg_load_tz(const char *name)
 		return NULL;			/* not going to fit */
 
 	/*
-	 * "GMT" is always sent to tzparse(); see comments for pg_tzset().
+	 * Let the IANA tzdb code interpret the time zone name.
 	 */
-	if (strcmp(name, "GMT") == 0)
-	{
-		if (!tzparse(name, &tz.state, true))
-		{
-			/* This really, really should not happen ... */
-			return NULL;
-		}
-	}
-	else if (tzload(name, NULL, &tz.state, true) != 0)
-	{
-		if (name[0] == ':' || !tzparse(name, &tz.state, false))
-		{
-			return NULL;		/* unknown timezone */
-		}
-	}
+	if (!pg_tzload(name, NULL, &tz.state))
+		return NULL;			/* unknown timezone */
 
 	strcpy(tz.TZname, name);
 
diff --git a/src/timezone/README b/src/timezone/README
index 1857f03e3dd..0d48193d021 100644
--- a/src/timezone/README
+++ b/src/timezone/README
@@ -61,7 +61,7 @@ Windows, since we still need to match properly on older versions.
 Time Zone code
 ==============
 
-The code in this directory is currently synced with tzcode release 2020d.
+The code in this directory is currently synced with tzcode release 2026b.
 There are many cosmetic (and not so cosmetic) differences from the
 original tzcode library, but diffs in the upstream version should usually
 be propagated to our version.  Here are some notes about that.
@@ -71,9 +71,7 @@ several considerations preventing an exact match:
 
 * For readability/maintainability we reformat the code to match our own
 conventions; this includes pgindent'ing it and getting rid of upstream's
-overuse of "register" declarations.  (It used to include conversion of
-old-style function declarations to C89 style, but thank goodness they
-fixed that.)
+overuse of "register" declarations.
 
 * We need the code to follow Postgres' portability conventions; this
 includes relying on configure's results rather than hand-hacked
@@ -88,11 +86,8 @@ other exposed names.
 "lineno" in our typedefs list would cause unfortunate pgindent behavior
 in some other files where we have variables named that.
 
-* We have exposed the tzload() and tzparse() internal functions, and
-slightly modified the API of the former, in part because it now relies
-on our own pg_open_tzfile() rather than opening files for itself.
-
-* tzparse() is adjusted to never try to load the TZDEFRULES zone.
+* tzload() now relies on our own pg_open_tzfile() rather than opening
+files for itself.
 
 * There's a fair amount of code we don't need and have removed,
 including all the nonstandard optional APIs.  We have also added
@@ -109,18 +104,17 @@ to first run the tzcode source files through a sed filter like this:
     sed -r \
         -e 's/^([ \t]*)\*\*([ \t])/\1 *\2/' \
         -e 's/^([ \t]*)\*\*$/\1 */' \
-        -e 's|^\*/| */|' \
         -e 's/\bregister[ \t]//g' \
-        -e 's/\bATTRIBUTE_PURE[ \t]//g' \
         -e 's/struct[ \t]+tm\b/struct pg_tm/g' \
         -e 's/\btime_t\b/pg_time_t/g' \
         -e 's/lineno/lineno_t/g' \
+        -i *.h *.c
 
-and then run them through pgindent.  (The first three sed patterns deal
+and then run them through pgindent.  (The first two sed patterns deal
 with conversion of their block comment style to something pgindent
 won't make a hash of; the remainder address other points noted above.)
 After that, the files can be diff'd directly against our corresponding
 files.  Also, it's typically helpful to diff against the previous tzcode
-release (after processing that the same way), and then try to apply the
+release (after processing that the same way), and then try to apply that
 diff to our files.  This will take care of most of the changes
 mechanically.
diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c
index fb04b4cf6bf..28d753e6fec 100644
--- a/src/timezone/localtime.c
+++ b/src/timezone/localtime.c
@@ -10,7 +10,7 @@
 
 /*
  * Leap second handling from Bradley White.
- * POSIX-style TZ environment variable handling from Guy Harris.
+ * POSIX.1-1988 style TZ environment variable handling from Guy Harris.
  */
 
 /* this file needs to build in both frontend and backend contexts */
@@ -25,6 +25,13 @@
 #include "tzfile.h"
 
 
+/*
+ * 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.
+ */
+#define UNCONST(a) unconstify(char *, a)
+
 #ifndef WILDABBR
 /*
  * Someone might make incorrect use of a time zone abbreviation:
@@ -36,7 +43,7 @@
  *		in which Daylight Saving Time is never observed.
  *	4.	They might reference tzname[0] after setting to a time zone
  *		in which Standard Time is never observed.
- *	5.	They might reference tm.tm_zone after calling offtime.
+ *	5.	They might reference tm.TM_ZONE after calling offtime.
  * What's best to do in the above cases is open to debate;
  * for now, we just set things up so that in any of the five cases
  * WILDABBR is used. Another possibility: initialize tzname[0] to the
@@ -45,22 +52,59 @@
  * manual page of what this "time zone abbreviation" means (doing this so
  * that tzname[0] has the "normal" length of three characters).
  */
-#define WILDABBR	"   "
+#define WILDABBR "   "
 #endif							/* !defined WILDABBR */
 
 static const char wildabbr[] = WILDABBR;
 
-static const char gmt[] = "GMT";
-
 /*
- * The DST rules to use if a POSIX TZ string has no rules.
+ * The DST rules to use if TZ has no rules.
  * Default to US rules as of 2017-05-07.
  * POSIX does not specify the default DST rules;
  * for historical reasons, US rules are a common default.
  */
+#ifndef TZDEFRULESTRING
 #define TZDEFRULESTRING ",M3.2.0,M11.1.0"
+#endif
+
+/* TZNAME_MAXIMUM and types ttinfo, lsinfo, state have been moved to pgtz.h */
 
-/* structs ttinfo, lsinfo, state have been moved to pgtz.h */
+static int
+leapcount(ATTRIBUTE_MAYBE_UNUSED struct state const *sp)
+{
+#if TZ_RUNTIME_LEAPS
+	return sp->leapcnt;
+#else
+	return 0;
+#endif
+}
+static void
+set_leapcount(ATTRIBUTE_MAYBE_UNUSED struct state *sp,
+			  ATTRIBUTE_MAYBE_UNUSED int leapcnt)
+{
+#if TZ_RUNTIME_LEAPS
+	sp->leapcnt = leapcnt;
+#endif
+}
+static struct lsinfo
+lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state const *sp,
+	   ATTRIBUTE_MAYBE_UNUSED int i)
+{
+#if TZ_RUNTIME_LEAPS
+	return sp->lsis[i];
+#else
+	unreachable();
+#endif
+}
+static void
+set_lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state *sp,
+		   ATTRIBUTE_MAYBE_UNUSED int i,
+		   ATTRIBUTE_MAYBE_UNUSED struct lsinfo lsinfo)
+{
+#if TZ_RUNTIME_LEAPS
+	sp->lsis[i] = lsinfo;
+#endif
+}
 
 enum r_type
 {
@@ -85,12 +129,12 @@ struct rule
 static struct pg_tm *gmtsub(pg_time_t const *timep, int_fast32_t offset,
 							struct pg_tm *tmp);
 static bool increment_overflow(int *ip, int j);
-static bool increment_overflow_time(pg_time_t *tp, int_fast32_t j);
-static int_fast64_t leapcorr(struct state const *sp, pg_time_t t);
+static bool increment_overflow_time(pg_time_t *tp, int_fast32_2s j);
+static int_fast32_2s leapcorr(struct state const *sp, pg_time_t t);
 static struct pg_tm *timesub(pg_time_t const *timep,
 							 int_fast32_t offset, struct state const *sp,
 							 struct pg_tm *tmp);
-static bool typesequiv(struct state const *sp, int a, int b);
+static bool tzparse(const char *name, struct state *sp, struct state const *basep);
 
 
 /*
@@ -105,7 +149,8 @@ static struct pg_tm tm;
 
 /* 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;
@@ -114,15 +159,14 @@ init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx)
 	s->tt_ttisut = false;
 }
 
-static int_fast32_t
+static int_fast32_2s
 detzcode(const char *const codep)
 {
-	int_fast32_t result;
 	int			i;
-	int_fast32_t one = 1;
-	int_fast32_t halfmaxval = one << (32 - 2);
-	int_fast32_t maxval = halfmaxval - 1 + halfmaxval;
-	int_fast32_t minval = -1 - maxval;
+	int_fast32_2s
+				maxval = TWO_31_MINUS_1,
+				minval = -1 - maxval,
+				result;
 
 	result = codep[0] & 0x7f;
 	for (i = 1; i < 4; ++i)
@@ -132,9 +176,8 @@ detzcode(const char *const codep)
 	{
 		/*
 		 * Do two's-complement negation even on non-two's-complement machines.
-		 * If the result would be minval - 1, return minval.
+		 * This cannot overflow, as int_fast32_2s is wide enough.
 		 */
-		result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0;
 		result += minval;
 	}
 	return result;
@@ -143,7 +186,7 @@ detzcode(const char *const codep)
 static int_fast64_t
 detzcode64(const char *const codep)
 {
-	uint_fast64_t result;
+	int_fast64_t result;
 	int			i;
 	int_fast64_t one = 1;
 	int_fast64_t halfmaxval = one << (64 - 2);
@@ -166,21 +209,16 @@ detzcode64(const char *const codep)
 	return result;
 }
 
-static bool
-differ_by_repeat(const pg_time_t t1, const pg_time_t t0)
-{
-	if (TYPE_BIT(pg_time_t) - TYPE_SIGNED(pg_time_t) < SECSPERREPEAT_BITS)
-		return 0;
-	return t1 - t0 == SECSPERREPEAT;
-}
-
 /* Input buffer for data read from a compiled tz file.  */
 union input_buffer
 {
 	/* The first part of the buffer, interpreted as a header.  */
 	struct tzhead tzhead;
 
-	/* The entire buffer.  */
+	/*
+	 * The entire buffer.  Ideally this would have no size limits; the
+	 * following should suffice for practical use.
+	 */
 	char		buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state)
 					+ 4 * TZ_MAX_TIMES];
 };
@@ -198,25 +236,38 @@ union local_storage
 		struct state st;
 	}			u;
 
-	/* We don't need the "fullname" member */
+	/* PG: we don't need the "fullname" member */
 };
 
+/* 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.  */
+enum
+{
+TZLOAD_TZDIR_SUB = 4};			/* TZ should be a file under TZDIR.  */
+
 /*
- * Load tz data from the file named NAME into *SP.  Read extended
- * format if DOEXTEND.  Use *LSP for temporary storage.  Return 0 on
+ * Load tz data from the file named NAME into *SP.  Respect TZLOADFLAGS.
+ * Use **LSPP for temporary storage.  Return 0 on
  * success, an errno value on failure.
  * PG: If "canonname" is not NULL, then on success the canonical spelling of
  * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
  */
 static int
-tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
-		   union local_storage *lsp)
+tzloadbody(char const *name, char *canonname,
+		   struct state *sp, char tzloadflags,
+		   union local_storage **lspp)
 {
 	int			i;
 	int			fid;
 	int			stored;
 	ssize_t		nread;
-	union input_buffer *up = &lsp->u.u;
+	union local_storage *lsp = *lspp;
+	union input_buffer *up;
 	int			tzheadsize = sizeof(struct tzhead);
 
 	sp->goback = sp->goahead = false;
@@ -231,10 +282,17 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 	if (name[0] == ':')
 		++name;
 
+	/*
+	 * The IANA code goes to a great deal of trouble here to try to prevent
+	 * inappropriate file accesses.  That seems unnecessary for PG since we
+	 * won't run as root.  pg_open_tzfile() does go to some effort to prevent
+	 * accesses outside the designated zoneinfo tree, though.
+	 */
 	fid = pg_open_tzfile(name, canonname);
 	if (fid < 0)
 		return ENOENT;			/* pg_open_tzfile may not set errno */
 
+	up = &lsp->u.u;
 	nread = read(fid, up->buf, sizeof up->buf);
 	if (nread < tzheadsize)
 	{
@@ -245,16 +303,19 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 	}
 	if (close(fid) < 0)
 		return errno;
+
 	for (stored = 4; stored <= 8; stored *= 2)
 	{
-		int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
-		int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt);
-		int_fast64_t prevtr = 0;
-		int_fast32_t prevcorr = 0;
-		int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt);
-		int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt);
-		int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt);
-		int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt);
+		char		version = up->tzhead.tzh_version[0];
+		bool		skip_datablock = stored == 4 && version;
+		int_fast32_t datablock_size;
+		int_fast32_2s
+					ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt),
+					ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt),
+					leapcnt = detzcode(up->tzhead.tzh_leapcnt),
+					timecnt = detzcode(up->tzhead.tzh_timecnt),
+					typecnt = detzcode(up->tzhead.tzh_typecnt),
+					charcnt = detzcode(up->tzhead.tzh_charcnt);
 		char const *p = up->buf + tzheadsize;
 
 		/*
@@ -262,174 +323,213 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 		 * support future formats that may allow zero typecnt in files that
 		 * have a TZ string and no transitions.
 		 */
-		if (!(0 <= leapcnt && leapcnt < TZ_MAX_LEAPS
-			  && 0 <= typecnt && typecnt < TZ_MAX_TYPES
-			  && 0 <= timecnt && timecnt < TZ_MAX_TIMES
-			  && 0 <= charcnt && charcnt < TZ_MAX_CHARS
-			  && (ttisstdcnt == typecnt || ttisstdcnt == 0)
-			  && (ttisutcnt == typecnt || ttisutcnt == 0)))
+		if (!(0 <= leapcnt
+			  && leapcnt <= (TZ_RUNTIME_LEAPS ? TZ_MAX_LEAPS : 0)
+			  && 0 <= typecnt && typecnt <= TZ_MAX_TYPES
+			  && 0 <= timecnt && timecnt <= TZ_MAX_TIMES
+			  && 0 <= charcnt && charcnt <= TZ_MAX_CHARS
+			  && 0 <= ttisstdcnt && ttisstdcnt <= TZ_MAX_TYPES
+			  && 0 <= ttisutcnt && ttisutcnt <= TZ_MAX_TYPES))
 			return EINVAL;
-		if (nread
-			< (tzheadsize		/* struct tzhead */
-			   + timecnt * stored	/* ats */
+		datablock_size
+			= (timecnt * stored /* ats */
 			   + timecnt		/* types */
 			   + typecnt * 6	/* ttinfos */
 			   + charcnt		/* chars */
 			   + leapcnt * (stored + 4) /* lsinfos */
 			   + ttisstdcnt		/* ttisstds */
-			   + ttisutcnt))	/* ttisuts */
+			   + ttisutcnt);	/* ttisuts */
+		if (nread < tzheadsize + datablock_size)
 			return EINVAL;
-		sp->leapcnt = leapcnt;
-		sp->timecnt = timecnt;
-		sp->typecnt = typecnt;
-		sp->charcnt = charcnt;
-
-		/*
-		 * Read transitions, discarding those out of pg_time_t range. But
-		 * pretend the last transition before TIME_T_MIN occurred at
-		 * TIME_T_MIN.
-		 */
-		timecnt = 0;
-		for (i = 0; i < sp->timecnt; ++i)
+		if (skip_datablock)
+			p += datablock_size;
+		else if (!((ttisstdcnt == typecnt || ttisstdcnt == 0)
+				   && (ttisutcnt == typecnt || ttisutcnt == 0)))
+			return EINVAL;
+		else
 		{
-			int_fast64_t at
-			= stored == 4 ? detzcode(p) : detzcode64(p);
+			int_fast64_t prevtr = -1;
+			int_fast32_2s prevcorr = -1;
+
+			set_leapcount(sp, leapcnt);
+			sp->timecnt = timecnt;
+			sp->typecnt = typecnt;
+			sp->charcnt = charcnt;
 
-			sp->types[i] = at <= TIME_T_MAX;
-			if (sp->types[i])
+			/*
+			 * Read transitions, discarding those out of pg_time_t range. But
+			 * pretend the last transition before TIME_T_MIN occurred at
+			 * TIME_T_MIN.
+			 */
+			timecnt = 0;
+			for (i = 0; i < sp->timecnt; ++i)
 			{
-				pg_time_t	attime
-				= ((TYPE_SIGNED(pg_time_t) ? at < TIME_T_MIN : at < 0)
-				   ? TIME_T_MIN : at);
+				int_fast64_t at
+				= stored == 4 ? detzcode(p) : detzcode64(p);
 
-				if (timecnt && attime <= sp->ats[timecnt - 1])
+				sp->types[i] = at <= TIME_T_MAX;
+				if (sp->types[i])
 				{
-					if (attime < sp->ats[timecnt - 1])
-						return EINVAL;
-					sp->types[i - 1] = 0;
-					timecnt--;
+					pg_time_t	attime
+					= ((TYPE_SIGNED(pg_time_t) ? at < TIME_T_MIN : at < 0)
+					   ? TIME_T_MIN : at);
+
+					if (timecnt && attime <= sp->ats[timecnt - 1])
+					{
+						if (attime < sp->ats[timecnt - 1])
+							return EINVAL;
+						sp->types[i - 1] = 0;
+						timecnt--;
+					}
+					sp->ats[timecnt++] = attime;
 				}
-				sp->ats[timecnt++] = attime;
+				p += stored;
 			}
-			p += stored;
-		}
 
-		timecnt = 0;
-		for (i = 0; i < sp->timecnt; ++i)
-		{
-			unsigned char typ = *p++;
-
-			if (sp->typecnt <= typ)
-				return EINVAL;
-			if (sp->types[i])
-				sp->types[timecnt++] = typ;
-		}
-		sp->timecnt = timecnt;
-		for (i = 0; i < sp->typecnt; ++i)
-		{
-			struct ttinfo *ttisp;
-			unsigned char isdst,
-						desigidx;
-
-			ttisp = &sp->ttis[i];
-			ttisp->tt_utoff = detzcode(p);
-			p += 4;
-			isdst = *p++;
-			if (!(isdst < 2))
-				return EINVAL;
-			ttisp->tt_isdst = isdst;
-			desigidx = *p++;
-			if (!(desigidx < sp->charcnt))
-				return EINVAL;
-			ttisp->tt_desigidx = desigidx;
-		}
-		for (i = 0; i < sp->charcnt; ++i)
-			sp->chars[i] = *p++;
-		sp->chars[i] = '\0';	/* ensure '\0' at end */
+			timecnt = 0;
+			for (i = 0; i < sp->timecnt; ++i)
+			{
+				unsigned char typ = *p++;
 
-		/* Read leap seconds, discarding those out of pg_time_t range.  */
-		leapcnt = 0;
-		for (i = 0; i < sp->leapcnt; ++i)
-		{
-			int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p);
-			int_fast32_t corr = detzcode(p + stored);
-
-			p += stored + 4;
-			/* Leap seconds cannot occur before the Epoch.  */
-			if (tr < 0)
-				return EINVAL;
-			if (tr <= TIME_T_MAX)
+				if (sp->typecnt <= typ)
+					return EINVAL;
+				if (sp->types[i])
+					sp->types[timecnt++] = typ;
+			}
+			sp->timecnt = timecnt;
+			for (i = 0; i < sp->typecnt; ++i)
 			{
+				struct ttinfo *ttisp;
+				unsigned char isdst,
+							desigidx;
+				int_fast32_2s utoff = detzcode(p);
+
 				/*
-				 * Leap seconds cannot occur more than once per UTC month, and
-				 * UTC months are at least 28 days long (minus 1 second for a
-				 * negative leap second).  Each leap second's correction must
-				 * differ from the previous one's by 1 second.
+				 * Reject a UT offset equal to -2**31, as it might cause
+				 * trouble both in this file and in callers. Also, it violates
+				 * RFC 9636 section 3.2.
 				 */
-				if (tr - prevtr < 28 * SECSPERDAY - 1
-					|| (corr != prevcorr - 1 && corr != prevcorr + 1))
+				if (utoff < -TWO_31_MINUS_1)
 					return EINVAL;
-				sp->lsis[leapcnt].ls_trans = prevtr = tr;
-				sp->lsis[leapcnt].ls_corr = prevcorr = corr;
-				leapcnt++;
+
+				ttisp = &sp->ttis[i];
+				ttisp->tt_utoff = utoff;
+				p += 4;
+				isdst = *p++;
+				if (!(isdst < 2))
+					return EINVAL;
+				ttisp->tt_isdst = isdst;
+				desigidx = *p++;
+				if (!(desigidx < sp->charcnt))
+					return EINVAL;
+				ttisp->tt_desigidx = desigidx;
 			}
-		}
-		sp->leapcnt = leapcnt;
+			for (i = 0; i < sp->charcnt; ++i)
+				sp->chars[i] = *p++;
 
-		for (i = 0; i < sp->typecnt; ++i)
-		{
-			struct ttinfo *ttisp;
+			/*
+			 * Ensure '\0'-terminated, and make it safe to call ttunspecified
+			 * later.
+			 */
+			memset(&sp->chars[i], 0, CHARS_EXTRA);
 
-			ttisp = &sp->ttis[i];
-			if (ttisstdcnt == 0)
-				ttisp->tt_ttisstd = false;
-			else
+			/* Read leap seconds, discarding those out of pg_time_t range.  */
+			leapcnt = 0;
+			for (i = 0; i < leapcount(sp); i++)
 			{
-				if (*p != true && *p != false)
+				int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p);
+				int_fast32_2s corr = detzcode(p + stored);
+
+				p += stored + 4;
+
+				/*
+				 * Leap seconds cannot occur before the Epoch, or out of
+				 * order.
+				 */
+				if (tr <= prevtr)
+					return EINVAL;
+
+				/*
+				 * To avoid other botches in this code, each leap second's
+				 * 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 9636, to
+				 * allow future RFC extensions.
+				 */
+				if (!(i == 0
+					  || (prevcorr < corr
+						  ? corr == prevcorr + 1
+						  : (corr == prevcorr
+							 || corr == prevcorr - 1))))
 					return EINVAL;
-				ttisp->tt_ttisstd = *p++;
+				prevtr = tr;
+				prevcorr = corr;
+
+				if (tr <= TIME_T_MAX)
+				{
+					struct lsinfo ls;
+
+					ls.ls_trans = tr;
+					ls.ls_corr = corr;
+					set_lsinfo(sp, leapcnt, ls);
+					leapcnt++;
+				}
 			}
-		}
-		for (i = 0; i < sp->typecnt; ++i)
-		{
-			struct ttinfo *ttisp;
+			set_leapcount(sp, leapcnt);
 
-			ttisp = &sp->ttis[i];
-			if (ttisutcnt == 0)
-				ttisp->tt_ttisut = false;
-			else
+			for (i = 0; i < sp->typecnt; ++i)
 			{
-				if (*p != true && *p != false)
-					return EINVAL;
-				ttisp->tt_ttisut = *p++;
+				struct ttinfo *ttisp;
+
+				ttisp = &sp->ttis[i];
+				if (ttisstdcnt == 0)
+					ttisp->tt_ttisstd = false;
+				else
+				{
+					if (*p != true && *p != false)
+						return EINVAL;
+					ttisp->tt_ttisstd = *p++;
+				}
+			}
+			for (i = 0; i < sp->typecnt; ++i)
+			{
+				struct ttinfo *ttisp;
+
+				ttisp = &sp->ttis[i];
+				if (ttisutcnt == 0)
+					ttisp->tt_ttisut = false;
+				else
+				{
+					if (*p != true && *p != false)
+						return EINVAL;
+					ttisp->tt_ttisut = *p++;
+				}
 			}
 		}
 
-		/*
-		 * If this is an old file, we're done.
-		 */
-		if (up->tzhead.tzh_version[0] == '\0')
-			break;
 		nread -= p - up->buf;
 		memmove(up->buf, p, nread);
+
+		/* If this is an old file, we're done.  */
+		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;
 
 		up->buf[nread - 1] = '\0';
-		if (tzparse(&up->buf[1], ts, false))
+		if (tzparse(&up->buf[1], ts, sp))
 		{
 
 			/*
 			 * Attempt to reuse existing abbreviations. Without this,
-			 * America/Anchorage would be right on the edge after 2037 when
-			 * TZ_MAX_CHARS is 50, as sp->charcnt equals 40 (for LMT AST AWT
-			 * APT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for
-			 * AKST AKDT).  Reusing means sp->charcnt can stay 40 in this
-			 * example.
+			 * America/Anchorage would consume 50 bytes for abbreviations, as
+			 * sp->charcnt equals 40 (for LMT AST AWT APT AHST AHDT YST AKDT
+			 * AKST) and ts->charcnt equals 10 (for AKST AKDT).  Reusing means
+			 * sp->charcnt can stay 40 in this example.
 			 */
 			int			gotabbr = 0;
 			int			charcnt = sp->charcnt;
@@ -448,11 +548,14 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 					}
 				if (!(j < charcnt))
 				{
-					int			tsabbrlen = strlen(tsabbr);
+					int			tsabbrlen = strnlen(tsabbr, TZ_MAX_CHARS - j);
 
 					if (j + tsabbrlen < TZ_MAX_CHARS)
 					{
-						strcpy(sp->chars + j, tsabbr);
+						char	   *cp = sp->chars + j;
+
+						cp = mempcpy(cp, tsabbr, tsabbrlen);
+						*cp = '\0';
 						charcnt = j + tsabbrlen + 1;
 						ts->ttis[i].tt_desigidx = j;
 						gotabbr++;
@@ -473,20 +576,25 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 						   == sp->types[sp->timecnt - 2]))
 					sp->timecnt--;
 
+				sp->goahead = ts->goahead;
+
 				for (i = 0; i < ts->timecnt; i++)
-					if (sp->timecnt == 0
-						|| (sp->ats[sp->timecnt - 1]
-							< ts->ats[i] + leapcorr(sp, ts->ats[i])))
-						break;
-				while (i < ts->timecnt
-					   && sp->timecnt < TZ_MAX_TIMES)
 				{
-					sp->ats[sp->timecnt]
-						= ts->ats[i] + leapcorr(sp, ts->ats[i]);
+					pg_time_t	t = ts->ats[i];
+
+					if (increment_overflow_time(&t, leapcorr(sp, t))
+						|| (0 < sp->timecnt
+							&& t <= sp->ats[sp->timecnt - 1]))
+						continue;
+					if (TZ_MAX_TIMES <= sp->timecnt)
+					{
+						sp->goahead = false;
+						break;
+					}
+					sp->ats[sp->timecnt] = t;
 					sp->types[sp->timecnt] = (sp->typecnt
 											  + ts->types[i]);
 					sp->timecnt++;
-					i++;
 				}
 				for (i = 0; i < ts->typecnt; i++)
 					sp->ttis[sp->typecnt++] = ts->ttis[i];
@@ -495,135 +603,25 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 	}
 	if (sp->typecnt == 0)
 		return EINVAL;
-	if (sp->timecnt > 1)
-	{
-		for (i = 1; i < sp->timecnt; ++i)
-			if (typesequiv(sp, sp->types[i], sp->types[0]) &&
-				differ_by_repeat(sp->ats[i], sp->ats[0]))
-			{
-				sp->goback = true;
-				break;
-			}
-		for (i = sp->timecnt - 2; i >= 0; --i)
-			if (typesequiv(sp, sp->types[sp->timecnt - 1],
-						   sp->types[i]) &&
-				differ_by_repeat(sp->ats[sp->timecnt - 1],
-								 sp->ats[i]))
-			{
-				sp->goahead = true;
-				break;
-			}
-	}
-
-	/*
-	 * Infer sp->defaulttype from the data.  Although this default type is
-	 * always zero for data from recent tzdb releases, things are trickier for
-	 * data from tzdb 2018e or earlier.
-	 *
-	 * The first set of heuristics work around bugs in 32-bit data generated
-	 * by tzdb 2013c or earlier.  The workaround is for zones like
-	 * Australia/Macquarie where timestamps before the first transition have a
-	 * time type that is not the earliest standard-time type.  See:
-	 * https://mm.icann.org/pipermail/tz/2013-May/019368.html
-	 */
-
-	/*
-	 * If type 0 is unused in transitions, it's the type to use for early
-	 * times.
-	 */
-	for (i = 0; i < sp->timecnt; ++i)
-		if (sp->types[i] == 0)
-			break;
-	i = i < sp->timecnt ? -1 : 0;
-
-	/*
-	 * Absent the above, if there are transition times and the first
-	 * transition is to a daylight time find the standard type less than and
-	 * closest to the type of the first transition.
-	 */
-	if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst)
-	{
-		i = sp->types[0];
-		while (--i >= 0)
-			if (!sp->ttis[i].tt_isdst)
-				break;
-	}
-
-	/*
-	 * The next heuristics are for data generated by tzdb 2018e or earlier,
-	 * for zones like EST5EDT where the first transition is to DST.
-	 */
-
-	/*
-	 * If no result yet, find the first standard type. If there is none, punt
-	 * to type zero.
-	 */
-	if (i < 0)
-	{
-		i = 0;
-		while (sp->ttis[i].tt_isdst)
-			if (++i >= sp->typecnt)
-			{
-				i = 0;
-				break;
-			}
-	}
-
-	/*
-	 * A simple 'sp->defaulttype = 0;' would suffice here if we didn't have to
-	 * worry about 2018e-or-earlier data.  Even simpler would be to remove the
-	 * defaulttype member and just use 0 in its place.
-	 */
-	sp->defaulttype = i;
 
 	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.
  * PG: If "canonname" is not NULL, then on success the canonical spelling of
  * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
  */
-int
-tzload(char const *name, char *canonname, struct state *sp, bool doextend)
-{
-	union local_storage *lsp = malloc(sizeof *lsp);
-
-	if (!lsp)
-		return errno;
-	else
-	{
-		int			err = tzloadbody(name, canonname, sp, doextend, lsp);
-
-		free(lsp);
-		return err;
-	}
-}
-
-static bool
-typesequiv(const struct state *sp, int a, int b)
+static int
+tzload(char const *name, char *canonname, struct state *sp, char tzloadflags)
 {
-	bool		result;
+	/* PG: our version of tzloadbody never reallocates *lspp */
+	union local_storage *lsp;
+	union local_storage ls;
 
-	if (sp == NULL ||
-		a < 0 || a >= sp->typecnt ||
-		b < 0 || b >= sp->typecnt)
-		result = false;
-	else
-	{
-		const struct ttinfo *ap = &sp->ttis[a];
-		const struct ttinfo *bp = &sp->ttis[b];
-
-		result = (ap->tt_utoff == bp->tt_utoff
-				  && ap->tt_isdst == bp->tt_isdst
-				  && ap->tt_ttisstd == bp->tt_ttisstd
-				  && ap->tt_ttisut == bp->tt_ttisut
-				  && (strcmp(&sp->chars[ap->tt_desigidx],
-							 &sp->chars[bp->tt_desigidx])
-					  == 0));
-	}
-	return result;
+	lsp = &ls;
+	return tzloadbody(name, canonname, sp, tzloadflags, &lsp);
 }
 
 static const int mon_lengths[2][MONSPERYEAR] = {
@@ -635,13 +633,20 @@ static const int year_lengths[2] = {
 	DAYSPERNYEAR, DAYSPERLYEAR
 };
 
+/* Is C an ASCII digit?  */
+static bool
+is_digit(char c)
+{
+	return '0' <= c && c <= '9';
+}
+
 /*
  * Given a pointer into a timezone string, scan until a character that is not
  * a valid character in a time zone abbreviation is found.
  * Return a pointer to that character.
  */
 
-static const char *
+ATTRIBUTE_PURE_114833 static const char *
 getzname(const char *strp)
 {
 	char		c;
@@ -662,7 +667,7 @@ getzname(const char *strp)
  * We don't do any checking here; checking is done later in common-case code.
  */
 
-static const char *
+ATTRIBUTE_PURE_114833 static const char *
 getqzname(const char *strp, const int delim)
 {
 	int			c;
@@ -713,16 +718,17 @@ static const char *
 getsecs(const char *strp, int_fast32_t *const secsp)
 {
 	int			num;
+	int_fast32_t secsperhour = SECSPERHOUR;
 
 	/*
-	 * 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
-	 * "M10.4.6/26", which does not conform to Posix, but which specifies the
+	 * 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-POSIX rules like
+	 * "M10.4.6/26", which does not conform to POSIX, but which specifies the
 	 * equivalent of "02:00 on the first Sunday on or after 23 Oct".
 	 */
 	strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
 	if (strp == NULL)
 		return NULL;
-	*secsp = num * (int_fast32_t) SECSPERHOUR;
+	*secsp = num * secsperhour;
 	if (*strp == ':')
 	{
 		++strp;
@@ -772,7 +778,8 @@ getoffset(const char *strp, int_fast32_t *const offsetp)
 
 /*
  * Given a pointer into a timezone string, extract a rule in the form
- * date[/time]. See POSIX section 8 for the format of "date" and "time".
+ * date[/time]. See POSIX Base Definitions section 8.3 variable TZ
+ * for the format of "date" and "time".
  * If a valid rule is not found, return NULL.
  * Otherwise, return a pointer to the first character not part of the rule.
  */
@@ -852,7 +859,6 @@ transtime(const int year, const struct rule *const rulep,
 				yy2,
 				dow;
 
-	INITIALIZE(value);
 	leapyear = isleap(year);
 	switch (rulep->r_type)
 	{
@@ -920,6 +926,9 @@ transtime(const int year, const struct rule *const rulep,
 			for (i = 0; i < rulep->r_mon - 1; ++i)
 				value += mon_lengths[leapyear][i] * SECSPERDAY;
 			break;
+
+		default:
+			unreachable();
 	}
 
 	/*
@@ -931,73 +940,74 @@ transtime(const int year, const struct rule *const rulep,
 }
 
 /*
- * Given a POSIX section 8-style TZ string, fill in the rule tables as
+ * Given a POSIX.1 proleptic TZ string, fill in the rule tables as
  * appropriate.
- * Returns true on success, false on failure.
  */
-bool
-tzparse(const char *name, struct state *sp, bool lastditch)
+
+static bool
+tzparse(const char *name, struct state *sp, struct state const *basep)
 {
 	const char *stdname;
 	const char *dstname = NULL;
-	size_t		stdlen;
-	size_t		dstlen;
-	size_t		charcnt;
 	int_fast32_t stdoffset;
 	int_fast32_t dstoffset;
 	char	   *cp;
-	bool		load_ok;
+	ptrdiff_t	stdlen,
+				dstlen,
+				charcnt;
+	pg_time_t	atlo = TIME_T_MIN,
+				leaplo = TIME_T_MIN;
 
 	stdname = name;
-	if (lastditch)
+	if (*name == '<')
 	{
-		/* Unlike IANA, don't assume name is exactly "GMT" */
-		stdlen = strlen(name);	/* length of standard zone name */
-		name += stdlen;
-		stdoffset = 0;
+		name++;
+		stdname = name;
+		name = getqzname(name, '>');
+		if (*name != '>')
+			return false;
+		stdlen = name - stdname;
+		name++;
 	}
 	else
 	{
-		if (*name == '<')
-		{
-			name++;
-			stdname = name;
-			name = getqzname(name, '>');
-			if (*name != '>')
-				return false;
-			stdlen = name - stdname;
-			name++;
-		}
-		else
-		{
-			name = getzname(name);
-			stdlen = name - stdname;
-		}
-		if (*name == '\0')		/* we allow empty STD abbrev, unlike IANA */
-			return false;
-		name = getoffset(name, &stdoffset);
-		if (name == NULL)
-			return false;
+		name = getzname(name);
+		stdlen = name - stdname;
 	}
-	charcnt = stdlen + 1;
-	if (sizeof sp->chars < charcnt)
+	if (stdlen > TZNAME_MAXIMUM)	/* allow empty STD abbrev, unlike IANA */
 		return false;
+	name = getoffset(name, &stdoffset);
+	if (name == NULL)
+		return false;
+	charcnt = stdlen + 1;
+	if (basep)
+	{
+		if (0 < basep->timecnt)
+			atlo = basep->ats[basep->timecnt - 1];
+		set_leapcount(sp, leapcount(basep));
+		if (0 < leapcount(sp))
+		{
+			int			i;
 
-	/*
-	 * The IANA code always tries to tzload(TZDEFRULES) here.  We do not want
-	 * to do that; it would be bad news in the lastditch case, where we can't
-	 * assume pg_open_tzfile() is sane yet.  Moreover, if we did load it and
-	 * it contains leap-second-dependent info, that would cause problems too.
-	 * Finally, IANA has deprecated the TZDEFRULES feature, so it presumably
-	 * will die at some point.  Desupporting it now seems like good
-	 * future-proofing.
-	 */
-	load_ok = false;
-	sp->goback = sp->goahead = false;	/* simulate failed tzload() */
-	sp->leapcnt = 0;			/* intentionally assume no leap seconds */
-
+			for (i = 0; i < leapcount(sp); i++)
+				set_lsinfo(sp, i, lsinfo(basep, i));
+			leaplo = lsinfo(sp, leapcount(sp) - 1).ls_trans;
+		}
+	}
+	else
+		set_leapcount(sp, 0);	/* So, we're off a little.  */
+	sp->goback = sp->goahead = false;
 	if (*name != '\0')
 	{
+		struct rule start,
+					end;
+		int			year,
+					yearbeg,
+					yearlim,
+					timecnt;
+		pg_time_t	janfirst;
+		int_fast32_t janoffset = 0;
+
 		if (*name == '<')
 		{
 			dstname = ++name;
@@ -1013,11 +1023,9 @@ tzparse(const char *name, struct state *sp, bool lastditch)
 			name = getzname(name);
 			dstlen = name - dstname;	/* length of DST abbr. */
 		}
-		if (!dstlen)
+		if (!(0 < dstlen && dstlen <= TZNAME_MAXIMUM))
 			return false;
 		charcnt += dstlen + 1;
-		if (sizeof sp->chars < charcnt)
-			return false;
 		if (*name != '\0' && *name != ',' && *name != ';')
 		{
 			name = getoffset(name, &dstoffset);
@@ -1026,202 +1034,117 @@ tzparse(const char *name, struct state *sp, bool lastditch)
 		}
 		else
 			dstoffset = stdoffset - SECSPERHOUR;
-		if (*name == '\0' && !load_ok)
-			name = TZDEFRULESTRING;
-		if (*name == ',' || *name == ';')
-		{
-			struct rule start;
-			struct rule end;
-			int			year;
-			int			yearlim;
-			int			timecnt;
-			pg_time_t	janfirst;
-			int_fast32_t janoffset = 0;
-			int			yearbeg;
-
-			++name;
-			if ((name = getrule(name, &start)) == NULL)
-				return false;
-			if (*name++ != ',')
-				return false;
-			if ((name = getrule(name, &end)) == NULL)
-				return false;
-			if (*name != '\0')
-				return false;
-			sp->typecnt = 2;	/* standard time and DST */
 
-			/*
-			 * Two transitions per year, from EPOCH_YEAR forward.
-			 */
-			init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
-			init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
-			sp->defaulttype = 0;
-			timecnt = 0;
-			janfirst = 0;
-			yearbeg = EPOCH_YEAR;
+		if (*name == '\0')
+			name = TZDEFRULESTRING;
+		if (!(*name == ',' || *name == ';'))
+			return false;
 
-			do
-			{
-				int_fast32_t yearsecs
-				= year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
+		name = getrule(name + 1, &start);
+		if (!name)
+			return false;
+		if (*name++ != ',')
+			return false;
+		name = getrule(name, &end);
+		if (!name || *name)
+			return false;
+		sp->typecnt = 2;		/* standard time and DST */
 
-				yearbeg--;
-				if (increment_overflow_time(&janfirst, -yearsecs))
-				{
-					janoffset = -yearsecs;
-					break;
-				}
-			} while (EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+		/*
+		 * Two transitions per year, from EPOCH_YEAR forward.
+		 */
+		init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
+		init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
+		timecnt = 0;
+		janfirst = 0;
+		yearbeg = EPOCH_YEAR;
 
-			yearlim = yearbeg + YEARSPERREPEAT + 1;
-			for (year = yearbeg; year < yearlim; year++)
-			{
-				int_fast32_t
-							starttime = transtime(year, &start, stdoffset),
-							endtime = transtime(year, &end, dstoffset);
-				int_fast32_t
-							yearsecs = (year_lengths[isleap(year)]
-										* SECSPERDAY);
-				bool		reversed = endtime < starttime;
-
-				if (reversed)
-				{
-					int_fast32_t swap = starttime;
+		do
+		{
+			int_fast32_t yearsecs
+			= year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
+			pg_time_t	janfirst1 = janfirst;
 
-					starttime = endtime;
-					endtime = swap;
-				}
-				if (reversed
-					|| (starttime < endtime
-						&& (endtime - starttime
-							< (yearsecs
-							   + (stdoffset - dstoffset)))))
-				{
-					if (TZ_MAX_TIMES - 2 < timecnt)
-						break;
-					sp->ats[timecnt] = janfirst;
-					if (!increment_overflow_time
-						(&sp->ats[timecnt],
-						 janoffset + starttime))
-						sp->types[timecnt++] = !reversed;
-					sp->ats[timecnt] = janfirst;
-					if (!increment_overflow_time
-						(&sp->ats[timecnt],
-						 janoffset + endtime))
-					{
-						sp->types[timecnt++] = reversed;
-						yearlim = year + YEARSPERREPEAT + 1;
-					}
-				}
-				if (increment_overflow_time
-					(&janfirst, janoffset + yearsecs))
-					break;
-				janoffset = 0;
-			}
-			sp->timecnt = timecnt;
-			if (!timecnt)
+			yearbeg--;
+			if (increment_overflow_time(&janfirst1, -yearsecs))
 			{
-				sp->ttis[0] = sp->ttis[1];
-				sp->typecnt = 1;	/* Perpetual DST.  */
+				janoffset = -yearsecs;
+				break;
 			}
-			else if (YEARSPERREPEAT < year - yearbeg)
-				sp->goback = sp->goahead = true;
-		}
-		else
+			janfirst = janfirst1;
+		} while (atlo < janfirst
+				 && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+
+		while (true)
 		{
-			int_fast32_t theirstdoffset;
-			int_fast32_t theirdstoffset;
-			int_fast32_t theiroffset;
-			bool		isdst;
-			int			i;
-			int			j;
+			int_fast32_t yearsecs
+			= year_lengths[isleap(yearbeg)] * SECSPERDAY;
+			int			yearbeg1 = yearbeg;
+			pg_time_t	janfirst1 = janfirst;
+
+			if (increment_overflow_time(&janfirst1, yearsecs)
+				|| increment_overflow(&yearbeg1, 1)
+				|| atlo <= janfirst1)
+				break;
+			yearbeg = yearbeg1;
+			janfirst = janfirst1;
+		}
 
-			if (*name != '\0')
-				return false;
+		yearlim = yearbeg;
+		if (increment_overflow(&yearlim, years_of_observations))
+			yearlim = INT_MAX;
+		for (year = yearbeg; year < yearlim; year++)
+		{
+			int_fast32_t
+						starttime = transtime(year, &start, stdoffset),
+						endtime = transtime(year, &end, dstoffset),
+						yearsecs = year_lengths[isleap(year)] * SECSPERDAY;
+			bool		reversed = endtime < starttime;
 
-			/*
-			 * Initial values of theirstdoffset and theirdstoffset.
-			 */
-			theirstdoffset = 0;
-			for (i = 0; i < sp->timecnt; ++i)
+			if (reversed)
 			{
-				j = sp->types[i];
-				if (!sp->ttis[j].tt_isdst)
-				{
-					theirstdoffset =
-						-sp->ttis[j].tt_utoff;
-					break;
-				}
+				int_fast32_t swap = starttime;
+
+				starttime = endtime;
+				endtime = swap;
 			}
-			theirdstoffset = 0;
-			for (i = 0; i < sp->timecnt; ++i)
+			if (reversed
+				|| (starttime < endtime
+					&& endtime - starttime < yearsecs))
 			{
-				j = sp->types[i];
-				if (sp->ttis[j].tt_isdst)
-				{
-					theirdstoffset =
-						-sp->ttis[j].tt_utoff;
+				if (TZ_MAX_TIMES - 2 < timecnt)
 					break;
+				sp->ats[timecnt] = janfirst;
+				if (!increment_overflow_time(&sp->ats[timecnt],
+											 janoffset + starttime)
+					&& atlo <= sp->ats[timecnt])
+					sp->types[timecnt++] = !reversed;
+				sp->ats[timecnt] = janfirst;
+				if (!increment_overflow_time(&sp->ats[timecnt],
+											 janoffset + endtime)
+					&& atlo <= sp->ats[timecnt])
+				{
+					sp->types[timecnt++] = reversed;
 				}
 			}
-
-			/*
-			 * Initially we're assumed to be in standard time.
-			 */
-			isdst = false;
-			theiroffset = theirstdoffset;
-
-			/*
-			 * Now juggle transition times and types tracking offsets as you
-			 * do.
-			 */
-			for (i = 0; i < sp->timecnt; ++i)
+			if (endtime < leaplo)
 			{
-				j = sp->types[i];
-				sp->types[i] = sp->ttis[j].tt_isdst;
-				if (sp->ttis[j].tt_ttisut)
-				{
-					/* No adjustment to transition time */
-				}
-				else
-				{
-					/*
-					 * If daylight saving time is in effect, and the
-					 * transition time was not specified as standard time, add
-					 * the daylight saving time offset to the transition time;
-					 * otherwise, add the standard time offset to the
-					 * transition time.
-					 */
-					/*
-					 * Transitions from DST to DDST will effectively disappear
-					 * since POSIX provides for only one DST offset.
-					 */
-					if (isdst && !sp->ttis[j].tt_ttisstd)
-					{
-						sp->ats[i] += dstoffset -
-							theirdstoffset;
-					}
-					else
-					{
-						sp->ats[i] += stdoffset -
-							theirstdoffset;
-					}
-				}
-				theiroffset = -sp->ttis[j].tt_utoff;
-				if (sp->ttis[j].tt_isdst)
-					theirdstoffset = theiroffset;
-				else
-					theirstdoffset = theiroffset;
+				yearlim = year;
+				if (increment_overflow(&yearlim, years_of_observations))
+					yearlim = INT_MAX;
 			}
-
-			/*
-			 * Finally, fill in ttis.
-			 */
-			init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
-			init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
-			sp->typecnt = 2;
-			sp->defaulttype = 0;
+			if (increment_overflow_time(&janfirst, janoffset + yearsecs))
+				break;
+			janoffset = 0;
+		}
+		sp->timecnt = timecnt;
+		if (!timecnt)
+		{
+			sp->ttis[0] = sp->ttis[1];
+			sp->typecnt = 1;	/* Perpetual DST.  */
 		}
+		else if (years_of_observations <= year - yearbeg)
+			sp->goback = sp->goahead = true;
 	}
 	else
 	{
@@ -1229,17 +1152,15 @@ tzparse(const char *name, struct state *sp, bool lastditch)
 		sp->typecnt = 1;		/* only standard time */
 		sp->timecnt = 0;
 		init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
-		sp->defaulttype = 0;
 	}
 	sp->charcnt = charcnt;
 	cp = sp->chars;
-	memcpy(cp, stdname, stdlen);
-	cp += stdlen;
+	cp = mempcpy(cp, stdname, stdlen);
 	*cp++ = '\0';
 	if (dstlen != 0)
 	{
-		memcpy(cp, dstname, dstlen);
-		*(cp + dstlen) = '\0';
+		cp = mempcpy(cp, dstname, dstlen);
+		*cp = '\0';
 	}
 	return true;
 }
@@ -1247,8 +1168,8 @@ tzparse(const char *name, struct state *sp, bool lastditch)
 static void
 gmtload(struct state *const sp)
 {
-	if (tzload(gmt, NULL, sp, true) != 0)
-		tzparse(gmt, sp, true);
+	/* PG: for historical compatibility, use "GMT" not "UTC" as TZ abbrev */
+	tzparse("GMT0", sp, NULL);
 }
 
 
@@ -1272,7 +1193,7 @@ localsub(struct state const *sp, pg_time_t const *timep,
 	if ((sp->goback && t < sp->ats[0]) ||
 		(sp->goahead && t > sp->ats[sp->timecnt - 1]))
 	{
-		pg_time_t	newt = t;
+		pg_time_t	newt;
 		pg_time_t	seconds;
 		pg_time_t	years;
 
@@ -1281,18 +1202,33 @@ localsub(struct state const *sp, pg_time_t const *timep,
 		else
 			seconds = t - sp->ats[sp->timecnt - 1];
 		--seconds;
-		years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT;
+
+		/*
+		 * Beware integer overflow, as SECONDS might be close to the maximum
+		 * pg_time_t.
+		 */
+		years = seconds / SECSPERREPEAT * YEARSPERREPEAT;
 		seconds = years * AVGSECSPERYEAR;
+		years += YEARSPERREPEAT;
 		if (t < sp->ats[0])
-			newt += seconds;
+			newt = t + seconds + SECSPERREPEAT;
 		else
-			newt -= seconds;
+			newt = t - seconds - SECSPERREPEAT;
+
 		if (newt < sp->ats[0] ||
 			newt > sp->ats[sp->timecnt - 1])
 			return NULL;		/* "cannot happen" */
 		result = localsub(sp, &newt, tmp);
 		if (result)
 		{
+#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
 			int_fast64_t newy;
 
 			newy = result->tm_year;
@@ -1303,12 +1239,13 @@ localsub(struct state const *sp, pg_time_t const *timep,
 			if (!(INT_MIN <= newy && newy <= INT_MAX))
 				return NULL;
 			result->tm_year = newy;
+#endif
 		}
 		return result;
 	}
 	if (sp->timecnt == 0 || t < sp->ats[0])
 	{
-		i = sp->defaulttype;
+		i = 0;
 	}
 	else
 	{
@@ -1324,20 +1261,22 @@ localsub(struct state const *sp, pg_time_t const *timep,
 			else
 				lo = mid + 1;
 		}
-		i = (int) sp->types[lo - 1];
+		i = sp->types[lo - 1];
 	}
 	ttisp = &sp->ttis[i];
 
 	/*
 	 * To get (wrong) behavior that's compatible with System V Release 2.0
 	 * you'd replace the statement below with t += ttisp->tt_utoff;
-	 * timesub(&t, 0L, sp, tmp);
+	 * timesub(&t, 0, sp, tmp);
 	 */
 	result = timesub(&t, ttisp->tt_utoff, sp, tmp);
 	if (result)
 	{
 		result->tm_isdst = ttisp->tt_isdst;
-		result->tm_zone = unconstify(char *, &sp->chars[ttisp->tt_desigidx]);
+#ifdef TM_ZONE
+		result->TM_ZONE = UNCONST(&sp->chars[ttisp->tt_desigidx]);
+#endif
 	}
 	return result;
 }
@@ -1353,12 +1292,12 @@ pg_localtime(const pg_time_t *timep, const pg_tz *tz)
 /*
  * gmtsub is to gmtime as localsub is to localtime.
  *
- * Except we have a private "struct state" for GMT, so no sp is passed in.
+ * PG: except we have a private "struct state" for GMT, so no sp is passed in.
  */
 
 static struct pg_tm *
-gmtsub(pg_time_t const *timep, int_fast32_t offset,
-	   struct pg_tm *tmp)
+gmtsub(pg_time_t const *timep,
+	   int_fast32_t offset, struct pg_tm *tmp)
 {
 	struct pg_tm *result;
 
@@ -1375,16 +1314,15 @@ gmtsub(pg_time_t const *timep, int_fast32_t offset,
 	}
 
 	result = timesub(timep, offset, gmtptr, tmp);
+#ifdef TM_ZONE
 
 	/*
 	 * Could get fancy here and deliver something such as "+xx" or "-xx" if
 	 * offset is non-zero, but this is no time for a treasure hunt.
 	 */
-	if (offset != 0)
-		tmp->tm_zone = wildabbr;
-	else
-		tmp->tm_zone = gmtptr->chars;
-
+	tmp->TM_ZONE = UNCONST(offset ? wildabbr
+						   : gmtptr->chars);
+#endif							/* defined TM_ZONE */
 	return result;
 }
 
@@ -1399,14 +1337,14 @@ pg_gmtime(const pg_time_t *timep)
  * where, to make the math easy, the answer for year zero is defined as zero.
  */
 
-static int
-leaps_thru_end_of_nonneg(int y)
+static pg_time_t
+leaps_thru_end_of_nonneg(pg_time_t y)
 {
 	return y / 4 - y / 100 + y / 400;
 }
 
-static int
-leaps_thru_end_of(const int y)
+static pg_time_t
+leaps_thru_end_of(pg_time_t y)
 {
 	return (y < 0
 			? -1 - leaps_thru_end_of_nonneg(-1 - y)
@@ -1417,123 +1355,151 @@ static struct pg_tm *
 timesub(const pg_time_t *timep, int_fast32_t offset,
 		const struct state *sp, struct pg_tm *tmp)
 {
-	const struct lsinfo *lp;
 	pg_time_t	tdays;
-	int			idays;			/* unsigned would be so 2003 */
-	int_fast64_t rem;
-	int			y;
 	const int  *ip;
-	int_fast64_t corr;
-	bool		hit;
+	int_fast32_2s corr;
 	int			i;
+	int_fast32_t idays,
+				rem,
+				dayoff,
+				dayrem;
+	pg_time_t	y;
+
+	/*
+	 * If less than SECSPERMIN, the number of seconds since the most recent
+	 * positive leap second; otherwise, do not add 1 to localtime tm_sec
+	 * because of leap seconds.
+	 */
+	pg_time_t	secs_since_posleap = SECSPERMIN;
 
 	corr = 0;
-	hit = false;
-	i = (sp == NULL) ? 0 : sp->leapcnt;
+	i = sp ? leapcount(sp) : 0;
 	while (--i >= 0)
 	{
-		lp = &sp->lsis[i];
-		if (*timep >= lp->ls_trans)
+		struct lsinfo ls = lsinfo(sp, i);
+
+		if (ls.ls_trans <= *timep)
 		{
-			corr = lp->ls_corr;
-			hit = (*timep == lp->ls_trans
-				   && (i == 0 ? 0 : lp[-1].ls_corr) < corr);
+			corr = ls.ls_corr;
+			if ((i == 0 ? 0 : lsinfo(sp, i - 1).ls_corr) < corr)
+				secs_since_posleap = *timep - ls.ls_trans;
 			break;
 		}
 	}
-	y = EPOCH_YEAR;
+
+	/*
+	 * Calculate the year, avoiding integer overflow even if pg_time_t is
+	 * unsigned.
+	 */
 	tdays = *timep / SECSPERDAY;
 	rem = *timep % SECSPERDAY;
-	while (tdays < 0 || tdays >= year_lengths[isleap(y)])
+	rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY;
+	dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3;
+	rem %= SECSPERDAY;
+
+	/*
+	 * y = (EPOCH_YEAR + 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 pg_time_t is unsigned.
+	 */
+	dayrem = tdays % DAYSPERREPEAT;
+	dayrem += dayoff % DAYSPERREPEAT;
+	y = (EPOCH_YEAR - YEARSPERREPEAT
+		 + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT
+			 - ((dayrem % DAYSPERREPEAT) < 0)
+			 + tdays / DAYSPERREPEAT)
+			* YEARSPERREPEAT));
+	/* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow.  */
+	idays = tdays % DAYSPERREPEAT;
+	idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT;
+	idays %= DAYSPERREPEAT;
+	/* Increase Y and decrease IDAYS until IDAYS is in range for Y.  */
+	while (year_lengths[isleap(y)] <= idays)
 	{
-		int			newy;
-		pg_time_t	tdelta;
-		int			idelta;
+		int			tdelta = idays / DAYSPERLYEAR;
+		int_fast32_t ydelta = tdelta + !tdelta;
+		pg_time_t	newy = y + ydelta;
 		int			leapdays;
 
-		tdelta = tdays / DAYSPERLYEAR;
-		if (!((!TYPE_SIGNED(pg_time_t) || INT_MIN <= tdelta)
-			  && tdelta <= INT_MAX))
-			goto out_of_range;
-		idelta = tdelta;
-		if (idelta == 0)
-			idelta = (tdays < 0) ? -1 : 1;
-		newy = y;
-		if (increment_overflow(&newy, idelta))
-			goto out_of_range;
 		leapdays = leaps_thru_end_of(newy - 1) -
 			leaps_thru_end_of(y - 1);
-		tdays -= ((pg_time_t) newy - y) * DAYSPERNYEAR;
-		tdays -= leapdays;
+		idays -= ydelta * DAYSPERNYEAR;
+		idays -= leapdays;
 		y = newy;
 	}
 
-	/*
-	 * Given the range, we can now fearlessly cast...
-	 */
-	idays = tdays;
-	rem += offset - corr;
-	while (rem < 0)
-	{
-		rem += SECSPERDAY;
-		--idays;
-	}
-	while (rem >= SECSPERDAY)
+#ifdef ckd_add
+	if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE))
 	{
-		rem -= SECSPERDAY;
-		++idays;
+		errno = EOVERFLOW;
+		return NULL;
 	}
-	while (idays < 0)
+#else
+	if (!TYPE_SIGNED(pg_time_t) && y < TM_YEAR_BASE)
 	{
-		if (increment_overflow(&y, -1))
-			goto out_of_range;
-		idays += year_lengths[isleap(y)];
+		int			signed_y = y;
+
+		tmp->tm_year = signed_y - TM_YEAR_BASE;
 	}
-	while (idays >= year_lengths[isleap(y)])
+	else if ((!TYPE_SIGNED(pg_time_t) || INT_MIN + TM_YEAR_BASE <= y)
+			 && y - TM_YEAR_BASE <= INT_MAX)
+		tmp->tm_year = y - TM_YEAR_BASE;
+	else
 	{
-		idays -= year_lengths[isleap(y)];
-		if (increment_overflow(&y, 1))
-			goto out_of_range;
+		errno = EOVERFLOW;
+		return NULL;
 	}
-	tmp->tm_year = y;
-	if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
-		goto out_of_range;
+#endif
 	tmp->tm_yday = idays;
 
 	/*
 	 * The "extra" mods below avoid overflow problems.
 	 */
-	tmp->tm_wday = EPOCH_WDAY +
-		((y - EPOCH_YEAR) % DAYSPERWEEK) *
-		(DAYSPERNYEAR % DAYSPERWEEK) +
-		leaps_thru_end_of(y - 1) -
-		leaps_thru_end_of(EPOCH_YEAR - 1) +
-		idays;
+	tmp->tm_wday = (TM_WDAY_BASE
+					+ ((tmp->tm_year % DAYSPERWEEK)
+					   * (DAYSPERNYEAR % DAYSPERWEEK))
+					+ leaps_thru_end_of(y - 1)
+					- leaps_thru_end_of(TM_YEAR_BASE - 1)
+					+ idays);
 	tmp->tm_wday %= DAYSPERWEEK;
 	if (tmp->tm_wday < 0)
 		tmp->tm_wday += DAYSPERWEEK;
-	tmp->tm_hour = (int) (rem / SECSPERHOUR);
+	tmp->tm_hour = rem / SECSPERHOUR;
 	rem %= SECSPERHOUR;
-	tmp->tm_min = (int) (rem / SECSPERMIN);
+	tmp->tm_min = rem / SECSPERMIN;
+	tmp->tm_sec = rem % SECSPERMIN;
 
 	/*
-	 * A positive leap second requires a special representation. This uses
-	 * "... ??:59:60" et seq.
+	 * Use "... ??:??:60" at the end of the localtime minute containing the
+	 * second just before the positive leap second.
 	 */
-	tmp->tm_sec = (int) (rem % SECSPERMIN) + hit;
+	tmp->tm_sec += secs_since_posleap <= tmp->tm_sec;
+
 	ip = mon_lengths[isleap(y)];
 	for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
 		idays -= ip[tmp->tm_mon];
-	tmp->tm_mday = (int) (idays + 1);
+	tmp->tm_mday = idays + 1;
 	tmp->tm_isdst = 0;
-	tmp->tm_gmtoff = offset;
+#ifdef TM_GMTOFF
+	tmp->TM_GMTOFF = offset;
+#endif							/* defined TM_GMTOFF */
 	return tmp;
-
-out_of_range:
-	errno = EOVERFLOW;
-	return NULL;
 }
 
+/*
+ * Adapted from code provided by Robert Elz, who writes:
+ *	The "best" way to do mktime I think is based on an idea of Bob
+ *	Kridle's (so its said...) from a long time ago.
+ *	It does a binary search of the pg_time_t space. Since pg_time_t's are
+ *	just 32 bits, its a max of 32 iterations (even at 64 bits it
+ *	would still be very reasonable).
+ */
+
+#ifndef WRONG
+#define WRONG (-1)
+#endif							/* !defined WRONG */
+
 /*
  * Normalize logic courtesy Paul Eggert.
  */
@@ -1541,6 +1507,9 @@ out_of_range:
 static bool
 increment_overflow(int *ip, int j)
 {
+#ifdef ckd_add
+	return ckd_add(ip, *ip, j);
+#else
 	int const	i = *ip;
 
 	/*----------
@@ -1554,11 +1523,15 @@ increment_overflow(int *ip, int j)
 		return true;
 	*ip += j;
 	return false;
+#endif
 }
 
 static bool
-increment_overflow_time(pg_time_t *tp, int_fast32_t j)
+increment_overflow_time(pg_time_t *tp, int_fast32_2s j)
 {
+#ifdef ckd_add
+	return ckd_add(tp, *tp, j);
+#else
 	/*----------
 	 * This is like
 	 * 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...',
@@ -1571,24 +1544,69 @@ increment_overflow_time(pg_time_t *tp, int_fast32_t j)
 		return true;
 	*tp += j;
 	return false;
+#endif
 }
 
-static int_fast64_t
+static int_fast32_2s
 leapcorr(struct state const *sp, pg_time_t t)
 {
-	struct lsinfo const *lp;
 	int			i;
 
-	i = sp->leapcnt;
+	i = leapcount(sp);
 	while (--i >= 0)
 	{
-		lp = &sp->lsis[i];
-		if (t >= lp->ls_trans)
-			return lp->ls_corr;
+		struct lsinfo ls = lsinfo(sp, i);
+
+		if (ls.ls_trans <= t)
+			return ls.ls_corr;
 	}
 	return 0;
 }
 
+/*
+ * Postgres-specific functions begin here.
+ */
+
+/*
+ * Load the definition of the given time zone name into *sp.
+ * Return true if successful, false if not.
+ * If "canonname" is not NULL, then on success the canonical spelling of
+ * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
+ *
+ * "GMT" is always interpreted as the gmtload() definition, without attempting
+ * to load a definition from the filesystem.  This has a number of benefits:
+ * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
+ * the bootstrap default timezone setting doesn't work (as could happen if
+ * the OS attempts to supply a leap-second-aware version of "GMT").
+ * 2. Because we aren't accessing the filesystem, we can safely initialize
+ * the "GMT" zone definition before my_exec_path is known.
+ * 3. It's quick enough that we don't waste much time when the bootstrap
+ * default timezone setting is later overridden from postgresql.conf.
+ */
+bool
+pg_tzload(const char *name, char *canonname, struct state *sp)
+{
+	if (strcmp(name, "GMT") == 0)
+	{
+		gmtload(sp);
+		/* Use given name as canonical */
+		if (canonname)
+			strcpy(canonname, name);
+	}
+	else if (tzload(name, canonname, sp, TZLOAD_TZSTRING) != 0)
+	{
+		if (name[0] == ':' || !tzparse(name, sp, NULL))
+		{
+			/* Unknown timezone. Fail our call instead of loading GMT! */
+			return false;
+		}
+		/* For POSIX timezone specs, use given name as canonical */
+		if (canonname)
+			strcpy(canonname, name);
+	}
+	return true;
+}
+
 /*
  * Find the next DST transition time in the given zone after the given time
  *
@@ -1627,8 +1645,8 @@ pg_next_dst_boundary(const pg_time_t *timep,
 	sp = &tz->state;
 	if (sp->timecnt == 0)
 	{
-		/* non-DST zone, use the defaulttype */
-		ttisp = &sp->ttis[sp->defaulttype];
+		/* non-DST zone, use the defaulttype (now always 0) */
+		ttisp = &sp->ttis[0];
 		*before_gmtoff = ttisp->tt_utoff;
 		*before_isdst = ttisp->tt_isdst;
 		return 0;
@@ -1688,8 +1706,8 @@ pg_next_dst_boundary(const pg_time_t *timep,
 	}
 	if (t < sp->ats[0])
 	{
-		/* For "before", use the defaulttype */
-		ttisp = &sp->ttis[sp->defaulttype];
+		/* For "before", use the defaulttype (now always 0) */
+		ttisp = &sp->ttis[0];
 		*before_gmtoff = ttisp->tt_utoff;
 		*before_isdst = ttisp->tt_isdst;
 		*boundary = sp->ats[0];
@@ -1821,7 +1839,7 @@ pg_interpret_timezone_abbrev(const char *abbrev,
 	 * Not found yet; check the defaulttype, which is notionally the era
 	 * before any of the entries in sp->types[].
 	 */
-	ttisp = &sp->ttis[sp->defaulttype];
+	ttisp = &sp->ttis[0];
 	if (ttisp->tt_desigidx == abbrind)
 	{
 		*gmtoff = ttisp->tt_utoff;
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index eac988c21e7..9561d94a67d 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -219,16 +219,6 @@ init_timezone_hashtable(void)
 /*
  * Load a timezone from file or from cache.
  * Does not verify that the timezone is acceptable!
- *
- * "GMT" is always interpreted as the tzparse() definition, without attempting
- * to load a definition from the filesystem.  This has a number of benefits:
- * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
- * the bootstrap default timezone setting doesn't work (as could happen if
- * the OS attempts to supply a leap-second-aware version of "GMT").
- * 2. Because we aren't accessing the filesystem, we can safely initialize
- * the "GMT" zone definition before my_exec_path is known.
- * 3. It's quick enough that we don't waste much time when the bootstrap
- * default timezone setting is later overridden from postgresql.conf.
  */
 pg_tz *
 pg_tzset(const char *tzname)
@@ -249,8 +239,8 @@ pg_tzset(const char *tzname)
 	/*
 	 * Upcase the given name to perform a case-insensitive hashtable search.
 	 * (We could alternatively downcase it, but we prefer upcase so that we
-	 * can get consistently upcased results from tzparse() in case the name is
-	 * a POSIX-style timezone spec.)
+	 * can get consistently upcased results from pg_tzload() in case the name
+	 * is a POSIX-style timezone spec.)
 	 */
 	p = uppername;
 	while (*tzname)
@@ -268,27 +258,17 @@ pg_tzset(const char *tzname)
 	}
 
 	/*
-	 * "GMT" is always sent to tzparse(), as per discussion above.
+	 * Let the IANA tzdb code interpret the time zone name.
 	 */
-	if (strcmp(uppername, "GMT") == 0)
+	if (!pg_tzload(uppername, canonname, &tzstate))
 	{
-		if (!tzparse(uppername, &tzstate, true))
+		if (strcmp(uppername, "GMT") == 0)
 		{
 			/* This really, really should not happen ... */
 			elog(ERROR, "could not initialize GMT time zone");
 		}
-		/* Use uppercase name as canonical */
-		strcpy(canonname, uppername);
-	}
-	else if (tzload(uppername, canonname, &tzstate, true) != 0)
-	{
-		if (uppername[0] == ':' || !tzparse(uppername, &tzstate, false))
-		{
-			/* Unknown timezone. Fail our call instead of loading GMT! */
-			return NULL;
-		}
-		/* For POSIX timezone specs, use uppercase name as canonical */
-		strcpy(canonname, uppername);
+		/* Unknown timezone. Fail our call instead of loading GMT! */
+		return NULL;
 	}
 
 	/* Save timezone in the cache */
@@ -467,12 +447,12 @@ pg_tzenumerate_next(pg_tzenum *dir)
 		}
 
 		/*
-		 * Load this timezone using tzload() not pg_tzset(), so we don't fill
-		 * the cache.  Also, don't ask for the canonical spelling: we already
-		 * know it, and pg_open_tzfile's way of finding it out is pretty
-		 * inefficient.
+		 * Load this timezone using pg_tzload() not pg_tzset(), so we don't
+		 * fill the cache.  Also, don't ask for the canonical spelling: we
+		 * already know it, and pg_open_tzfile's way of finding it out is
+		 * pretty inefficient.
 		 */
-		if (tzload(fullname + dir->baselen, NULL, &dir->tz.state, true) != 0)
+		if (!pg_tzload(fullname + dir->baselen, NULL, &dir->tz.state))
 		{
 			/* Zone could not be loaded, ignore it */
 			continue;
diff --git a/src/timezone/pgtz.h b/src/timezone/pgtz.h
index 852b677d67b..d4ae94de74a 100644
--- a/src/timezone/pgtz.h
+++ b/src/timezone/pgtz.h
@@ -19,28 +19,72 @@
 #include "pgtime.h"
 #include "tzfile.h"
 
+/*
+ * Support leap seconds in TZ files.
+ * Maybe we should disable this?
+ */
+#define TZ_RUNTIME_LEAPS 1
+
+/*
+ * 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".
+ */
+#define TZNAME_MAXIMUM 254
 
-#define SMALLEST(a, b)	(((a) < (b)) ? (a) : (b))
-#define BIGGEST(a, b)	(((a) > (b)) ? (a) : (b))
+typedef unsigned char desigidx_type;
+
+/*
+ * A type that can represent any 32-bit two's complement integer,
+ * i.e., any integer in the range -2**31 .. 2**31 - 1.
+ */
+typedef int_fast32_t int_fast32_2s;
 
 struct ttinfo
 {								/* time type information */
-	int_fast32_t tt_utoff;		/* UT offset in seconds */
+	int_least32_t tt_utoff;		/* UT offset in seconds; in the range -2**31 +
+								 * 1 .. 2**31 - 1  */
+	desigidx_type tt_desigidx;	/* abbreviation list index */
 	bool		tt_isdst;		/* used to set tm_isdst */
-	int			tt_desigidx;	/* abbreviation list index */
 	bool		tt_ttisstd;		/* transition is std time */
 	bool		tt_ttisut;		/* transition is UT */
 };
 
 struct lsinfo
 {								/* leap second information */
-	pg_time_t	ls_trans;		/* transition time */
-	int_fast64_t ls_corr;		/* correction to apply */
+	pg_time_t	ls_trans;		/* transition time (positive) */
+	int_fast32_2s ls_corr;		/* correction to apply */
 };
 
+/* This abbreviation means local time is unspecified.  */
+static char const UNSPEC[] = "-00";
+
+/*
+ * How many extra bytes are needed at the end of struct state's chars array.
+ * This needs to be at least 1 for null termination in case the input
+ * data isn't properly terminated, and it also needs to be big enough
+ * for ttunspecified to work without crashing.
+ */
+enum
+{
+CHARS_EXTRA = Max(sizeof UNSPEC, 2) - 1};
+
+/*
+ * 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
+ * are put on the stack and stacks are relatively small on some platforms.
+ * See tzfile.h for more about the sizes.
+ */
 struct state
 {
+#if TZ_RUNTIME_LEAPS
 	int			leapcnt;
+#endif
 	int			timecnt;
 	int			typecnt;
 	int			charcnt;
@@ -49,16 +93,11 @@ struct state
 	pg_time_t	ats[TZ_MAX_TIMES];
 	unsigned char types[TZ_MAX_TIMES];
 	struct ttinfo ttis[TZ_MAX_TYPES];
-	char		chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, 4 /* sizeof gmt */ ),
-							  (2 * (TZ_STRLEN_MAX + 1)))];
+	char		chars[Max(Max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"),
+						  2 * (TZNAME_MAXIMUM + 1))];
+#if TZ_RUNTIME_LEAPS
 	struct lsinfo lsis[TZ_MAX_LEAPS];
-
-	/*
-	 * The time type to use for early times or if no transitions. It is always
-	 * zero for recent tzdb releases. It might be nonzero for data from tzdb
-	 * 2018e or earlier.
-	 */
-	int			defaulttype;
+#endif
 };
 
 
@@ -74,8 +113,6 @@ struct pg_tz
 extern int	pg_open_tzfile(const char *name, char *canonname);
 
 /* in localtime.c */
-extern int	tzload(const char *name, char *canonname, struct state *sp,
-				   bool doextend);
-extern bool tzparse(const char *name, struct state *sp, bool lastditch);
+extern bool pg_tzload(const char *name, char *canonname, struct state *sp);
 
 #endif							/* _PGTZ_H */
diff --git a/src/timezone/private.h b/src/timezone/private.h
index ab89028f3e1..0779f513ef5 100644
--- a/src/timezone/private.h
+++ b/src/timezone/private.h
@@ -20,20 +20,72 @@
  * Thank you!
  */
 
-#include <limits.h>				/* for CHAR_BIT et al. */
-#include <sys/wait.h>			/* for WIFEXITED and WEXITSTATUS */
-#include <unistd.h>				/* for F_OK and R_OK */
+/*
+ * IANA now expects this symbol to be defined via a compiler switch,
+ * but in PG we don't want to do it that way.
+ */
+#define TZDEFAULT	"/etc/localtime"
 
-#include "pgtime.h"
+/*
+ * IANA messes with some feature-test macros here, but in PG we want pretty
+ * much all of that to be done by PG's configure script and c.h header.
+ */
+
+/*
+ * For pre-C23 compilers, a substitute for static_assert.
+ * Some of these compilers may warn if it is used outside the top level.
+ */
+#if __STDC_VERSION__ < 202311 && !defined static_assert
+#define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1]
+#endif
 
 /* This string was in the Factory zone through version 2016f.  */
+#ifndef GRANDPARENTED
 #define GRANDPARENTED	"Local time zone must be set--see zic manual page"
+#endif
 
 /*
  * IANA has a bunch of HAVE_FOO #defines here, but in PG we want pretty
- * much all of that to be done by PG's configure script.
+ * much all of that to be done by PG's configure script and c.h header.
+ */
+
+#define ATTRIBUTE_FALLTHROUGH pg_fallthrough
+#if defined(__GNUC__)
+#define ATTRIBUTE_FORMAT(spec) __attribute__((format spec))
+#else
+#define ATTRIBUTE_FORMAT(spec)	/* empty */
+#endif
+#define ATTRIBUTE_MAYBE_UNUSED pg_attribute_unused()
+#define ATTRIBUTE_NORETURN pg_noreturn
+#define ATTRIBUTE_PURE_114833
+#define ATTRIBUTE_PURE_114833_HACK
+
+/*
+ * Nested includes
+ * In PG, much of this was already done by c.h.
  */
 
+#include "pgtime.h"
+
+#include <limits.h>				/* for CHAR_BIT et al. */
+#include <unistd.h>				/* for F_OK and R_OK */
+
+#ifndef EINVAL
+#define EINVAL ERANGE
+#endif
+
+#ifndef ELOOP
+#define ELOOP EINVAL
+#endif
+#ifndef ENAMETOOLONG
+#define ENAMETOOLONG EINVAL
+#endif
+#ifndef ENOMEM
+#define ENOMEM EINVAL
+#endif
+#ifndef ENOTCAPABLE
+#define ENOTCAPABLE EINVAL
+#endif
 #ifndef ENOTSUP
 #define ENOTSUP EINVAL
 #endif
@@ -41,17 +93,69 @@
 #define EOVERFLOW EINVAL
 #endif
 
-/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
-#define is_digit(c) ((unsigned)(c) - '0' <= 9)
+/*
+ * The maximum size of any created object, as a signed integer.
+ * Although the C standard does not outright prohibit larger objects,
+ * behavior is undefined if the result of pointer subtraction does not
+ * fit into ptrdiff_t, and the code assumes in several places that
+ * pointer subtraction works.  As a practical matter it's OK to not
+ * support objects larger than this.
+ */
+#define INDEX_MAX ((ptrdiff_t) min(PTRDIFF_MAX, SIZE_MAX))
+
+/*
+ * Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like
+ * 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 1
+#endif
+#endif
+#ifdef HAVE_STDCKDINT_H
+#if HAVE_STDCKDINT_H
+#include <stdckdint.h>
+#endif
+#elif defined __EDG__
+/* Do nothing, to work around EDG bug <https://bugs.gnu.org/53256>.  */
+#elif defined __has_builtin
+#if __has_builtin(__builtin_add_overflow)
+#define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
+#endif
+#if __has_builtin(__builtin_sub_overflow)
+#define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
+#endif
+#if __has_builtin(__builtin_mul_overflow)
+#define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
+#endif
+#elif 7 <= __GNUC__
+#define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
+#define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
+#define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
+#endif
+
+
+/*
+ * In PG, we always have these fields in struct pg_tm.
+ */
+#define TM_GMTOFF tm_gmtoff
+#define TM_ZONE tm_zone
 
 
 /*
  * Finally, some convenience items.
  */
 
-#define TYPE_BIT(type)	(sizeof (type) * CHAR_BIT)
+#define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type))
 #define TYPE_SIGNED(type) (((type) -1) < 0)
-#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0)
+#define TWOS_COMPLEMENT(type) (TYPE_SIGNED (type) && (! ~ (type) -1))
+
+/*
+ * Minimum and maximum of two values.  Use lower case to avoid
+ * naming clashes with standard include files.
+ */
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#define min(a, b) ((a) < (b) ? (a) : (b))
 
 /*
  * Max and min values of the integer type T, of which only the bottom
@@ -83,48 +187,94 @@
  */
 #define INITIALIZE(x)	((x) = 0)
 
+#define unreachable() pg_unreachable()
+
+/*
+ * For the benefit of GNU folk...
+ * '_(MSGID)' uses the current locale's message library string for MSGID.
+ * The default is to use gettext if available, and use MSGID otherwise.
+ * For PG's purposes, there is no need to support localization in zic.
+ */
+
 #undef _
 #define _(msgid) (msgid)
+#define N_(msgid) (msgid)
+
+/* Handy constants that are independent of tzfile implementation.  */
+
+/* 2**31 - 1 as a signed integer, and usable in #if.  */
+#define TWO_31_MINUS_1 2147483647
+
+enum
+{
+	SECSPERMIN = 60,
+	MINSPERHOUR = 60,
+	SECSPERHOUR = SECSPERMIN * MINSPERHOUR,
+	HOURSPERDAY = 24,
+	DAYSPERWEEK = 7,
+	DAYSPERNYEAR = 365,
+	DAYSPERLYEAR = DAYSPERNYEAR + 1,
+	MONSPERYEAR = 12,
+	YEARSPERREPEAT = 400		/* years before a Gregorian repeat */
+};
 
-/* Handy macros that are independent of tzfile implementation.  */
+#define SECSPERDAY	((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
 
-#define YEARSPERREPEAT		400 /* years before a Gregorian repeat */
+#define DAYSPERREPEAT		((int_fast32_t) 400 * 365 + 100 - 4 + 1)
+#define SECSPERREPEAT		((int_fast64_t) DAYSPERREPEAT * SECSPERDAY)
+#define AVGSECSPERYEAR		(SECSPERREPEAT / YEARSPERREPEAT)
 
-#define SECSPERMIN	60
-#define MINSPERHOUR	60
-#define HOURSPERDAY	24
-#define DAYSPERWEEK	7
-#define DAYSPERNYEAR	365
-#define DAYSPERLYEAR	366
-#define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
-#define SECSPERDAY	((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
-#define MONSPERYEAR	12
-
-#define TM_SUNDAY	0
-#define TM_MONDAY	1
-#define TM_TUESDAY	2
-#define TM_WEDNESDAY	3
-#define TM_THURSDAY	4
-#define TM_FRIDAY	5
-#define TM_SATURDAY	6
-
-#define TM_JANUARY	0
-#define TM_FEBRUARY	1
-#define TM_MARCH	2
-#define TM_APRIL	3
-#define TM_MAY		4
-#define TM_JUNE		5
-#define TM_JULY		6
-#define TM_AUGUST	7
-#define TM_SEPTEMBER	8
-#define TM_OCTOBER	9
-#define TM_NOVEMBER	10
-#define TM_DECEMBER	11
-
-#define TM_YEAR_BASE	1900
-
-#define EPOCH_YEAR	1970
-#define EPOCH_WDAY	TM_THURSDAY
+/*
+ * How many years to generate (in zic.c) or search through (in localtime.c).
+ * This is two years larger than the obvious 400, to avoid edge cases.
+ * E.g., suppose a rule applies from 2012 on with transitions
+ * in March and September, plus one-off transitions in November 2013,
+ * and suppose the rule cannot be expressed as a proleptic TZ string.
+ * If zic looked only at the last 400 years, it would set max_year=2413,
+ * with the intent that the 400 years 2014 through 2413 will be repeated.
+ * The last transition listed in the tzfile would be in 2413-09,
+ * less than 400 years after the last one-off transition in 2013-11.
+ * Two years is not overkill for localtime.c, as a one-year bump
+ * would mishandle 2023d's America/Ciudad_Juarez for November 2422.
+ */
+enum
+{
+years_of_observations = YEARSPERREPEAT + 2};
+
+enum
+{
+	TM_SUNDAY,
+	TM_MONDAY,
+	TM_TUESDAY,
+	TM_WEDNESDAY,
+	TM_THURSDAY,
+	TM_FRIDAY,
+	TM_SATURDAY
+};
+
+enum
+{
+	TM_JANUARY,
+	TM_FEBRUARY,
+	TM_MARCH,
+	TM_APRIL,
+	TM_MAY,
+	TM_JUNE,
+	TM_JULY,
+	TM_AUGUST,
+	TM_SEPTEMBER,
+	TM_OCTOBER,
+	TM_NOVEMBER,
+	TM_DECEMBER
+};
+
+enum
+{
+	TM_YEAR_BASE = 1900,
+	TM_WDAY_BASE = TM_MONDAY,
+	EPOCH_YEAR = 1970,
+	EPOCH_WDAY = TM_THURSDAY
+};
 
 #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
 
@@ -142,14 +292,4 @@
 
 #define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
 
-
-/*
- * The Gregorian year averages 365.2425 days, which is 31556952 seconds.
- */
-
-#define AVGSECSPERYEAR		31556952L
-#define SECSPERREPEAT \
-  ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR)
-#define SECSPERREPEAT_BITS	34	/* ceil(log2(SECSPERREPEAT)) */
-
 #endif							/* !defined PRIVATE_H */
diff --git a/src/timezone/strftime.c b/src/timezone/strftime.c
index 3da0b4d7658..9c959f0119a 100644
--- a/src/timezone/strftime.c
+++ b/src/timezone/strftime.c
@@ -59,8 +59,6 @@ struct lc_time_T
 	const char *date_fmt;
 };
 
-#define Locale	(&C_time_locale)
-
 static const struct lc_time_T C_time_locale = {
 	{
 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
@@ -80,19 +78,15 @@ static const struct lc_time_T C_time_locale = {
 	"%H:%M:%S",
 
 	/*
-	 * x_fmt
-	 *
-	 * C99 and later require this format. Using just numbers (as here) makes
-	 * Quakers happier; it's also compatible with SVR4.
+	 * x_fmt C99 and later require this format. Using just numbers (as here)
+	 * makes Quakers happier; it's also compatible with SVR4.
 	 */
 	"%m/%d/%y",
 
 	/*
-	 * c_fmt
-	 *
-	 * C99 and later require this format. Previously this code used "%D %X",
-	 * but we now conform to C99. Note that "%a %b %d %H:%M:%S %Y" is used by
-	 * Solaris 2.3.
+	 * c_fmt C99 and later require this format. Previously this code used "%D
+	 * %X", but we now conform to C99. Note that "%a %b %d %H:%M:%S %Y" is
+	 * used by Solaris 2.3.
 	 */
 	"%a %b %e %T %Y",
 
@@ -162,6 +156,8 @@ static char *
 _fmt(const char *format, const struct pg_tm *t, char *pt,
 	 const char *ptlim, enum warn *warnp)
 {
+	struct lc_time_T const *Locale = &C_time_locale;
+
 	for (; *format; ++format)
 	{
 		if (*format == '%')
@@ -169,7 +165,14 @@ _fmt(const char *format, const struct pg_tm *t, char *pt,
 	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':
@@ -478,9 +481,7 @@ _fmt(const char *format, const struct pg_tm *t, char *pt,
 						char const *sign;
 						bool		negative;
 
-						if (t->tm_isdst < 0)
-							continue;
-						diff = t->tm_gmtoff;
+						diff = t->TM_GMTOFF;
 						negative = diff < 0;
 						if (diff == 0)
 						{
@@ -506,13 +507,6 @@ _fmt(const char *format, const struct pg_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;
 			}
 		}
@@ -555,7 +549,8 @@ _yconv(int a, int b, bool convert_top, bool convert_yy,
 	int			lead;
 	int			trail;
 
-#define DIVISOR	100
+	int			DIVISOR = 100;
+
 	trail = a % DIVISOR + b % DIVISOR;
 	lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
 	trail %= DIVISOR;
diff --git a/src/timezone/tzfile.h b/src/timezone/tzfile.h
index 8f3eb6bd3f2..994acbe50e8 100644
--- a/src/timezone/tzfile.h
+++ b/src/timezone/tzfile.h
@@ -22,24 +22,19 @@
 
 /*
  * Information about time zone files.
+ * See Internet RFC 9636 for more details about the following format.
  */
 
-#define TZDEFAULT	"/etc/localtime"
-#define TZDEFRULES	"posixrules"
-
-
-/* See Internet RFC 8536 for more details about the following format.  */
-
 /*
  * Each file begins with. . .
  */
 
-#define	TZ_MAGIC	"TZif"
+#define TZ_MAGIC "TZif"
 
 struct tzhead
 {
 	char		tzh_magic[4];	/* TZ_MAGIC */
-	char		tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */
+	char		tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */
 	char		tzh_reserved[15];	/* reserved; must be zero */
 	char		tzh_ttisutcnt[4];	/* coded number of trans. time flags */
 	char		tzh_ttisstdcnt[4];	/* coded number of trans. time flags */
@@ -79,14 +74,16 @@ struct tzhead
  * If tzh_version is '2' or greater, the above is followed by a second instance
  * of tzhead and a second instance of the data in which each coded transition
  * time uses 8 rather than 4 chars,
- * then a POSIX-TZ-environment-variable-style string for use in handling
+ * then a POSIX.1-2017 proleptic TZ string for use in handling
  * instants after the last transition time stored in the file
- * (with nothing between the newlines if there is no POSIX representation for
- * such instants).
+ * (with nothing between the newlines if there is no POSIX.1-2017
+ * representation for such instants).
  *
- * If tz_version is '3' or greater, the above is extended as follows.
- * First, the POSIX TZ string's hour offset may range from -167
- * through 167 as compared to the POSIX-required 0 through 24.
+ * If tz_version is '3' or greater, the TZ string can be any POSIX.1-2024
+ * proleptic TZ string, which means the above is extended as follows.
+ * First, the TZ string's hour offset may range from -167
+ * through 167 as compared to the range 0 through 24 required
+ * by POSIX.1-2017 and earlier.
  * Second, its DST start time may be January 1 at 00:00 and its stop
  * time December 31 at 24:00 plus the difference between DST and
  * standard time, indicating DST all year.
@@ -97,14 +94,32 @@ struct tzhead
  * exceed any of the limits below.
  */
 
-#define TZ_MAX_TIMES	2000
+#ifndef TZ_MAX_TIMES
+/*
+ * The following limit applies to localtime.c; zic has no such limit.
+ * The limit must be at least 310 for Asia/Hebron with 'zic -b fat'.
+ */
+#define TZ_MAX_TIMES 2000
+#endif							/* !defined TZ_MAX_TIMES */
 
-/* This must be at least 17 for Europe/Samara and Europe/Vilnius.  */
-#define TZ_MAX_TYPES	256		/* Limited by what (unsigned char)'s can hold */
+#ifndef TZ_MAX_TYPES
+/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'.  */
+#define TZ_MAX_TYPES 256		/* Limited to 256 by Internet RFC 9636.  */
+#endif							/* !defined TZ_MAX_TYPES */
 
-#define TZ_MAX_CHARS	50		/* Maximum number of abbreviation characters */
- /* (limited by what unsigned chars can hold) */
+#ifndef TZ_MAX_CHARS
+/* This must be at least 40 for America/Anchorage.  */
+#define TZ_MAX_CHARS 256		/* Maximum number of abbreviation characters */
+ /* (limited to 256 by Internet RFC 9636) */
+#endif							/* !defined TZ_MAX_CHARS */
 
-#define TZ_MAX_LEAPS	50		/* Maximum number of leap second corrections */
+#ifndef TZ_MAX_LEAPS
+/*
+ * The following limit applies to localtime.c; zic has no such limit.
+ * The limit must be at least 27 for leap seconds from 1972 through mid-2023.
+ * There's a plan to discontinue leap seconds by 2035.
+ */
+#define TZ_MAX_LEAPS 50			/* Maximum number of leap second corrections */
+#endif							/* !defined TZ_MAX_LEAPS */
 
 #endif							/* !defined TZFILE_H */
diff --git a/src/timezone/zic.c b/src/timezone/zic.c
index 739974bf481..2938537c1d9 100644
--- a/src/timezone/zic.c
+++ b/src/timezone/zic.c
@@ -11,6 +11,9 @@
 #include "postgres_fe.h"
 
 #include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
 #include <sys/stat.h>
 #include <time.h>
 
@@ -19,39 +22,81 @@
 #include "private.h"
 #include "tzfile.h"
 
-#define	ZIC_VERSION_PRE_2013 '2'
-#define	ZIC_VERSION	'3'
+#ifndef O_BINARY
+#define O_BINARY 0				/* MS-Windows */
+#endif
 
 typedef int_fast64_t zic_t;
-#define ZIC_MIN INT_FAST64_MIN
-#define ZIC_MAX INT_FAST64_MAX
-#define PRIdZIC PRIdFAST64
+static zic_t const
+			ZIC_MIN = INT_FAST64_MIN,
+			ZIC_MAX = INT_FAST64_MAX,
+			ZIC32_MIN = -1 - (zic_t) TWO_31_MINUS_1,
+			ZIC32_MAX = TWO_31_MINUS_1;
 #define SCNdZIC SCNdFAST64
 
 #ifndef ZIC_MAX_ABBR_LEN_WO_WARN
-#define ZIC_MAX_ABBR_LEN_WO_WARN	6
+#define ZIC_MAX_ABBR_LEN_WO_WARN 6
 #endif							/* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
 
-#ifndef WIN32
-#ifdef S_IRUSR
-#define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
-#else
-#define MKDIR_UMASK 0755
+/* Minimum and maximum years, assuming signed 32-bit pg_time_t.  */
+enum
+{
+YEAR_32BIT_MIN = 1901, YEAR_32BIT_MAX = 2038};
+
+/* An upper bound on how much a format might grow due to concatenation.  */
+enum
+{
+FORMAT_LEN_GROWTH_BOUND = 5};
+
+/* All file permission bits.  */
+#define ALL_PERMS (S_IRWXU | S_IRWXG | S_IRWXO)
+
+/* Troublesome file permission bits.  */
+#define TROUBLE_PERMS (S_IWGRP | S_IWOTH)
+
+/*
+ * File permission bits for making directories.
+ * The umask modifies these bits.
+ */
+#define MKDIR_PERMS (ALL_PERMS & ~TROUBLE_PERMS)
+
+/*
+ * File permission bits for making regular files.
+ * The umask modifies these bits.
+ */
+#define CREAT_PERMS (MKDIR_PERMS & ~(S_IXUSR | S_IXGRP | S_IXOTH))
+static mode_t creat_perms = CREAT_PERMS;
+
+static gid_t const no_gid = -1;
+static uid_t const no_uid = -1;
+static gid_t output_group = -1;
+static uid_t output_owner = -1;
+#ifndef GID_T_MAX
+#define GID_T_MAX MAXVAL(gid_t, TYPE_BIT(gid_t))
 #endif
+#ifndef UID_T_MAX
+#define UID_T_MAX MAXVAL(uid_t, TYPE_BIT(uid_t))
 #endif
-/* Port to native MS-Windows and to ancient UNIX.  */
-#if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT
-#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+
+/*
+ * The minimum alignment of a type, for pre-C23 platforms.
+ * The __SUNPRO_C test is because Oracle Developer Studio 12.6 lacks
+ * <stdalign.h> even though __STDC_VERSION__ == 201112.
+ */
+#if __STDC_VERSION__ < 201112 || defined __SUNPRO_C
+#define alignof(type) offsetof(struct { char a; type b; }, b)
+#elif __STDC_VERSION__ < 202311
+#include <stdalign.h>
 #endif
 
-/* The maximum ptrdiff_t value, for pre-C99 platforms.  */
-#ifndef PTRDIFF_MAX
-static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
+/* The name used for the file implementing the obsolete -p option.  */
+#ifndef TZDEFRULES
+#define TZDEFRULES "posixrules"
 #endif
 
-/* The minimum alignment of a type, for pre-C11 platforms.  */
-#if __STDC_VERSION__ < 201112
-#define _Alignof(type) offsetof(struct { char a; type b; }, b)
+/* The maximum length of a text line, including the trailing newline.  */
+#ifndef _POSIX2_LINE_MAX
+#define _POSIX2_LINE_MAX 2048
 #endif
 
 /*
@@ -63,13 +108,12 @@ typedef intmax_t lineno_t;
 
 struct rule
 {
-	const char *r_filename;
+	int			r_filenum;
 	lineno_t	r_linenum;
 	const char *r_name;
 
 	zic_t		r_loyear;		/* for example, 1986 */
 	zic_t		r_hiyear;		/* for example, 1986 */
-	bool		r_lowasnum;
 	bool		r_hiwasnum;
 
 	int			r_month;		/* 0..11 */
@@ -90,16 +134,18 @@ struct rule
 };
 
 /*
- *	r_dycode		r_dayofmonth	r_wday
+ * r_dycode	r_dayofmonth	r_wday
  */
-
-#define DC_DOM		0	/* 1..31 */ /* unused */
-#define DC_DOWGEQ	1	/* 1..31 */ /* 0..6 (Sun..Sat) */
-#define DC_DOWLEQ	2	/* 1..31 */ /* 0..6 (Sun..Sat) */
+enum
+{
+	DC_DOM,						/* 1..31 */	/* unused */
+	DC_DOWGEQ,					/* 1..31 */	/* 0..6 (Sun..Sat) */
+	DC_DOWLEQ					/* 1..31 */	/* 0..6 (Sun..Sat) */
+};
 
 struct zone
 {
-	const char *z_filename;
+	int			z_filenum;
 	lineno_t	z_linenum;
 
 	const char *z_name;
@@ -118,72 +164,83 @@ struct zone
 	zic_t		z_untiltime;
 };
 
-extern int	link(const char *target, const char *linkname);
+#ifdef WIN32
+static int	link(const char *oldpath, const char *newpath);
+#endif
+
+#if ! HAVE_SYMLINK
+static ssize_t
+readlink(char const *file, char *buf, size_t size)
+{
+	errno = ENOTSUP;
+	return -1;
+}
+static int
+symlink(char const *target, char const *linkname)
+{
+	errno = ENOTSUP;
+	return -1;
+}
+#endif
+
 #ifndef AT_SYMLINK_FOLLOW
 #define linkat(targetdir, target, linknamedir, linkname, flag) \
-	(itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname))
+   (errno = ENOTSUP, -1)
 #endif
 
-static void verror(const char *const string, va_list args) pg_attribute_printf(1, 0);
-static void error(const char *const string, ...) pg_attribute_printf(1, 2);
-static void warning(const char *const string, ...) pg_attribute_printf(1, 2);
+static int	addabbr(char chs[TZ_MAX_CHARS], int *pnchs, char const *abbr);
 static void addtt(zic_t starttime, int type);
 static int	addtype(zic_t utoff, char const *abbr,
 					bool isdst, bool ttisstd, bool ttisut);
-static void leapadd(zic_t t, int correction, int rolling);
 static void adjleap(void);
 static void associate(void);
-static void dolink(const char *target, const char *linkname,
-				   bool staysymlink);
-static char **getfields(char *cp);
+static void checkabbr(char const *string);
+static void check_for_signal(void);
+static void dolink(char const *target, char const *linkname, bool staysymlink);
+static int	getfields(char *cp, char **array, int arrayelts);
 static zic_t gethms(const char *string, const char *errstring);
 static zic_t getsave(char *field, bool *isdst);
 static void inexpires(char **fields, int nfields);
-static void infile(const char *name);
+static void infile(int fnum, char const *name);
 static void inleap(char **fields, int nfields);
 static void inlink(char **fields, int nfields);
 static void inrule(char **fields, int nfields);
 static bool inzcont(char **fields, int nfields);
 static bool inzone(char **fields, int nfields);
 static bool inzsub(char **fields, int nfields, bool iscont);
-static bool itsdir(char const *name);
-static bool itssymlink(char const *name);
 static bool is_alpha(char a);
+static int	itssymlink(char const *name, int *cache);
+static void leapadd(zic_t t, int correction, int rolling);
 static char lowerit(char a);
 static void mkdirs(char const *argname, bool ancestors);
-static void newabbr(const char *string);
 static zic_t oadd(zic_t t1, zic_t t2);
+static zic_t omul(zic_t t1, zic_t t2);
 static void outzone(const struct zone *zpfirst, ptrdiff_t zonecount);
 static zic_t rpytime(const struct rule *rp, zic_t wantedy);
-static void rulesub(struct rule *rp,
+static bool rulesub(struct rule *rp,
 					const char *loyearp, const char *hiyearp,
 					const char *typep, const char *monthp,
 					const char *dayp, const char *timep);
 static zic_t tadd(zic_t t1, zic_t t2);
 
-/* Bound on length of what %z can expand to.  */
-enum
+/* Is C an ASCII digit?  */
+static bool
+is_digit(char c)
 {
-PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};
+	return '0' <= c && c <= '9';
+}
 
-/*
- * If true, work around a bug in Qt 5.6.1 and earlier, which mishandles
- * TZif files whose POSIX-TZ-style strings contain '<'; see
- * QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>.  This
- * workaround will no longer be needed when Qt 5.6.1 and earlier are
- * obsolete, say in the year 2021.
- */
-#ifndef WORK_AROUND_QTBUG_53071
+/* Bound on length of what %z can expand to.  */
 enum
 {
-WORK_AROUND_QTBUG_53071 = true};
-#endif
+PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};
 
 static int	charcnt;
 static bool errors;
 static bool warnings;
-static const char *filename;
-static int	leapcnt;
+static int	filenum;
+static ptrdiff_t leapcnt;
+static ptrdiff_t leap_alloc;
 static bool leapseen;
 static zic_t leapminyear;
 static zic_t leapmaxyear;
@@ -195,97 +252,135 @@ static zic_t min_year;
 static bool noise;
 static bool print_abbrevs;
 static zic_t print_cutoff;
-static const char *rfilename;
+static bool skip_mkdir;
+static int	rfilenum;
 static lineno_t rlinenum;
 static const char *progname;
+static char const *leapsec;
+static char *const *main_argv;
 static ptrdiff_t timecnt;
 static ptrdiff_t timecnt_alloc;
 static int	typecnt;
+static int	unspecifiedtype;
 
 /*
  * Line codes.
  */
 
-#define LC_RULE		0
-#define LC_ZONE		1
-#define LC_LINK		2
-#define LC_LEAP		3
-#define LC_EXPIRES	4
+enum
+{
+	LC_RULE,
+	LC_ZONE,
+	LC_LINK,
+	LC_LEAP,
+	LC_EXPIRES
+};
 
 /*
  * Which fields are which on a Zone line.
  */
 
-#define ZF_NAME		1
-#define ZF_STDOFF	2
-#define ZF_RULE		3
-#define ZF_FORMAT	4
-#define ZF_TILYEAR	5
-#define ZF_TILMONTH	6
-#define ZF_TILDAY	7
-#define ZF_TILTIME	8
-#define ZONE_MINFIELDS	5
-#define ZONE_MAXFIELDS	9
+enum
+{
+	ZF_NAME = 1,
+	ZF_STDOFF,
+	ZF_RULE,
+	ZF_FORMAT,
+	ZF_TILYEAR,
+	ZF_TILMONTH,
+	ZF_TILDAY,
+	ZF_TILTIME,
+	ZONE_MAXFIELDS,
+	ZONE_MINFIELDS = ZF_TILYEAR
+};
 
 /*
  * Which fields are which on a Zone continuation line.
  */
 
-#define ZFC_STDOFF	0
-#define ZFC_RULE	1
-#define ZFC_FORMAT	2
-#define ZFC_TILYEAR	3
-#define ZFC_TILMONTH	4
-#define ZFC_TILDAY	5
-#define ZFC_TILTIME	6
-#define ZONEC_MINFIELDS	3
-#define ZONEC_MAXFIELDS	7
+enum
+{
+	ZFC_STDOFF,
+	ZFC_RULE,
+	ZFC_FORMAT,
+	ZFC_TILYEAR,
+	ZFC_TILMONTH,
+	ZFC_TILDAY,
+	ZFC_TILTIME,
+	ZONEC_MAXFIELDS,
+	ZONEC_MINFIELDS = ZFC_TILYEAR
+};
 
 /*
  * Which files are which on a Rule line.
  */
 
-#define RF_NAME		1
-#define RF_LOYEAR	2
-#define RF_HIYEAR	3
-#define RF_COMMAND	4
-#define RF_MONTH	5
-#define RF_DAY		6
-#define RF_TOD		7
-#define RF_SAVE		8
-#define RF_ABBRVAR	9
-#define RULE_FIELDS	10
+enum
+{
+	RF_NAME = 1,
+	RF_LOYEAR,
+	RF_HIYEAR,
+	RF_COMMAND,
+	RF_MONTH,
+	RF_DAY,
+	RF_TOD,
+	RF_SAVE,
+	RF_ABBRVAR,
+	RULE_FIELDS
+};
 
 /*
  * Which fields are which on a Link line.
  */
 
-#define LF_TARGET	1
-#define LF_LINKNAME	2
-#define LINK_FIELDS	3
+enum
+{
+	LF_TARGET = 1,
+	LF_LINKNAME,
+	LINK_FIELDS
+};
 
 /*
  * Which fields are which on a Leap line.
  */
 
-#define LP_YEAR		1
-#define LP_MONTH	2
-#define LP_DAY		3
-#define LP_TIME		4
-#define LP_CORR		5
-#define LP_ROLL		6
-#define LEAP_FIELDS	7
+enum
+{
+	LP_YEAR = 1,
+	LP_MONTH,
+	LP_DAY,
+	LP_TIME,
+	LP_CORR,
+	LP_ROLL,
+	LEAP_FIELDS,
 
-/* Expires lines are like Leap lines, except without CORR and ROLL fields.  */
-#define EXPIRES_FIELDS	5
+	/*
+	 * Expires lines are like Leap lines, except without CORR and ROLL fields.
+	 */
+	EXPIRES_FIELDS = LP_TIME + 1
+};
+
+/*
+ * The maximum number of fields on any of the above lines.
+ * (The "+"s pacify gcc -Wenum-compare.)
+ */
+enum
+{
+	MAX_FIELDS = max(max(+RULE_FIELDS, +LINK_FIELDS),
+					 max(+LEAP_FIELDS, +EXPIRES_FIELDS))
+};
 
 /*
  * Year synonyms.
  */
 
-#define YR_MINIMUM	0
-#define YR_MAXIMUM	1
-#define YR_ONLY		2
+enum
+{
+	YR_MINIMUM,					/* "minimum" is for backward compatibility
+								 * only */
+	YR_MAXIMUM,
+	YR_ONLY
+};
 
 static struct rule *rules;
 static ptrdiff_t nrules;		/* number of rules */
@@ -297,7 +392,7 @@ static ptrdiff_t nzones_alloc;
 
 struct link
 {
-	const char *l_filename;
+	int			l_filenum;
 	lineno_t	l_linenum;
 	const char *l_target;
 	const char *l_linkname;
@@ -368,12 +463,10 @@ static struct lookup const lasts[] = {
 
 static struct lookup const begin_years[] = {
 	{"minimum", YR_MINIMUM},
-	{"maximum", YR_MAXIMUM},
 	{NULL, 0}
 };
 
 static struct lookup const end_years[] = {
-	{"minimum", YR_MINIMUM},
 	{"maximum", YR_MAXIMUM},
 	{"only", YR_ONLY},
 	{NULL, 0}
@@ -406,38 +499,72 @@ static unsigned char desigidx[TZ_MAX_TYPES];
 static bool ttisstds[TZ_MAX_TYPES];
 static bool ttisuts[TZ_MAX_TYPES];
 static char chars[TZ_MAX_CHARS];
-static zic_t trans[TZ_MAX_LEAPS];
-static zic_t corr[TZ_MAX_LEAPS];
-static char roll[TZ_MAX_LEAPS];
+static struct
+{
+	zic_t		trans;
+	zic_t		corr;
+	char		roll;
+}		   *leap;
 
 /*
  * Memory allocation.
  */
 
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
 memory_exhausted(const char *msg)
 {
 	fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
 	exit(EXIT_FAILURE);
 }
 
-static size_t
-size_product(size_t nitems, size_t itemsize)
+ATTRIBUTE_NORETURN static void
+size_overflow(void)
+{
+	memory_exhausted(_("size overflow"));
+}
+
+ATTRIBUTE_PURE_114833_HACK
+static ptrdiff_t
+size_sum(size_t a, size_t b)
 {
-	if (SIZE_MAX / itemsize < nitems)
-		memory_exhausted(_("size overflow"));
-	return nitems * itemsize;
+#ifdef ckd_add
+	ptrdiff_t	sum;
+
+	if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
+		return sum;
+#else
+	if (a <= INDEX_MAX && b <= INDEX_MAX - a)
+		return a + b;
+#endif
+	size_overflow();
 }
 
-static size_t
-align_to(size_t size, size_t alignment)
+ATTRIBUTE_PURE_114833_HACK
+static ptrdiff_t
+size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
 {
-	size_t		aligned_size = size + alignment - 1;
+#ifdef ckd_mul
+	ptrdiff_t	product;
 
-	aligned_size -= aligned_size % alignment;
-	if (aligned_size < size)
-		memory_exhausted(_("alignment overflow"));
-	return aligned_size;
+	if (!ckd_mul(&product, nitems, itemsize) && product <= INDEX_MAX)
+		return product;
+#else
+	ptrdiff_t	nitems_max = INDEX_MAX / itemsize;
+
+	if (nitems <= nitems_max)
+		return nitems * itemsize;
+#endif
+	size_overflow();
+}
+
+ATTRIBUTE_PURE_114833_HACK
+static ptrdiff_t
+align_to(ptrdiff_t size, ptrdiff_t alignment)
+{
+	ptrdiff_t	lo_bits = alignment - 1,
+				sum = size_sum(size, lo_bits);
+
+	return sum & ~lo_bits;
 }
 
 static void *
@@ -449,76 +576,114 @@ 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 *
-ecpyalloc(char const *str)
+xstrdup(char const *str)
 {
 	return memcheck(strdup(str));
 }
 
-static void *
-growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc)
+static ptrdiff_t
+grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
 {
-	if (nitems < *nitems_alloc)
-		return ptr;
-	else
-	{
-		ptrdiff_t	nitems_max = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071;
-		ptrdiff_t	amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX;
+	ptrdiff_t	addend = (*nitems_alloc >> 1) + 1;
+#if defined ckd_add && defined ckd_mul
+	ptrdiff_t	product;
 
-		if ((amax - 1) / 3 * 2 < *nitems_alloc)
-			memory_exhausted(_("integer overflow"));
-		*nitems_alloc += (*nitems_alloc >> 1) + 1;
-		return erealloc(ptr, size_product(*nitems_alloc, itemsize));
+	if (!ckd_add(nitems_alloc, *nitems_alloc, addend)
+		&& !ckd_mul(&product, *nitems_alloc, itemsize) && product <= INDEX_MAX)
+		return product;
+#else
+	if (*nitems_alloc <= ((INDEX_MAX - 1) / 3 * 2) / itemsize)
+	{
+		*nitems_alloc += addend;
+		return *nitems_alloc * itemsize;
 	}
+#endif
+	memory_exhausted(_("integer overflow"));
+}
+
+static void *
+growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems,
+		  ptrdiff_t *nitems_alloc)
+{
+	return (nitems < *nitems_alloc
+			? ptr
+			: xrealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
 }
 
 /*
  * Error handling.
  */
 
+/*
+ * In most of the code, an input file name is represented by its index
+ * into the main argument vector, except that LEAPSEC_FILENUM stands
+ * for leapsec and COMMAND_LINE_FILENUM stands for the command line.
+ */
+enum
+{
+LEAPSEC_FILENUM = -2, COMMAND_LINE_FILENUM = -1};
+
+/* Return the name of the Ith input file, for diagnostics.  */
+static char const *
+filename(int i)
+{
+	if (i == COMMAND_LINE_FILENUM)
+		return _("command line");
+	else
+	{
+		char const *fname = i == LEAPSEC_FILENUM ? leapsec : main_argv[i];
+
+		return strcmp(fname, "-") == 0 ? _("standard input") : fname;
+	}
+}
+
 static void
-eats(char const *name, lineno_t num, char const *rname, lineno_t rnum)
+eats(int fnum, lineno_t num, int rfnum, lineno_t rnum)
 {
-	filename = name;
+	filenum = fnum;
 	linenum = num;
-	rfilename = rname;
+	rfilenum = rfnum;
 	rlinenum = rnum;
 }
 
 static void
-eat(char const *name, lineno_t num)
+eat(int fnum, lineno_t num)
 {
-	eats(name, num, NULL, -1);
+	eats(fnum, num, 0, -1);
 }
 
-static void
+ATTRIBUTE_FORMAT((printf, 1, 0)) static void
 verror(const char *const string, va_list args)
 {
+	check_for_signal();
+
 	/*
 	 * Match the format of "cc" to allow sh users to zic ... 2>&1 | error -t
 	 * "*" -v on BSD systems.
 	 */
-	if (filename)
-		fprintf(stderr, _("\"%s\", line %" PRIdMAX ": "), filename, linenum);
+	if (filenum)
+		fprintf(stderr, _("\"%s\", line %" PRIdMAX ": "),
+				filename(filenum), linenum);
 	vfprintf(stderr, string, args);
-	if (rfilename != NULL)
+	if (rfilenum)
 		fprintf(stderr, _(" (rule from \"%s\", line %" PRIdMAX ")"),
-				rfilename, rlinenum);
+				filename(rfilenum), rlinenum);
 	fprintf(stderr, "\n");
 }
 
-static void
+ATTRIBUTE_FORMAT((printf, 1, 2)) static void
 error(const char *const string, ...)
 {
 	va_list		args;
@@ -529,7 +694,7 @@ error(const char *const string, ...)
 	errors = true;
 }
 
-static void
+ATTRIBUTE_FORMAT((printf, 1, 2)) static void
 warning(const char *const string, ...)
 {
 	va_list		args;
@@ -541,38 +706,221 @@ warning(const char *const string, ...)
 	warnings = true;
 }
 
+/*
+ * Convert ARG, a string in base BASE, to an unsigned long value no
+ * greater than MAXVAL.  On failure, diagnose with MSGID and exit.
+ */
+static unsigned long
+arg2num(char const *arg, int base, unsigned long maxval, char const *msgid)
+{
+	unsigned long n;
+	char	   *ep;
+
+	errno = 0;
+	n = strtoul(arg, &ep, base);
+	if (ep == arg || *ep || maxval < n || errno)
+	{
+		fprintf(stderr, _(msgid), progname, arg);
+		exit(EXIT_FAILURE);
+	}
+	return n;
+}
+
+#ifndef MODE_T_MAX
+#define MODE_T_MAX MAXVAL(mode_t, TYPE_BIT(mode_t))
+#endif
+
+#ifndef HAVE_FCHMOD
+#define HAVE_FCHMOD 1
+#endif
+#if !HAVE_FCHMOD
+#define fchmod(fd, mode) 0
+#endif
+
+#ifndef HAVE_SETMODE
+#if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+	  || (defined __APPLE__ && defined __MACH__))
+#define HAVE_SETMODE 1
+#else
+#define HAVE_SETMODE 0
+#endif
+#endif
+
+static mode_t const no_mode = -1;
+static mode_t output_mode = -1;
+
+static mode_t
+mode_option(char const *arg)
+{
+#if HAVE_SETMODE
+	void	   *set = setmode(arg);
+
+	if (set)
+	{
+		mode_t		mode = getmode(set, CREAT_PERMS);
+
+		free(set);
+		return mode;
+	}
+#endif
+	return arg2num(arg, 8, min(MODE_T_MAX, ULONG_MAX),
+				   N_("%s: -m '%s': invalid mode\n"));
+}
+
+static int
+chmetadata(FILE *stream)
+{
+	if (output_owner != no_uid || output_group != no_gid)
+	{
+		int			r = fchown(fileno(stream), output_owner, output_group);
+
+		if (r < 0)
+			return r;
+	}
+	return output_mode == no_mode ? 0 : fchmod(fileno(stream), output_mode);
+}
+
+/*
+ * Close STREAM.
+ * If it had an I/O error, report it against DIR/NAME,
+ * remove TEMPNAME if nonnull, and then exit.
+ * If TEMPNAME is nonnull, and if requested,
+ * change the stream's metadata before closing.
+ */
 static void
-close_file(FILE *stream, char const *dir, char const *name)
+close_file(FILE *stream, char const *dir, char const *name,
+		   char const *tempname)
 {
 	char const *e = (ferror(stream) ? _("I/O error")
-					 : fclose(stream) != 0 ? strerror(errno) : NULL);
+					 : ((tempname
+						 && (fflush(stream) < 0 || chmetadata(stream) < 0))
+						|| fclose(stream) < 0)
+					 ? strerror(errno) : NULL);
 
 	if (e)
 	{
+		if (name && *name == '/')
+			dir = NULL;
 		fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
 				dir ? dir : "", dir ? "/" : "",
 				name ? name : "", name ? ": " : "",
 				e);
+		if (tempname)
+			remove(tempname);
 		exit(EXIT_FAILURE);
 	}
 }
 
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
+duplicate_options(char const *opt)
+{
+	fprintf(stderr, _("%s: More than one %s option specified\n"), progname, opt);
+	exit(EXIT_FAILURE);
+}
+
+ATTRIBUTE_NORETURN static void
 usage(FILE *stream, int status)
 {
 	fprintf(stream,
 			_("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n"
-			  "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]"
-			  " [ -L leapseconds ] \\\n"
-			  "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -t localtime-link ] \\\n"
+			  "\t[ -b {slim|fat} ] [ -d directory ] [ -D ] \\\n"
+			  "\t[ -l localtime ] [ -L leapseconds ] [ -m mode ] \\\n"
+			  "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R @hi ] \\\n"
+			  "\t[ -t localtime-link ] [ -u 'owner[:group]' ] \\\n"
 			  "\t[ filename ... ]\n\n"
 			  "Report bugs to %s.\n"),
 			progname, progname, PACKAGE_BUGREPORT);
 	if (status == EXIT_SUCCESS)
-		close_file(stream, NULL, NULL);
+		close_file(stream, NULL, NULL, NULL);
 	exit(status);
 }
 
+static void
+group_option(char const *arg)
+{
+	if (*arg)
+	{
+		if (output_group != no_gid)
+		{
+			fprintf(stderr, _("multiple groups specified"));
+			exit(EXIT_FAILURE);
+		}
+		else
+		{
+			struct group *gr = getgrnam(arg);
+
+			output_group = (gr ? gr->gr_gid
+							: arg2num(arg, 10, min(GID_T_MAX, ULONG_MAX),
+									  N_("%s: invalid group: %s\n")));
+		}
+	}
+}
+
+static void
+owner_option(char const *arg)
+{
+	if (*arg)
+	{
+		if (output_owner != no_uid)
+		{
+			fprintf(stderr, _("multiple owners specified"));
+			exit(EXIT_FAILURE);
+		}
+		else
+		{
+			struct passwd *pw = getpwnam(arg);
+
+			output_owner = (pw ? pw->pw_uid
+							: arg2num(arg, 10, min(UID_T_MAX, ULONG_MAX),
+									  N_("%s: invalid owner: %s\n")));
+		}
+	}
+}
+
+/*
+ * If setting owner or group, use temp file permissions that avoid
+ * security races before the fchmod at the end.
+ */
+static void
+use_safe_temp_permissions(void)
+{
+	if (output_owner != no_uid || output_group != no_gid)
+	{
+
+		/* The mode when done with the file.  */
+		mode_t		omode;
+
+		if (output_mode == no_mode)
+		{
+			mode_t		cmask = umask(0);
+
+			umask(cmask);
+			omode = CREAT_PERMS & ~cmask;
+		}
+		else
+			omode = output_mode;
+
+		/*
+		 * The mode passed to open+O_CREAT.  Do not bother with executable
+		 * permissions, as they should not be used and this mode is merely a
+		 * nicety (even a mode of 0 still work).
+		 */
+		creat_perms = ((((omode & (S_IRUSR | S_IRGRP | S_IROTH))
+						 == (S_IRUSR | S_IRGRP | S_IROTH))
+						? S_IRUSR | S_IRGRP | S_IROTH : 0)
+					   | (((omode & (S_IWUSR | S_IWGRP | S_IWOTH))
+						   == (S_IWUSR | S_IWGRP | S_IWOTH))
+						  ? S_IWUSR | S_IWGRP | S_IWOTH : 0));
+
+		/*
+		 * If creat_perms is not the final mode, arrange to run fchmod later,
+		 * even if -m was not used.
+		 */
+		if (creat_perms != omode)
+			output_mode = omode;
+	}
+}
+
 /*
  * Change the working directory to DIR, possibly creating DIR and its
  * ancestors.  After this is done, all files are accessed with names
@@ -599,7 +947,223 @@ change_directory(char const *dir)
 	}
 }
 
-#define TIME_T_BITS_IN_FILE 64
+/* Compare the two links A and B, for a stable sort by link name.  */
+static int
+qsort_linkcmp(void const *a, void const *b)
+{
+	struct link const *l = a;
+	struct link const *m = b;
+	int			cmp = strcmp(l->l_linkname, m->l_linkname);
+
+	if (cmp)
+		return cmp;
+
+	/*
+	 * The link names are the same.  Make the sort stable by comparing file
+	 * numbers (where subtraction cannot overflow) and possibly line numbers
+	 * (where it can).
+	 */
+	cmp = l->l_filenum - m->l_filenum;
+	if (cmp)
+		return cmp;
+	return (l->l_linenum > m->l_linenum) - (l->l_linenum < m->l_linenum);
+}
+
+/* Compare the string KEY to the link B, for bsearch.  */
+static int
+bsearch_linkcmp(void const *key, void const *b)
+{
+	struct link const *m = b;
+
+	return strcmp(key, m->l_linkname);
+}
+
+/* Make the links specified by the Link lines.  */
+static void
+make_links(void)
+{
+	ptrdiff_t	i,
+				j,
+				nalinks,
+				pass_size;
+
+	if (1 < nlinks)
+		qsort(links, nlinks, sizeof *links, qsort_linkcmp);
+
+	/* Ignore each link superseded by a later link with the same name.  */
+	j = 0;
+	for (i = 0; i < nlinks; i++)
+	{
+		while (i + 1 < nlinks
+			   && strcmp(links[i].l_linkname, links[i + 1].l_linkname) == 0)
+			i++;
+		links[j++] = links[i];
+	}
+	nlinks = pass_size = j;
+
+	/*
+	 * Walk through the link array making links.  However, if a link's target
+	 * has not been made yet, append a copy to the end of the array.  The end
+	 * of the array will gradually fill up with a small sorted subsequence of
+	 * not-yet-made links. nalinks counts all the links in the array,
+	 * including copies. When we reach the copied subsequence, it may still
+	 * contain a link to a not-yet-made link, so the process repeats. At any
+	 * given point in time, the link array consists of the following
+	 * subregions, where 0 <= i <= j <= nalinks and 0 <= nlinks <= nalinks:
+	 *
+	 * 0 .. (i - 1): links that either have been made, or have been copied to
+	 * a later point point in the array (this later point can be in any of the
+	 * three subregions) i .. (j - 1): not-yet-made links for this pass j ..
+	 * (nalinks - 1): not-yet-made links that this pass has skipped because
+	 * they were links to not-yet-made links
+	 *
+	 * The first subregion might not be sorted if nlinks < i; the other two
+	 * subregions are sorted.  This algorithm does not alter entries 0 ..
+	 * (nlinks - 1), which remain sorted.
+	 *
+	 * If there are L links, this algorithm is O(C*L*log(L)) where C is the
+	 * length of the longest link chain.  Usually C is short (e.g., 3) though
+	 * its worst-case value is L.
+	 */
+
+	j = nalinks = nlinks;
+
+	for (i = 0; i < nalinks; i++)
+	{
+		struct link *l;
+
+		eat(links[i].l_filenum, links[i].l_linenum);
+
+		/* If this pass examined all its links, start the next pass.  */
+		if (i == j)
+		{
+			if (nalinks - i == pass_size)
+			{
+				error(_("\"Link %s %s\" is part of a link cycle"),
+					  links[i].l_target, links[i].l_linkname);
+				break;
+			}
+			j = nalinks;
+			pass_size = nalinks - i;
+		}
+
+		/*
+		 * Diagnose self links, which the cycle detection algorithm would not
+		 * otherwise catch.
+		 */
+		if (strcmp(links[i].l_target, links[i].l_linkname) == 0)
+		{
+			error(_("link %s targets itself"), links[i].l_target);
+			continue;
+		}
+
+		/* Make this link unless its target has not been made yet.  */
+		l = bsearch(links[i].l_target, &links[i + 1], j - (i + 1),
+					sizeof *links, bsearch_linkcmp);
+		if (!l)
+			l = bsearch(links[i].l_target, &links[j], nalinks - j,
+						sizeof *links, bsearch_linkcmp);
+		if (!l)
+			dolink(links[i].l_target, links[i].l_linkname, false);
+		else
+		{
+			/*
+			 * The link target has not been made yet; copy the link to the
+			 * end.
+			 */
+			links = growalloc(links, sizeof *links, nalinks, &nlinks_alloc);
+			links[nalinks++] = links[i];
+		}
+
+		if (noise && i < nlinks)
+		{
+			if (l)
+				warning(_("link %s targeting link %s mishandled by pre-2023 zic"),
+						links[i].l_linkname, links[i].l_target);
+			else if (bsearch(links[i].l_target, links, nlinks, sizeof *links,
+							 bsearch_linkcmp))
+				warning(_("link %s targeting link %s"),
+						links[i].l_linkname, links[i].l_target);
+		}
+		check_for_signal();
+	}
+}
+
+/*
+ * Simple signal handling: just set a flag that is checked
+ * periodically outside critical sections.  To set up the handler,
+ * prefer sigaction if available to close a signal race.
+ */
+
+static sig_atomic_t got_signal;
+
+static void
+signal_handler(int sig)
+{
+#ifndef SA_SIGINFO
+	signal(sig, signal_handler);
+#endif
+	got_signal = sig;
+}
+
+/* Arrange for SIGINT etc. to be caught by the handler.  */
+static void
+catch_signals(void)
+{
+	static int const signals[] = {
+#ifdef SIGHUP
+		SIGHUP,
+#endif
+		SIGINT,
+#ifdef SIGPIPE
+		SIGPIPE,
+#endif
+		SIGTERM
+	};
+	int			i;
+
+	for (i = 0; i < sizeof signals / sizeof signals[0]; i++)
+	{
+#ifdef SA_SIGINFO
+		struct sigaction act0,
+					act;
+
+		act.sa_handler = signal_handler;
+		sigemptyset(&act.sa_mask);
+		act.sa_flags = 0;
+		if (sigaction(signals[i], &act, &act0) == 0
+			&& !(act0.sa_flags & SA_SIGINFO) && act0.sa_handler == SIG_IGN)
+		{
+			sigaction(signals[i], &act0, NULL);
+			got_signal = 0;
+		}
+#else
+		if (signal(signals[i], signal_handler) == SIG_IGN)
+		{
+			signal(signals[i], SIG_IGN);
+			got_signal = 0;
+		}
+#endif
+	}
+}
+
+/* If a signal has arrived, terminate zic with appropriate status.  */
+static void
+check_for_signal(void)
+{
+	int			sig = got_signal;
+
+	if (sig)
+	{
+		signal(sig, SIG_DFL);
+		raise(sig);
+		abort();				/* A bug in 'raise'.  */
+	}
+}
+
+enum
+{
+TIME_T_BITS_IN_FILE = 64};
 
 /* The minimum and maximum values representable in a TZif file.  */
 static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
@@ -612,12 +1176,15 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
 static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
 static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
 
+/*
+ * The time specified by the -R option, defaulting to MIN_TIME;
+ * or lo_time, whichever is greater.
+ */
+static zic_t redundant_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
+
 /* The time specified by an Expires line, or negative if no such line.  */
 static zic_t leapexpires = -1;
 
-/* The time specified by an #expires comment, or negative if no such line.  */
-static zic_t comment_leapexpires = -1;
-
 /*
  * Set the time range of the output to TIMERANGE.
  * Return true if successful.
@@ -648,17 +1215,38 @@ timerange_option(char *timerange)
 	}
 	if (*hi_end || hi < lo || max_time < lo || hi < min_time)
 		return false;
-	lo_time = lo < min_time ? min_time : lo;
-	hi_time = max_time < hi ? max_time : hi;
+	lo_time = max(lo, min_time);
+	hi_time = min(hi, max_time);
 	return true;
 }
 
+/* Generate redundant time stamps up to OPT.  Return true if successful.  */
+static bool
+redundant_time_option(char *opt)
+{
+	if (*opt == '@')
+	{
+		intmax_t	redundant;
+		char	   *opt_end;
+
+		redundant = strtoimax(opt + 1, &opt_end, 10);
+		if (opt_end != opt + 1 && !*opt_end)
+		{
+			redundant_time = max(redundant_time, redundant);
+			return true;
+		}
+	}
+	return false;
+}
+
 static const char *psxrules;
 static const char *lcltime;
 static const char *directory;
-static const char *leapsec;
 static const char *tzdefault;
 
+/* True if DIRECTORY ends in '/'.  */
+static bool directory_ends_in_slash;
+
 /*
  * -1 if the TZif output file should be slim, 0 if default, 1 if the
  * output should be fat for backward compatibility.  ZIC_BLOAT_DEFAULT
@@ -685,10 +1273,8 @@ main(int argc, char **argv)
 				j;
 	bool		timerange_given = false;
 
-#ifndef WIN32
-	umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
-#endif
-	progname = argv[0];
+	main_argv = argv;
+	progname = argv[0] ? argv[0] : "zic";
 	if (TYPE_BIT(zic_t) < 64)
 	{
 		fprintf(stderr, "%s: %s\n", progname,
@@ -699,14 +1285,14 @@ main(int argc, char **argv)
 		if (strcmp(argv[k], "--version") == 0)
 		{
 			printf("zic %s\n", PG_VERSION);
-			close_file(stdout, NULL, NULL);
+			close_file(stdout, NULL, NULL, NULL);
 			return EXIT_SUCCESS;
 		}
 		else if (strcmp(argv[k], "--help") == 0)
 		{
 			usage(stdout, EXIT_SUCCESS);
 		}
-	while ((c = getopt(argc, argv, "b:d:l:L:p:Pr:st:vy:")) != EOF && c != -1)
+	while ((c = getopt(argc, argv, "b:d:Dg:l:L:m:p:Pr:R:st:u:vy:")) != -1)
 		switch (c)
 		{
 			default:
@@ -728,62 +1314,62 @@ main(int argc, char **argv)
 					error(_("invalid option: -b '%s'"), optarg);
 				break;
 			case 'd':
-				if (directory == NULL)
-					directory = strdup(optarg);
-				else
-				{
-					fprintf(stderr,
-							_("%s: More than one -d option specified\n"),
-							progname);
-					return EXIT_FAILURE;
-				}
+				if (directory)
+					duplicate_options("-d");
+				directory = strdup(optarg);
+				break;
+			case 'D':
+				skip_mkdir = true;
+				break;
+			case 'g':
+
+				/*
+				 * This undocumented option is present for compatibility with
+				 * FreeBSD 14.
+				 */
+				group_option(optarg);
 				break;
 			case 'l':
-				if (lcltime == NULL)
-					lcltime = strdup(optarg);
-				else
-				{
-					fprintf(stderr,
-							_("%s: More than one -l option specified\n"),
-							progname);
-					return EXIT_FAILURE;
-				}
+				if (lcltime)
+					duplicate_options("-l");
+				lcltime = strdup(optarg);
+				break;
+			case 'm':
+				if (output_mode != no_mode)
+					duplicate_options("-m");
+				output_mode = mode_option(optarg);
 				break;
 			case 'p':
-				if (psxrules == NULL)
-					psxrules = strdup(optarg);
-				else
-				{
-					fprintf(stderr,
-							_("%s: More than one -p option specified\n"),
-							progname);
-					return EXIT_FAILURE;
-				}
+				if (psxrules)
+					duplicate_options("-p");
+				if (strcmp(optarg, "-") != 0)
+					warning(_("-p is obsolete"
+							  " and likely ineffective"));
+				psxrules = strdup(optarg);
 				break;
 			case 't':
-				if (tzdefault != NULL)
+				if (tzdefault)
+					duplicate_options("-t");
+				tzdefault = strdup(optarg);
+				break;
+			case 'u':
 				{
-					fprintf(stderr,
-							_("%s: More than one -t option"
-							  " specified\n"),
-							progname);
-					return EXIT_FAILURE;
+					char	   *colon = strchr(optarg, ':');
+
+					if (colon)
+						*colon = '\0';
+					owner_option(optarg);
+					if (colon)
+						group_option(colon + 1);
 				}
-				tzdefault = optarg;
 				break;
 			case 'y':
 				warning(_("-y ignored"));
 				break;
 			case 'L':
-				if (leapsec == NULL)
-					leapsec = strdup(optarg);
-				else
-				{
-					fprintf(stderr,
-							_("%s: More than one -L option specified\n"),
-							progname);
-					return EXIT_FAILURE;
-				}
+				if (leapsec)
+					duplicate_options("-L");
+				leapsec = strdup(optarg);
 				break;
 			case 'v':
 				noise = true;
@@ -794,12 +1380,7 @@ main(int argc, char **argv)
 				break;
 			case 'r':
 				if (timerange_given)
-				{
-					fprintf(stderr,
-							_("%s: More than one -r option specified\n"),
-							progname);
-					return EXIT_FAILURE;
-				}
+					duplicate_options("-r");
 				if (!timerange_option(optarg))
 				{
 					fprintf(stderr,
@@ -809,12 +1390,27 @@ main(int argc, char **argv)
 				}
 				timerange_given = true;
 				break;
+			case 'R':
+				if (!redundant_time_option(optarg))
+				{
+					fprintf(stderr, _("%s: invalid time: %s\n"),
+							progname, optarg);
+					return EXIT_FAILURE;
+				}
+				break;
 			case 's':
 				warning(_("-s ignored"));
 				break;
 		}
 	if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
 		usage(stderr, EXIT_FAILURE);	/* usage message by request */
+	if (hi_time + (hi_time < ZIC_MAX) < redundant_time)
+	{
+		fprintf(stderr, _("%s: -R time exceeds -r cutoff\n"), progname);
+		return EXIT_FAILURE;
+	}
+	if (redundant_time < lo_time)
+		redundant_time = lo_time;
 	if (bloat == 0)
 	{
 		static char const bloat_default[] = ZIC_BLOAT_DEFAULT;
@@ -833,16 +1429,19 @@ main(int argc, char **argv)
 
 	if (optind < argc && leapsec != NULL)
 	{
-		infile(leapsec);
+		infile(LEAPSEC_FILENUM, leapsec);
 		adjleap();
 	}
 
 	for (k = optind; k < argc; k++)
-		infile(argv[k]);
+		infile(k, argv[k]);
 	if (errors)
 		return EXIT_FAILURE;
 	associate();
+	use_safe_temp_permissions();
 	change_directory(directory);
+	directory_ends_in_slash = directory[strlen(directory) - 1] == '/';
+	catch_signals();
 	for (i = 0; i < nzones; i = j)
 	{
 		/*
@@ -851,29 +1450,17 @@ main(int argc, char **argv)
 		for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j)
 			continue;
 		outzone(&zones[i], j - i);
+		check_for_signal();
 	}
-
-	/*
-	 * Make links.
-	 */
-	for (i = 0; i < nlinks; ++i)
-	{
-		eat(links[i].l_filename, links[i].l_linenum);
-		dolink(links[i].l_target, links[i].l_linkname, false);
-		if (noise)
-			for (j = 0; j < nlinks; ++j)
-				if (strcmp(links[i].l_linkname,
-						   links[j].l_target) == 0)
-					warning(_("link to link"));
-	}
+	make_links();
 	if (lcltime != NULL)
 	{
-		eat(_("command line"), 1);
+		eat(COMMAND_LINE_FILENUM, 1);
 		dolink(lcltime, tzdefault, true);
 	}
 	if (psxrules != NULL)
 	{
-		eat(_("command line"), 1);
+		eat(COMMAND_LINE_FILENUM, 1);
 		dolink(psxrules, TZDEFRULES, true);
 	}
 	if (warnings && (ferror(stderr) || fclose(stderr) != 0))
@@ -906,71 +1493,301 @@ componentcheck(char const *name, char const *component,
 	if (0 < component_len && component_len <= 2
 		&& component[0] == '.' && component_end[-1] == '.')
 	{
-		int			len = component_len;
+		int			len = component_len;
+
+		error(_("file name '%s' contains '%.*s' component"),
+			  name, len, component);
+		return false;
+	}
+	if (noise)
+	{
+		if (0 < component_len && component[0] == '-')
+			warning(_("file name '%s' component contains leading '-'"),
+					name);
+		if (component_len_max < component_len)
+			warning(_("file name '%s' contains overlength component"
+					  " '%.*s...'"),
+					name, component_len_max, component);
+	}
+	return true;
+}
+
+static bool
+namecheck(const char *name)
+{
+	char const *cp;
+
+	/* Benign characters in a portable file name.  */
+	static char const benign[] =
+		"-/_"
+		"abcdefghijklmnopqrstuvwxyz"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+	/*
+	 * Non-control chars in the POSIX portable character set, excluding the
+	 * benign characters.
+	 */
+	static char const printable_and_not_benign[] =
+		" !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";
+
+	char const *component = name;
+
+	for (cp = name; *cp; cp++)
+	{
+		unsigned char c = *cp;
+
+		if (noise && !strchr(benign, c))
+		{
+			warning((strchr(printable_and_not_benign, c)
+					 ? _("file name '%s' contains byte '%c'")
+					 : _("file name '%s' contains byte '\\%o'")),
+					name, c);
+		}
+		if (c == '/')
+		{
+			if (!componentcheck(name, component, cp))
+				return false;
+			component = cp + 1;
+		}
+	}
+	return componentcheck(name, component, cp);
+}
+
+/* Return a random uint_fast64_t.  */
+static uint_fast64_t
+get_rand_u64(void)
+{
+#if HAVE_GETRANDOM
+	static uint_fast64_t entropy_buffer[max(1, 256 / sizeof(uint_fast64_t))];
+	static int	nwords;
+
+	if (!nwords)
+	{
+		ssize_t		s;
+
+		for (;; check_for_signal())
+		{
+			s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
+			if (!(s < 0 && errno == EINTR))
+				break;
+		}
+
+		nwords = s < 0 ? -1 : s / sizeof *entropy_buffer;
+	}
+	if (0 < nwords)
+		return entropy_buffer[--nwords];
+#endif
+
+	/*
+	 * getrandom didn't work, so fall back on portable code that is not the
+	 * best because the seed isn't cryptographically random and 'rand' might
+	 * not be cryptographically secure.
+	 */
+	{
+		static bool initialized;
+
+		if (!initialized)
+		{
+			srand(time(NULL));
+			initialized = true;
+		}
+	}
+
+	/*
+	 * Return a random number if rand() yields a random number and in the
+	 * typical case where RAND_MAX is one less than a power of two. In other
+	 * cases this code yields a sort-of-random number.
+	 */
+	{
+		uint_fast64_t rand_max = RAND_MAX,
+					nrand = rand_max < UINT_FAST64_MAX ? rand_max + 1 : 0,
+					rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
+					r = 0,
+					rmax = 0;
+
+		for (;; check_for_signal())
+		{
+			uint_fast64_t rmax1 = rmax;
+
+			if (rmod)
+			{
+				/*
+				 * Avoid signed integer overflow on theoretical platforms
+				 * where uint_fast64_t promotes to int.
+				 */
+				rmax1 %= rmod;
+				r %= rmod;
+			}
+			rmax1 = nrand * rmax1 + rand_max;
+			r = nrand * r + rand();
+			rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
+			if (UINT_FAST64_MAX <= rmax)
+				break;
+		}
+
+		return r;
+	}
+}
+
+/*
+ * Generate a randomish name in the same directory as *NAME.  If
+ * *NAMEALLOC, put the name into *NAMEALLOC which is assumed to be
+ * that returned by a previous call and is thus already almost set up
+ * and equal to *NAME; otherwise, allocate a new name and put its
+ * address into both *NAMEALLOC and *NAME.
+ */
+static void
+random_dirent(char const **name, char **namealloc)
+{
+	char const *src = *name;
+	char	   *dst = *namealloc;
+	static char const prefix[] = ".zic";
+	static char const alphabet[] =
+		"abcdefghijklmnopqrstuvwxyz"
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"0123456789";
+	enum
+	{
+	prefixlen = sizeof prefix - 1, alphabetlen = sizeof alphabet - 1};
+	int			suffixlen = 6;
+	char const *lastslash = strrchr(src, '/');
+	ptrdiff_t	dirlen = lastslash ? lastslash + 1 - src : 0;
+	int			i;
+	uint_fast64_t r;
+	uint_fast64_t base = alphabetlen;
 
-		error(_("file name '%s' contains '%.*s' component"),
-			  name, len, component);
-		return false;
+	/* BASE**6 */
+	uint_fast64_t base__6 = base * base * base * base * base * base;
+
+	/*
+	 * The largest uintmax_t that is a multiple of BASE**6.  Any random
+	 * uintmax_t value that is this value or greater, yields a biased
+	 * remainder when divided by BASE**6.  UNFAIR_MIN equals the mathematical
+	 * value of ((UINTMAX_MAX + 1) - (UINTMAX_MAX + 1) % BASE**6) computed
+	 * without overflow.
+	 */
+	uint_fast64_t unfair_min = -((UINTMAX_MAX % base__6 + 1) % base__6);
+
+	if (!dst)
+	{
+		char	   *cp = dst = xmalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
+
+		cp = mempcpy(cp, src, dirlen);
+		cp = mempcpy(cp, prefix, prefixlen);
+		cp[suffixlen] = '\0';
+		*name = *namealloc = dst;
 	}
-	if (noise)
+
+	for (;; check_for_signal())
 	{
-		if (0 < component_len && component[0] == '-')
-			warning(_("file name '%s' component contains leading '-'"),
-					name);
-		if (component_len_max < component_len)
-			warning(_("file name '%s' contains overlength component"
-					  " '%.*s...'"),
-					name, component_len_max, component);
+		r = get_rand_u64();
+		if (r < unfair_min)
+			break;
+	}
+
+	for (i = 0; i < suffixlen; i++)
+	{
+		dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
+		r /= alphabetlen;
 	}
-	return true;
 }
 
-static bool
-namecheck(const char *name)
+/*
+ * For diagnostics the directory, and file name relative to that
+ * directory, respectively.  A diagnostic routine can name FILENAME by
+ * outputting diagdir(FILENAME), then diagslash(FILENAME), then FILENAME.
+ */
+static char const *
+diagdir(char const *filename)
 {
-	char const *cp;
-
-	/* Benign characters in a portable file name.  */
-	static char const benign[] =
-		"-/_"
-		"abcdefghijklmnopqrstuvwxyz"
-		"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+	return *filename == '/' ? "" : directory;
+}
+static char const *
+diagslash(char const *filename)
+{
+	return &"/"[*filename == '/' || directory_ends_in_slash];
+}
 
-	/*
-	 * Non-control chars in the POSIX portable character set, excluding the
-	 * benign characters.
-	 */
-	static char const printable_and_not_benign[] =
-		" !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";
+/*
+ * Prepare to write to the file *OUTNAME, using *TEMPNAME to store the
+ * name of the temporary file that will eventually be renamed to
+ * *OUTNAME.  Assign the temporary file's name to both *OUTNAME and
+ * *TEMPNAME.  If *TEMPNAME is null, allocate the name of any such
+ * temporary file; otherwise, reuse *TEMPNAME's storage, which is
+ * already set up and only needs its trailing suffix updated.
+ */
+static FILE *
+open_outfile(char const **outname, char **tempname)
+{
+	bool		dirs_made = false;
 
-	char const *component = name;
+	if (!*tempname)
+		random_dirent(outname, tempname);
 
-	for (cp = name; *cp; cp++)
+	for (;; check_for_signal())
 	{
-		unsigned char c = *cp;
+		int			oflags = O_WRONLY | O_BINARY | O_CREAT | O_EXCL;
+		int			fd = open(*outname, oflags, creat_perms);
+		int			err;
 
-		if (noise && !strchr(benign, c))
+		if (fd < 0)
+			err = errno;
+		else
 		{
-			warning((strchr(printable_and_not_benign, c)
-					 ? _("file name '%s' contains byte '%c'")
-					 : _("file name '%s' contains byte '\\%o'")),
-					name, c);
+			FILE	   *fp = fdopen(fd, "wb");
+
+			if (fp)
+				return fp;
+			err = errno;
+			close(fd);
 		}
-		if (c == '/')
+		if (err == ENOENT && !dirs_made)
 		{
-			if (!componentcheck(name, component, cp))
-				return false;
-			component = cp + 1;
+			mkdirs(*outname, true);
+			dirs_made = true;
+		}
+		else if (err == EEXIST)
+			random_dirent(outname, tempname);
+		else
+		{
+			fprintf(stderr, _("%s: Can't create %s%s%s: %s\n"),
+					progname, diagdir(*outname), diagslash(*outname), *outname,
+					strerror(err));
+			exit(EXIT_FAILURE);
 		}
 	}
-	return componentcheck(name, component, cp);
 }
 
 /*
- * Create symlink contents suitable for symlinking FROM to TO, as a
- * freshly allocated string.  FROM should be a relative file name, and
- * is relative to the global variable DIRECTORY.  TO can be either
- * relative or absolute.
+ * If TEMPNAME, the result is in the temporary file TEMPNAME even
+ * though the user wanted it in NAME, so rename TEMPNAME to NAME.
+ * Report an error and exit if there is trouble.  Also, free TEMPNAME.
+ */
+static void
+rename_dest(char *tempname, char const *name)
+{
+	if (tempname)
+	{
+		if (rename(tempname, name) != 0)
+		{
+			int			rename_errno = errno;
+
+			remove(tempname);
+			fprintf(stderr, _("%s: rename to %s%s%s: %s\n"),
+					progname, diagdir(name), diagslash(name), name,
+					strerror(rename_errno));
+			exit(EXIT_FAILURE);
+		}
+		free(tempname);
+	}
+}
+
+/*
+ * Create symlink contents suitable for symlinking TARGET to LINKNAME, as a
+ * freshly allocated string.  TARGET should be a relative file name, and
+ * is relative to the global variable DIRECTORY.  LINKNAME can be either
+ * relative or absolute.  Return a null pointer if the symlink contents
+ * was not computed because LINKNAME is absolute but DIRECTORY is not.
  */
 #ifdef HAVE_SYMLINK
 static char *
@@ -978,10 +1795,10 @@ relname(char const *target, char const *linkname)
 {
 	size_t		i,
 				taillen,
-				dotdotetcsize;
-	size_t		dir_len = 0,
-				dotdots = 0,
-				linksize = SIZE_MAX;
+				dir_len = 0,
+				dotdots = 0;
+	ptrdiff_t	dotdotetcsize,
+				linksize = INDEX_MAX;
 	char const *f = target;
 	char	   *result = NULL;
 
@@ -989,13 +1806,18 @@ relname(char const *target, char const *linkname)
 	{
 		/* Make F absolute too.  */
 		size_t		len = strlen(directory);
-		bool		needslash = len && directory[len - 1] != '/';
-
-		linksize = len + needslash + strlen(target) + 1;
-		f = result = emalloc(linksize);
-		strcpy(result, directory);
-		result[len] = '/';
-		strcpy(result + len + needslash, target);
+		bool		needs_slash = len && directory[len - 1] != '/';
+		size_t		lenslash = len + needs_slash;
+		size_t		targetsize = strlen(target) + 1;
+		char	   *cp;
+
+		if (*directory != '/')
+			return NULL;
+		linksize = size_sum(lenslash, targetsize);
+		f = cp = result = xmalloc(linksize);
+		cp = mempcpy(cp, directory, len);
+		*cp = '/';
+		memcpy(cp + needs_slash, target, targetsize);
 	}
 	for (i = 0; f[i] && f[i] == linkname[i]; i++)
 		if (f[i] == '/')
@@ -1003,68 +1825,115 @@ relname(char const *target, char const *linkname)
 	for (; linkname[i]; i++)
 		dotdots += linkname[i] == '/' && linkname[i - 1] != '/';
 	taillen = strlen(f + dir_len);
-	dotdotetcsize = 3 * dotdots + taillen + 1;
+	dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1);
 	if (dotdotetcsize <= linksize)
 	{
+		char	   *cp;
+
 		if (!result)
-			result = emalloc(dotdotetcsize);
+			result = xmalloc(dotdotetcsize);
+		cp = result;
 		for (i = 0; i < dotdots; i++)
-			memcpy(result + 3 * i, "../", 3);
-		memmove(result + 3 * dotdots, f + dir_len, taillen + 1);
+			cp = mempcpy(cp, "../", 3);
+		memmove(cp, f + dir_len, taillen + 1);
 	}
 	return result;
 }
 #endif							/* HAVE_SYMLINK */
 
 /*
- * Hard link FROM to TO, following any symbolic links.
- * Return 0 if successful, an error number otherwise.
+ * Return true if A and B must have the same parent dir if A and B exist.
+ * Return false if this is not necessarily true (though it might be true).
+ * Keep it simple, and do not inspect the file system.
  */
-static int
-hardlinkerr(char const *target, char const *linkname)
+ATTRIBUTE_PURE_114833
+static bool
+same_parent_dirs(char const *a, char const *b)
 {
-	int			r = linkat(AT_FDCWD, target, AT_FDCWD, linkname, AT_SYMLINK_FOLLOW);
-
-	return r == 0 ? 0 : errno;
+	for (; *a == *b; a++, b++)
+		if (!*a)
+			return true;
+	return !(strchr(a, '/') || strchr(b, '/'));
 }
 
 static void
 dolink(char const *target, char const *linkname, bool staysymlink)
 {
-	bool		remove_only = strcmp(target, "-") == 0;
 	bool		linkdirs_made = false;
 	int			link_errno;
+	char	   *tempname = NULL;
+	char const *outname = linkname;
+	int			targetissym = -2,
+				linknameissym = -2;
 
-	/*
-	 * We get to be careful here since there's a fair chance of root running
-	 * us.
-	 */
-	if (!remove_only && itsdir(target))
-	{
-		fprintf(stderr, _("%s: linking target %s/%s failed: %s\n"),
-				progname, directory, target, strerror(EPERM));
-		exit(EXIT_FAILURE);
-	}
-	if (staysymlink)
-		staysymlink = itssymlink(linkname);
-	if (remove(linkname) == 0)
-		linkdirs_made = true;
-	else if (errno != ENOENT)
+	if (strcmp(target, "-") == 0)
 	{
-		char const *e = strerror(errno);
+		if (remove(linkname) == 0 || errno == ENOENT || errno == ENOTDIR)
+			return;
+		else
+		{
+			char const *e = strerror(errno);
 
-		fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
-				progname, directory, linkname, e);
-		exit(EXIT_FAILURE);
+			fprintf(stderr, _("%s: Can't remove %s%s%s: %s\n"),
+					progname, diagdir(linkname), diagslash(linkname), linkname,
+					e);
+			exit(EXIT_FAILURE);
+		}
 	}
-	if (remove_only)
-		return;
-	link_errno = staysymlink ? ENOTSUP : hardlinkerr(target, linkname);
-	if (link_errno == ENOENT && !linkdirs_made)
+
+	for (;; check_for_signal())
 	{
-		mkdirs(linkname, true);
-		linkdirs_made = true;
-		link_errno = hardlinkerr(target, linkname);
+		if (linkat(AT_FDCWD, target, AT_FDCWD, outname, AT_SYMLINK_FOLLOW)
+			== 0)
+		{
+			link_errno = 0;
+			break;
+		}
+		link_errno = errno;
+		/* Linux 2.6.16 and 2.6.17 mishandle AT_SYMLINK_FOLLOW.  */
+		if (link_errno == EINVAL)
+			link_errno = ENOTSUP;
+
+		/*
+		 * If linkat is not supported, fall back on link(A, B). However, skip
+		 * this if A is a relative symlink and A and B might not have the same
+		 * parent directory. On some platforms link(A, B) does not follow a
+		 * symlink A, and if A is relative it might misbehave elsewhere.
+		 */
+		if (link_errno == ENOTSUP
+			&& (same_parent_dirs(target, outname)
+				|| 0 <= itssymlink(target, &targetissym)))
+		{
+			if (link(target, outname) == 0)
+			{
+				link_errno = 0;
+				break;
+			}
+			link_errno = errno;
+		}
+		if (link_errno == EXDEV || link_errno == ENOTSUP)
+			break;
+
+		if (link_errno == EEXIST)
+		{
+			staysymlink &= !tempname;
+			random_dirent(&outname, &tempname);
+			if (staysymlink && itssymlink(linkname, &linknameissym))
+				break;
+		}
+		else if (link_errno == ENOENT && !linkdirs_made)
+		{
+			mkdirs(linkname, true);
+			linkdirs_made = true;
+		}
+		else
+		{
+			fprintf(stderr, _("%s: Can't link %s%s%s to %s%s%s: %s\n"),
+					progname, diagdir(target), diagslash(target), target,
+					diagdir(outname), diagslash(outname), outname,
+					strerror(link_errno));
+			exit(EXIT_FAILURE);
+		}
 	}
 	if (link_errno != 0)
 	{
@@ -1072,19 +1941,33 @@ dolink(char const *target, char const *linkname, bool staysymlink)
 		bool		absolute = *target == '/';
 		char	   *linkalloc = absolute ? NULL : relname(target, linkname);
 		char const *contents = absolute ? target : linkalloc;
-		int			symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno;
+		int			symlink_errno = -1;
 
-		if (!linkdirs_made
-			&& (symlink_errno == ENOENT || symlink_errno == ENOTSUP))
+		if (contents)
 		{
-			mkdirs(linkname, true);
-			if (symlink_errno == ENOENT)
-				symlink_errno = symlink(contents, linkname) == 0 ? 0 : errno;
+			for (;; check_for_signal())
+			{
+				if (symlink(contents, outname) == 0)
+				{
+					symlink_errno = 0;
+					break;
+				}
+				symlink_errno = errno;
+				if (symlink_errno == EEXIST)
+					random_dirent(&outname, &tempname);
+				else if (symlink_errno == ENOENT && !linkdirs_made)
+				{
+					mkdirs(linkname, true);
+					linkdirs_made = true;
+				}
+				else
+					break;
+			}
 		}
 		free(linkalloc);
 		if (symlink_errno == 0)
 		{
-			if (link_errno != ENOTSUP)
+			if (link_errno != ENOTSUP && link_errno != EEXIST)
 				warning(_("symbolic link used because hard link failed: %s"),
 						strerror(link_errno));
 		}
@@ -1100,68 +1983,46 @@ dolink(char const *target, char const *linkname, bool staysymlink)
 			{
 				char const *e = strerror(errno);
 
-				fprintf(stderr, _("%s: Can't read %s/%s: %s\n"),
-						progname, directory, target, e);
-				exit(EXIT_FAILURE);
-			}
-			tp = fopen(linkname, "wb");
-			if (!tp)
-			{
-				char const *e = strerror(errno);
-
-				fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
-						progname, directory, linkname, e);
+				fprintf(stderr, _("%s: Can't read %s%s%s: %s\n"),
+						progname, diagdir(target), diagslash(target), target, e);
 				exit(EXIT_FAILURE);
 			}
-			while ((c = getc(fp)) != EOF)
+			tp = open_outfile(&outname, &tempname);
+			for (; (c = getc(fp)) != EOF; check_for_signal())
 				putc(c, tp);
-			close_file(fp, directory, target);
-			close_file(tp, directory, linkname);
+			close_file(tp, directory, linkname, tempname);
+			close_file(fp, directory, target, NULL);
 			if (link_errno != ENOTSUP)
 				warning(_("copy used because hard link failed: %s"),
 						strerror(link_errno));
 #ifdef HAVE_SYMLINK
+			else if (symlink_errno < 0)
+				warning(_("copy used because symbolic link not obvious"));
 			else if (symlink_errno != ENOTSUP)
 				warning(_("copy used because symbolic link failed: %s"),
 						strerror(symlink_errno));
 #endif
 		}
 	}
+	rename_dest(tempname, linkname);
 }
 
-/* Return true if NAME is a directory.  */
-static bool
-itsdir(char const *name)
+/*
+ * Return 1 if NAME is an absolute symbolic link, -1 if it is relative,
+ * 0 if it is not a symbolic link.  If *CACHE is not -2, it is the
+ * cached result of a previous call to this function with the same NAME.
+ */
+static int
+itssymlink(char const *name, int *cache)
 {
-	struct stat st;
-	int			res = stat(name, &st);
-#ifdef S_ISDIR
-	if (res == 0)
-		return S_ISDIR(st.st_mode) != 0;
-#endif
-	if (res == 0 || errno == EOVERFLOW)
+#ifdef HAVE_SYMLINK
+	if (*cache == -2)
 	{
-		size_t		n = strlen(name);
-		char	   *nameslashdot = emalloc(n + 3);
-		bool		dir;
+		char		c = '\0';
 
-		memcpy(nameslashdot, name, n);
-		strcpy(&nameslashdot[n], &"/."[!(n && name[n - 1] != '/')]);
-		dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW;
-		free(nameslashdot);
-		return dir;
+		*cache = readlink(name, &c, 1) < 0 ? 0 : c == '/' ? 1 : -1;
 	}
-	return false;
-}
-
-/* Return true if NAME is a symbolic link.  */
-static bool
-itssymlink(char const *name)
-{
-#ifdef HAVE_SYMLINK
-	char		c;
-
-	return 0 <= readlink(name, &c, 1);
+	return *cache;
 #else
 	return false;
 #endif
@@ -1178,8 +2039,10 @@ itssymlink(char const *name)
 static int
 rcomp(const void *cp1, const void *cp2)
 {
-	return strcmp(((const struct rule *) cp1)->r_name,
-				  ((const struct rule *) cp2)->r_name);
+	struct rule const *r1 = cp1,
+			   *r2 = cp2;
+
+	return strcmp(r1->r_name, r2->r_name);
 }
 
 static void
@@ -1192,7 +2055,7 @@ associate(void)
 				base,
 				out;
 
-	if (nrules != 0)
+	if (1 < nrules)
 	{
 		qsort(rules, nrules, sizeof *rules, rcomp);
 		for (i = 0; i < nrules - 1; ++i)
@@ -1200,23 +2063,21 @@ associate(void)
 			if (strcmp(rules[i].r_name,
 					   rules[i + 1].r_name) != 0)
 				continue;
-			if (strcmp(rules[i].r_filename,
-					   rules[i + 1].r_filename) == 0)
+			if (rules[i].r_filenum == rules[i + 1].r_filenum)
 				continue;
-			eat(rules[i].r_filename, rules[i].r_linenum);
+			eat(rules[i].r_filenum, rules[i].r_linenum);
 			warning(_("same rule name in multiple files"));
-			eat(rules[i + 1].r_filename, rules[i + 1].r_linenum);
+			eat(rules[i + 1].r_filenum, rules[i + 1].r_linenum);
 			warning(_("same rule name in multiple files"));
 			for (j = i + 2; j < nrules; ++j)
 			{
 				if (strcmp(rules[i].r_name,
 						   rules[j].r_name) != 0)
 					break;
-				if (strcmp(rules[i].r_filename,
-						   rules[j].r_filename) == 0)
+				if (rules[i].r_filenum == rules[j].r_filenum)
 					continue;
-				if (strcmp(rules[i + 1].r_filename,
-						   rules[j].r_filename) == 0)
+				if (rules[i + 1].r_filenum
+					== rules[j].r_filenum)
 					continue;
 				break;
 			}
@@ -1252,7 +2113,7 @@ associate(void)
 			/*
 			 * Maybe we have a local standard time offset.
 			 */
-			eat(zp->z_filename, zp->z_linenum);
+			eat(zp->z_filenum, zp->z_linenum);
 			zp->z_save = getsave(zp->z_rule, &zp->z_isdst);
 
 			/*
@@ -1267,21 +2128,58 @@ associate(void)
 		exit(EXIT_FAILURE);
 }
 
+/*
+ * Read a text line from FP into BUF, which is of size BUFSIZE.
+ * Terminate it with a NUL byte instead of a newline.
+ * Return true if successful, false if EOF.
+ * On error, report the error and exit.
+ */
+static bool
+inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
+{
+	ptrdiff_t	linelen = 0,
+				ch;
+
+	for (; (ch = getc(fp)) != '\n'; check_for_signal())
+	{
+		if (ch < 0)
+		{
+			if (ferror(fp))
+			{
+				error(_("input error"));
+				exit(EXIT_FAILURE);
+			}
+			if (linelen == 0)
+				return false;
+			error(_("unterminated line"));
+			exit(EXIT_FAILURE);
+		}
+		if (!ch)
+		{
+			error(_("NUL input byte"));
+			exit(EXIT_FAILURE);
+		}
+		buf[linelen++] = ch;
+		if (linelen == bufsize)
+		{
+			error(_("line too long"));
+			exit(EXIT_FAILURE);
+		}
+	}
+	buf[linelen] = '\0';
+	return true;
+}
+
 static void
-infile(const char *name)
+infile(int fnum, char const *name)
 {
 	FILE	   *fp;
-	char	  **fields;
-	char	   *cp;
 	const struct lookup *lp;
-	int			nfields;
 	bool		wantcont;
 	lineno_t	num;
-	char		buf[BUFSIZ];
 
 	if (strcmp(name, "-") == 0)
 	{
-		name = _("standard input");
 		fp = stdin;
 	}
 	else if ((fp = fopen(name, "r")) == NULL)
@@ -1295,30 +2193,22 @@ infile(const char *name)
 	wantcont = false;
 	for (num = 1;; ++num)
 	{
-		eat(name, num);
-		if (fgets(buf, sizeof buf, fp) != buf)
-			break;
-		cp = strchr(buf, '\n');
-		if (cp == NULL)
-		{
-			error(_("line too long"));
-			exit(EXIT_FAILURE);
-		}
-		*cp = '\0';
-		fields = getfields(buf);
-		nfields = 0;
-		while (fields[nfields] != NULL)
+		enum
 		{
-			static char nada;
-
-			if (strcmp(fields[nfields], "-") == 0)
-				fields[nfields] = &nada;
-			++nfields;
-		}
+			bufsize_bound
+		= (min(INT_MAX, INDEX_MAX) / FORMAT_LEN_GROWTH_BOUND)};
+		char		buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
+		int			nfields;
+		char	   *fields[MAX_FIELDS];
+
+		eat(fnum, num);
+		if (!inputline(fp, buf, sizeof buf))
+			break;
+		nfields = getfields(buf, fields,
+							sizeof fields / sizeof *fields);
 		if (nfields == 0)
 		{
-			if (name == leapsec && *buf == '#')
-				sscanf(buf, "#expires %" SCNdZIC, &comment_leapexpires);
+			/* nothing to do */
 		}
 		else if (wantcont)
 		{
@@ -1327,7 +2217,7 @@ infile(const char *name)
 		else
 		{
 			struct lookup const *line_codes
-			= name == leapsec ? leap_line_codes : zi_line_codes;
+			= fnum < 0 ? leap_line_codes : zi_line_codes;
 
 			lp = byword(fields[0], line_codes);
 			if (lp == NULL)
@@ -1354,16 +2244,13 @@ infile(const char *name)
 						inexpires(fields, nfields);
 						wantcont = false;
 						break;
-					default:	/* "cannot happen" */
-						fprintf(stderr,
-								_("%s: panic: Invalid l_value %d\n"),
-								progname, lp->l_value);
-						exit(EXIT_FAILURE);
+					default:
+						unreachable();
 				}
 		}
-		free(fields);
+		check_for_signal();
 	}
-	close_file(fp, NULL, filename);
+	close_file(fp, NULL, filename(fnum), NULL);
 	if (wantcont)
 		error(_("expected continuation line not found"));
 }
@@ -1408,20 +2295,20 @@ gethms(char const *string, char const *errstring)
 			ok = false;
 			break;
 		case 8:
-			ok = '0' <= xr && xr <= '9';
-			pg_fallthrough;
+			ok = is_digit(xr);
+			ATTRIBUTE_FALLTHROUGH;
 		case 7:
 			ok &= ssx == '.';
 			if (ok && noise)
 				warning(_("fractional seconds rejected by"
 						  " pre-2018 versions of zic"));
-			pg_fallthrough;
+			ATTRIBUTE_FALLTHROUGH;
 		case 5:
 			ok &= mmx == ':';
-			pg_fallthrough;
+			ATTRIBUTE_FALLTHROUGH;
 		case 3:
 			ok &= hhx == ':';
-			pg_fallthrough;
+			ATTRIBUTE_FALLTHROUGH;
 		case 1:
 			break;
 	}
@@ -1437,16 +2324,11 @@ gethms(char const *string, char const *errstring)
 		error("%s", errstring);
 		return 0;
 	}
-	if (ZIC_MAX / SECSPERHOUR < hh)
-	{
-		error(_("time overflow"));
-		return 0;
-	}
 	ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths;	/* Round to even.  */
 	if (noise && (hh > HOURSPERDAY ||
 				  (hh == HOURSPERDAY && (mm != 0 || ss != 0))))
 		warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
-	return oadd(sign * hh * SECSPERHOUR,
+	return oadd(omul(hh, sign * SECSPERHOUR),
 				sign * (mm * SECSPERMIN + ss));
 }
 
@@ -1455,7 +2337,7 @@ getsave(char *field, bool *isdst)
 {
 	int			dst = -1;
 	zic_t		save;
-	size_t		fieldlen = strlen(field);
+	ptrdiff_t	fieldlen = strlen(field);
 
 	if (fieldlen != 0)
 	{
@@ -1481,7 +2363,7 @@ getsave(char *field, bool *isdst)
 static void
 inrule(char **fields, int nfields)
 {
-	static struct rule r;
+	struct rule r;
 
 	if (nfields != RULE_FIELDS)
 	{
@@ -1512,13 +2394,15 @@ inrule(char **fields, int nfields)
 			error(_("Invalid rule name \"%s\""), fields[RF_NAME]);
 			return;
 	}
-	r.r_filename = filename;
+	r.r_filenum = filenum;
 	r.r_linenum = linenum;
 	r.r_save = getsave(fields[RF_SAVE], &r.r_isdst);
-	rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
-			fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
-	r.r_name = ecpyalloc(fields[RF_NAME]);
-	r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
+	if (!rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR],
+				 fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY],
+				 fields[RF_TOD]))
+		return;
+	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);
@@ -1537,15 +2421,13 @@ inzone(char **fields, int nfields)
 	}
 	if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0)
 	{
-		error(
-			  _("\"Zone %s\" line and -l option are mutually exclusive"),
+		error(_("\"Zone %s\" line and -l option are mutually exclusive"),
 			  tzdefault);
 		return false;
 	}
 	if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL)
 	{
-		error(
-			  _("\"Zone %s\" line and -p option are mutually exclusive"),
+		error(_("\"Zone %s\" line and -p option are mutually exclusive"),
 			  TZDEFRULES);
 		return false;
 	}
@@ -1556,7 +2438,7 @@ inzone(char **fields, int nfields)
 			error(_("duplicate zone name %s"
 					" (file \"%s\", line %" PRIdMAX ")"),
 				  fields[ZF_NAME],
-				  zones[i].z_filename,
+				  filename(zones[i].z_filenum),
 				  zones[i].z_linenum);
 			return false;
 		}
@@ -1579,7 +2461,8 @@ inzsub(char **fields, int nfields, bool iscont)
 {
 	char	   *cp;
 	char	   *cp1;
-	static struct zone z;
+	struct zone z;
+	int			format_len;
 	int			i_stdoff,
 				i_rule,
 				i_format;
@@ -1598,7 +2481,6 @@ inzsub(char **fields, int nfields, bool iscont)
 		i_untilmonth = ZFC_TILMONTH;
 		i_untilday = ZFC_TILDAY;
 		i_untiltime = ZFC_TILTIME;
-		z.z_name = NULL;
 	}
 	else if (!namecheck(fields[ZF_NAME]))
 		return false;
@@ -1611,12 +2493,12 @@ inzsub(char **fields, int nfields, bool iscont)
 		i_untilmonth = ZF_TILMONTH;
 		i_untilday = ZF_TILDAY;
 		i_untiltime = ZF_TILTIME;
-		z.z_name = ecpyalloc(fields[ZF_NAME]);
 	}
-	z.z_filename = filename;
+	z.z_filenum = filenum;
 	z.z_linenum = linenum;
 	z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset"));
-	if ((cp = strchr(fields[i_format], '%')) != NULL)
+	cp = strchr(fields[i_format], '%');
+	if (cp)
 	{
 		if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
 			|| strchr(fields[i_format], '/'))
@@ -1625,44 +2507,45 @@ inzsub(char **fields, int nfields, bool iscont)
 			return false;
 		}
 	}
-	z.z_rule = ecpyalloc(fields[i_rule]);
-	z.z_format = cp1 = ecpyalloc(fields[i_format]);
 	z.z_format_specifier = cp ? *cp : '\0';
-	if (z.z_format_specifier == 'z')
-	{
-		if (noise)
-			warning(_("format '%s' not handled by pre-2015 versions of zic"),
-					z.z_format);
-		cp1[cp - fields[i_format]] = 's';
-	}
-	if (max_format_len < strlen(z.z_format))
-		max_format_len = strlen(z.z_format);
+	format_len = strlen(fields[i_format]);
+	if (max_format_len < format_len)
+		max_format_len = format_len;
 	hasuntil = nfields > i_untilyear;
 	if (hasuntil)
 	{
-		z.z_untilrule.r_filename = filename;
+		z.z_untilrule.r_filenum = filenum;
 		z.z_untilrule.r_linenum = linenum;
-		rulesub(&z.z_untilrule,
-				fields[i_untilyear],
-				"only",
-				"",
-				(nfields > i_untilmonth) ?
-				fields[i_untilmonth] : "Jan",
-				(nfields > i_untilday) ? fields[i_untilday] : "1",
-				(nfields > i_untiltime) ? fields[i_untiltime] : "0");
+		if (!rulesub(
+					 &z.z_untilrule,
+					 fields[i_untilyear],
+					 "only",
+					 "",
+					 (nfields > i_untilmonth) ?
+					 fields[i_untilmonth] : "Jan",
+					 (nfields > i_untilday) ? fields[i_untilday] : "1",
+					 (nfields > i_untiltime) ? fields[i_untiltime] : "0"))
+			return false;
 		z.z_untiltime = rpytime(&z.z_untilrule,
 								z.z_untilrule.r_loyear);
 		if (iscont && nzones > 0 &&
-			z.z_untiltime > min_time &&
-			z.z_untiltime < max_time &&
-			zones[nzones - 1].z_untiltime > min_time &&
-			zones[nzones - 1].z_untiltime < max_time &&
 			zones[nzones - 1].z_untiltime >= z.z_untiltime)
 		{
-			error(_("Zone continuation line end time is not after end time of previous line"));
+			error(_("Zone continuation line end time is"
+					" not after end time of previous line"));
 			return false;
 		}
 	}
+	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)
+			warning(_("format '%s' not handled by pre-2015 versions of zic"),
+					fields[i_format]);
+	}
 	zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc);
 	zones[nzones++] = z;
 
@@ -1674,7 +2557,7 @@ inzsub(char **fields, int nfields, bool iscont)
 }
 
 static zic_t
-getleapdatetime(char **fields, int nfields, bool expire_line)
+getleapdatetime(char **fields, bool expire_line)
 {
 	const char *cp;
 	const struct lookup *lp;
@@ -1742,17 +2625,7 @@ getleapdatetime(char **fields, int nfields, bool expire_line)
 		return -1;
 	}
 	dayoff = oadd(dayoff, day - 1);
-	if (dayoff < min_time / SECSPERDAY)
-	{
-		error(_("time too small"));
-		return -1;
-	}
-	if (dayoff > max_time / SECSPERDAY)
-	{
-		error(_("time too large"));
-		return -1;
-	}
-	t = dayoff * SECSPERDAY;
+	t = omul(dayoff, SECSPERDAY);
 	tod = gethms(fields[LP_TIME], _("invalid time of day"));
 	t = tadd(t, tod);
 	if (t < 0)
@@ -1767,7 +2640,7 @@ inleap(char **fields, int nfields)
 		error(_("wrong number of fields on Leap line"));
 	else
 	{
-		zic_t		t = getleapdatetime(fields, nfields, false);
+		zic_t		t = getleapdatetime(fields, false);
 
 		if (0 <= t)
 		{
@@ -1800,7 +2673,7 @@ inexpires(char **fields, int nfields)
 	else if (0 <= leapexpires)
 		error(_("multiple Expires lines"));
 	else
-		leapexpires = getleapdatetime(fields, nfields, true);
+		leapexpires = getleapdatetime(fields, true);
 }
 
 static void
@@ -1820,15 +2693,15 @@ inlink(char **fields, int nfields)
 	}
 	if (!namecheck(fields[LF_LINKNAME]))
 		return;
-	l.l_filename = filename;
+	l.l_filenum = filenum;
 	l.l_linenum = linenum;
-	l.l_target = ecpyalloc(fields[LF_TARGET]);
-	l.l_linkname = ecpyalloc(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;
 }
 
-static void
+static bool
 rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 		const char *typep, const char *monthp, const char *dayp,
 		const char *timep)
@@ -1842,12 +2715,12 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	if ((lp = byword(monthp, mon_names)) == NULL)
 	{
 		error(_("invalid month name"));
-		return;
+		return false;
 	}
 	rp->r_month = lp->l_value;
 	rp->r_todisstd = false;
 	rp->r_todisut = false;
-	dp = ecpyalloc(timep);
+	dp = xstrdup(timep);
 	if (*dp != '\0')
 	{
 		ep = dp + strlen(dp) - 1;
@@ -1880,26 +2753,22 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	 */
 	cp = loyearp;
 	lp = byword(cp, begin_years);
-	rp->r_lowasnum = lp == NULL;
-	if (!rp->r_lowasnum)
+	if (lp)
 		switch (lp->l_value)
 		{
 			case YR_MINIMUM:
-				rp->r_loyear = ZIC_MIN;
+				warning(_("FROM year \"%s\" is obsolete;"
+						  " treated as %d"),
+						cp, YEAR_32BIT_MIN - 1);
+				rp->r_loyear = YEAR_32BIT_MIN - 1;
 				break;
-			case YR_MAXIMUM:
-				rp->r_loyear = ZIC_MAX;
-				break;
-			default:			/* "cannot happen" */
-				fprintf(stderr,
-						_("%s: panic: Invalid l_value %d\n"),
-						progname, lp->l_value);
-				exit(EXIT_FAILURE);
+			default:
+				unreachable();
 		}
 	else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_loyear, &xs) != 1)
 	{
 		error(_("invalid starting year"));
-		return;
+		return false;
 	}
 	cp = hiyearp;
 	lp = byword(cp, end_years);
@@ -1907,43 +2776,37 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	if (!rp->r_hiwasnum)
 		switch (lp->l_value)
 		{
-			case YR_MINIMUM:
-				rp->r_hiyear = ZIC_MIN;
-				break;
 			case YR_MAXIMUM:
 				rp->r_hiyear = ZIC_MAX;
 				break;
 			case YR_ONLY:
 				rp->r_hiyear = rp->r_loyear;
 				break;
-			default:			/* "cannot happen" */
-				fprintf(stderr,
-						_("%s: panic: Invalid l_value %d\n"),
-						progname, lp->l_value);
-				exit(EXIT_FAILURE);
+			default:
+				unreachable();
 		}
 	else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_hiyear, &xs) != 1)
 	{
 		error(_("invalid ending year"));
-		return;
+		return false;
 	}
 	if (rp->r_loyear > rp->r_hiyear)
 	{
 		error(_("starting year greater than ending year"));
-		return;
+		return false;
 	}
 	if (*typep != '\0')
 	{
 		error(_("year type \"%s\" is unsupported; use \"-\" instead"),
 			  typep);
-		return;
+		return false;
 	}
 
 	/*
 	 * Day work. Accept things such as: 1 lastSunday last-Sunday
 	 * (undocumented; warn about this) Sun<=20 Sun>=7
 	 */
-	dp = ecpyalloc(dayp);
+	dp = xstrdup(dayp);
 	if ((lp = byword(dp, lasts)) != NULL)
 	{
 		rp->r_dycode = DC_DOWLEQ;
@@ -1952,14 +2815,19 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	}
 	else
 	{
-		if ((ep = strchr(dp, '<')) != NULL)
+		ep = strchr(dp, '<');
+		if (ep)
 			rp->r_dycode = DC_DOWLEQ;
-		else if ((ep = strchr(dp, '>')) != NULL)
-			rp->r_dycode = DC_DOWGEQ;
 		else
 		{
-			ep = dp;
-			rp->r_dycode = DC_DOM;
+			ep = strchr(dp, '>');
+			if (ep)
+				rp->r_dycode = DC_DOWGEQ;
+			else
+			{
+				ep = dp;
+				rp->r_dycode = DC_DOM;
+			}
 		}
 		if (rp->r_dycode != DC_DOM)
 		{
@@ -1968,13 +2836,13 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 			{
 				error(_("invalid day of month"));
 				free(dp);
-				return;
+				return false;
 			}
 			if ((lp = byword(dp, wday_names)) == NULL)
 			{
 				error(_("invalid weekday name"));
 				free(dp);
-				return;
+				return false;
 			}
 			rp->r_wday = lp->l_value;
 		}
@@ -1984,36 +2852,37 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 		{
 			error(_("invalid day of month"));
 			free(dp);
-			return;
+			return false;
 		}
 	}
 	free(dp);
+	return true;
 }
 
 static void
-convert(const int_fast32_t val, char *const buf)
+convert(uint_fast32_t val, char *buf)
 {
 	int			i;
 	int			shift;
 	unsigned char *const b = (unsigned char *) buf;
 
 	for (i = 0, shift = 24; i < 4; ++i, shift -= 8)
-		b[i] = val >> shift;
+		b[i] = (val >> shift) & 0xff;
 }
 
 static void
-convert64(const zic_t val, char *const buf)
+convert64(uint_fast64_t val, char *buf)
 {
 	int			i;
 	int			shift;
 	unsigned char *const b = (unsigned char *) buf;
 
 	for (i = 0, shift = 56; i < 8; ++i, shift -= 8)
-		b[i] = val >> shift;
+		b[i] = (val >> shift) & 0xff;
 }
 
 static void
-puttzcode(const int_fast32_t val, FILE *const fp)
+puttzcode(zic_t val, FILE *fp)
 {
 	char		buf[4];
 
@@ -2038,10 +2907,12 @@ puttzcodepass(zic_t val, FILE *fp, int pass)
 static int
 atcomp(const void *avp, const void *bvp)
 {
-	const zic_t a = ((const struct attype *) avp)->at;
-	const zic_t b = ((const struct attype *) bvp)->at;
+	struct attype const *ap = avp,
+			   *bp = bvp;
+	zic_t		a = ap->at,
+				b = bp->at;
 
-	return (a < b) ? -1 : (a > b);
+	return a < b ? -1 : a > b;
 }
 
 struct timerange
@@ -2049,34 +2920,56 @@ struct timerange
 	int			defaulttype;
 	ptrdiff_t	base,
 				count;
-	int			leapbase,
+	ptrdiff_t	leapbase,
 				leapcount;
+	bool		leapexpiry;
 };
 
 static struct timerange
 limitrange(struct timerange r, zic_t lo, zic_t hi,
 		   zic_t const *ats, unsigned char const *types)
 {
+	/* Omit ordinary transitions < LO.  */
 	while (0 < r.count && ats[r.base] < lo)
 	{
 		r.defaulttype = types[r.base];
 		r.count--;
 		r.base++;
 	}
-	while (0 < r.leapcount && trans[r.leapbase] < lo)
+
+	/*
+	 * Omit as many initial leap seconds as possible, such that the first leap
+	 * second in the truncated list is <= LO, and is a positive leap second if
+	 * and only if it has a positive correction. This supports common TZif
+	 * readers that assume that the first leap second is positive if and only
+	 * if its correction is positive.
+	 */
+	while (1 < r.leapcount && leap[r.leapbase + 1].trans <= lo)
 	{
 		r.leapcount--;
 		r.leapbase++;
 	}
+	while (0 < r.leapbase
+		   && ((leap[r.leapbase - 1].corr < leap[r.leapbase].corr)
+			   != (0 < leap[r.leapbase].corr)))
+	{
+		r.leapcount++;
+		r.leapbase--;
+	}
+
 
-	if (hi < ZIC_MAX)
+	/* Omit ordinary and leap second transitions greater than HI + 1.  */
+	if (hi < max_time)
 	{
 		while (0 < r.count && hi + 1 < ats[r.base + r.count - 1])
 			r.count--;
-		while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1])
+		while (0 < r.leapcount && hi + 1 < leap[r.leapbase + r.leapcount - 1].trans)
 			r.leapcount--;
 	}
 
+	/* Determine whether to append an expiration to the leap second table.  */
+	r.leapexpiry = 0 <= leapexpires && leapexpires - 1 <= hi;
+
 	return r;
 }
 
@@ -2088,24 +2981,20 @@ writezone(const char *const name, const char *const string, char version,
 	ptrdiff_t	i,
 				j;
 	int			pass;
-	static const struct tzhead tzh0;
-	static struct tzhead tzh;
-	bool		dir_checked = false;
-	zic_t		one = 1;
-	zic_t		y2038_boundary = one << 31;
-	ptrdiff_t	nats = timecnt + WORK_AROUND_QTBUG_53071;
+	char	   *tempname = NULL;
+	char const *outname = name;
 
 	/*
 	 * Allocate the ATS and TYPES arrays via a single malloc, as this is a bit
-	 * faster.
+	 * faster.  Do not malloc(0) if !timecnt, as that might return NULL even
+	 * on success.
 	 */
-	zic_t	   *ats = emalloc(align_to(size_product(nats, sizeof *ats + 1),
-									   _Alignof(zic_t)));
-	void	   *typesptr = ats + nats;
+	zic_t	   *ats = xmalloc(align_to(size_product(timecnt + !timecnt,
+													sizeof *ats + 1),
+									   alignof(zic_t)));
+	void	   *typesptr = ats + timecnt;
 	unsigned char *types = typesptr;
-	struct timerange rangeall,
-				range32,
-				range64;
+	struct timerange rangeall = {0}, range32, range64;
 
 	/*
 	 * Sort.
@@ -2114,7 +3003,7 @@ writezone(const char *const name, const char *const string, char version,
 		qsort(attypes, timecnt, sizeof *attypes, atcomp);
 
 	/*
-	 * Optimize.
+	 * Optimize and skip unwanted transitions.
 	 */
 	{
 		ptrdiff_t	fromi,
@@ -2124,17 +3013,34 @@ writezone(const char *const name, const char *const string, char version,
 		fromi = 0;
 		for (; fromi < timecnt; ++fromi)
 		{
-			if (toi != 0
-				&& ((attypes[fromi].at
-					 + utoffs[attypes[toi - 1].type])
-					<= (attypes[toi - 1].at
-						+ utoffs[toi == 1 ? 0
-								 : attypes[toi - 2].type])))
+			if (toi != 0)
 			{
-				attypes[toi - 1].type =
-					attypes[fromi].type;
-				continue;
+				/*
+				 * Skip the previous transition if it is unwanted because its
+				 * local time is not earlier. The UT offset additions can't
+				 * overflow because of how the times were calculated.
+				 */
+				unsigned char type_2 =
+					toi == 1 ? 0 : attypes[toi - 2].type;
+
+				if ((attypes[fromi].at
+					 + utoffs[attypes[toi - 1].type])
+					<= attypes[toi - 1].at + utoffs[type_2])
+				{
+					if (attypes[fromi].type == type_2)
+						toi--;
+					else
+						attypes[toi - 1].type =
+							attypes[fromi].type;
+					continue;
+				}
 			}
+
+			/*
+			 * Use a transition if it is the first one, or if it cannot be
+			 * merged for other reasons, or if it transitions to different
+			 * timekeeping.
+			 */
 			if (toi == 0
 				|| attypes[fromi].dontmerge
 				|| (utoffs[attypes[toi - 1].type]
@@ -2148,15 +3054,21 @@ writezone(const char *const name, const char *const string, char version,
 		timecnt = toi;
 	}
 
-	if (noise && timecnt > 1200)
+	if (noise)
 	{
-		if (timecnt > TZ_MAX_TIMES)
-			warning(_("reference clients mishandle"
-					  " more than %d transition times"),
-					TZ_MAX_TIMES);
-		else
-			warning(_("pre-2014 clients may mishandle"
-					  " more than 1200 transition times"));
+		if (1200 < timecnt)
+		{
+			if (TZ_MAX_TIMES < timecnt)
+				warning(_("reference clients mishandle"
+						  " more than %d transition times"),
+						TZ_MAX_TIMES);
+			else
+				warning(_("pre-2014 clients may mishandle"
+						  " more than 1200 transition times"));
+		}
+		if (TZ_MAX_LEAPS < leapcnt)
+			warning(_("reference clients mishandle more than %d leap seconds"),
+					TZ_MAX_LEAPS);
 	}
 
 	/*
@@ -2175,81 +3087,73 @@ writezone(const char *const name, const char *const string, char version,
 	{
 		j = leapcnt;
 		while (--j >= 0)
-			if (ats[i] > trans[j] - corr[j])
+			if (leap[j].trans - leap[j].corr < ats[i])
 			{
-				ats[i] = tadd(ats[i], corr[j]);
+				ats[i] = tadd(ats[i], leap[j].corr);
 				break;
 			}
 	}
 
-	/*
-	 * Work around QTBUG-53071 for timestamps less than y2038_boundary - 1, by
-	 * inserting a no-op transition at time y2038_boundary - 1. This works
-	 * only for timestamps before the boundary, which should be good enough in
-	 * practice as QTBUG-53071 should be long-dead by 2038.  Do this after
-	 * correcting for leap seconds, as the idea is to insert a transition just
-	 * before 32-bit pg_time_t rolls around, and this occurs at a slightly
-	 * different moment if transitions are leap-second corrected.
-	 */
-	if (WORK_AROUND_QTBUG_53071 && timecnt != 0 && want_bloat()
-		&& ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<'))
-	{
-		ats[timecnt] = y2038_boundary - 1;
-		types[timecnt] = types[timecnt - 1];
-		timecnt++;
-	}
-
 	rangeall.defaulttype = defaulttype;
-	rangeall.base = rangeall.leapbase = 0;
 	rangeall.count = timecnt;
 	rangeall.leapcount = leapcnt;
-	range64 = limitrange(rangeall, lo_time, hi_time, ats, types);
-	range32 = limitrange(range64, INT32_MIN, INT32_MAX, ats, types);
+	range64 = limitrange(rangeall, lo_time,
+						 max(hi_time,
+							 redundant_time - (ZIC_MIN < redundant_time)),
+						 ats, types);
+	range32 = limitrange(range64, ZIC32_MIN, ZIC32_MAX, ats, types);
 
 	/*
-	 * Remove old file, if any, to snap links.
+	 * TZif version 4 is needed if a no-op transition is appended to indicate
+	 * the expiration of the leap second table, or if the first leap second
+	 * transition is not to a +1 or -1 correction.
 	 */
-	if (remove(name) == 0)
-		dir_checked = true;
-	else if (errno != ENOENT)
-	{
-		const char *e = strerror(errno);
-
-		fprintf(stderr, _("%s: Cannot remove %s/%s: %s\n"),
-				progname, directory, name, e);
-		exit(EXIT_FAILURE);
-	}
-	fp = fopen(name, "wb");
-	if (!fp)
+	for (pass = 1; pass <= 2; pass++)
 	{
-		int			fopen_errno = errno;
+		struct timerange const *r = pass == 1 ? &range32 : &range64;
 
-		if (fopen_errno == ENOENT && !dir_checked)
+		if (pass == 1 && !want_bloat())
+			continue;
+		if (r->leapexpiry)
 		{
-			mkdirs(name, true);
-			fp = fopen(name, "wb");
-			fopen_errno = errno;
+			if (noise)
+				warning(_("%s: pre-2021b clients may mishandle"
+						  " leap second expiry"),
+						name);
+			version = '4';
 		}
-		if (!fp)
+		if (0 < r->leapcount
+			&& leap[r->leapbase].corr != 1 && leap[r->leapbase].corr != -1)
 		{
-			fprintf(stderr, _("%s: Cannot create %s/%s: %s\n"),
-					progname, directory, name, strerror(fopen_errno));
-			exit(EXIT_FAILURE);
+			if (noise)
+				warning(_("%s: pre-2021b clients may mishandle"
+						  " leap second table truncation"),
+						name);
+			version = '4';
 		}
+		if (version == '4')
+			break;
 	}
+
+	fp = open_outfile(&outname, &tempname);
+
 	for (pass = 1; pass <= 2; ++pass)
 	{
 		ptrdiff_t	thistimei,
 					thistimecnt,
 					thistimelim;
-		int			thisleapi,
+		ptrdiff_t	thisleapi,
 					thisleapcnt,
 					thisleaplim;
-		int			currenttype,
+		struct tzhead tzh;
+		int			pretranstype = -1,
 					thisdefaulttype;
 		bool		locut,
-					hicut;
-		zic_t		lo;
+					hicut,
+					thisleapexpiry;
+		zic_t		lo,
+					thismin,
+					thismax;
 		int			old0;
 		char		omittype[TZ_MAX_TYPES];
 		int			typemap[TZ_MAX_TYPES];
@@ -2263,28 +3167,15 @@ writezone(const char *const name, const char *const string, char version,
 
 		if (pass == 1)
 		{
-			/*
-			 * Arguably the default time type in the 32-bit data should be
-			 * range32.defaulttype, which is suited for timestamps just before
-			 * INT32_MIN.  However, zic traditionally used the time type of
-			 * the indefinite past instead.  Internet RFC 8532 says readers
-			 * should ignore 32-bit data, so this discrepancy matters only to
-			 * obsolete readers where the traditional type might be more
-			 * appropriate even if it's "wrong".  So, use the historical zic
-			 * value, unless -r specifies a low cutoff that excludes some
-			 * 32-bit timestamps.
-			 */
-			thisdefaulttype = (lo_time <= INT32_MIN
-							   ? range64.defaulttype
-							   : range32.defaulttype);
-
+			thisdefaulttype = range32.defaulttype;
 			thistimei = range32.base;
 			thistimecnt = range32.count;
 			toomanytimes = thistimecnt >> 31 >> 1 != 0;
 			thisleapi = range32.leapbase;
 			thisleapcnt = range32.leapcount;
-			locut = INT32_MIN < lo_time;
-			hicut = hi_time < INT32_MAX;
+			thisleapexpiry = range32.leapexpiry;
+			thismin = ZIC32_MIN;
+			thismax = ZIC32_MAX;
 		}
 		else
 		{
@@ -2294,40 +3185,52 @@ writezone(const char *const name, const char *const string, char version,
 			toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
 			thisleapi = range64.leapbase;
 			thisleapcnt = range64.leapcount;
-			locut = min_time < lo_time;
-			hicut = hi_time < max_time;
+			thisleapexpiry = range64.leapexpiry;
+			thismin = min_time;
+			thismax = max_time;
 		}
 		if (toomanytimes)
 			error(_("too many transition times"));
 
+		locut = thismin < lo_time && lo_time <= thismax;
+		hicut = thismin <= hi_time && hi_time < thismax;
+		thistimelim = thistimei + thistimecnt;
+		memset(omittype, true, typecnt);
+
 		/*
-		 * Keep the last too-low transition if no transition is exactly at LO.
-		 * The kept transition will be output as a LO "transition"; see
-		 * "Output a LO_TIME transition" below.  This is needed when the
-		 * output is truncated at the start, and is also useful when catering
-		 * to buggy 32-bit clients that do not use time type 0 for timestamps
-		 * before the first transition.
+		 * Determine whether to output a transition before the first
+		 * transition in range.  This is needed when the output is truncated
+		 * at the start, and is also useful when catering to buggy 32-bit
+		 * clients that do not use time type 0 for timestamps before the first
+		 * transition.
 		 */
-		if (0 < thistimei && ats[thistimei] != lo_time)
+		if ((locut || (pass == 1 && thistimei))
+			&& !(thistimecnt && ats[thistimei] == lo_time))
 		{
-			thistimei--;
-			thistimecnt++;
-			locut = false;
+			pretranstype = thisdefaulttype;
+			omittype[pretranstype] = false;
 		}
 
-		thistimelim = thistimei + thistimecnt;
-		thisleaplim = thisleapi + thisleapcnt;
-		if (thistimecnt != 0)
-		{
-			if (ats[thistimei] == lo_time)
-				locut = false;
-			if (hi_time < ZIC_MAX && ats[thistimelim - 1] == hi_time + 1)
-				hicut = false;
-		}
-		memset(omittype, true, typecnt);
+		/*
+		 * Arguably the default time type in the 32-bit data should be
+		 * range32.defaulttype, which is suited for timestamps just before
+		 * ZIC32_MIN.  However, zic traditionally used the time type of the
+		 * indefinite past instead.  Internet RFC 8532 says readers should
+		 * ignore 32-bit data, so this discrepancy matters only to obsolete
+		 * readers where the traditional type might be more appropriate even
+		 * if it's "wrong".  So, use the historical zic value, unless -r
+		 * specifies a low cutoff that excludes some 32-bit timestamps.
+		 */
+		if (pass == 1 && lo_time <= thismin)
+			thisdefaulttype = range64.defaulttype;
+
+		if (locut)
+			thisdefaulttype = unspecifiedtype;
 		omittype[thisdefaulttype] = false;
 		for (i = thistimei; i < thistimelim; i++)
 			omittype[types[i]] = false;
+		if (hicut)
+			omittype[unspecifiedtype] = false;
 
 		/*
 		 * Reorder types to make THISDEFAULTTYPE type 0. Use TYPEMAP to swap
@@ -2353,7 +3256,14 @@ writezone(const char *const name, const char *const string, char version,
 						type;
 
 			hidst = histd = mrudst = mrustd = -1;
-			for (i = thistimei; i < thistimelim; ++i)
+			if (0 <= pretranstype)
+			{
+				if (isdsts[pretranstype])
+					mrudst = pretranstype;
+				else
+					mrustd = pretranstype;
+			}
+			for (i = thistimei; i < thistimelim; i++)
 				if (isdsts[types[i]])
 					mrudst = types[i];
 				else
@@ -2405,47 +3315,46 @@ writezone(const char *const name, const char *const string, char version,
 						: i == thisdefaulttype ? old0 : i]
 					= thistypecnt++;
 
-		for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
-			indmap[i] = -1;
 		thischarcnt = stdcnt = utcnt = 0;
 		for (i = old0; i < typecnt; i++)
 		{
-			char	   *thisabbr;
-
 			if (omittype[i])
 				continue;
 			if (ttisstds[i])
 				stdcnt = thistypecnt;
 			if (ttisuts[i])
 				utcnt = thistypecnt;
-			if (indmap[desigidx[i]] >= 0)
-				continue;
-			thisabbr = &chars[desigidx[i]];
-			for (j = 0; j < thischarcnt; ++j)
-				if (strcmp(&thischars[j], thisabbr) == 0)
-					break;
-			if (j == thischarcnt)
-			{
-				strcpy(&thischars[thischarcnt], thisabbr);
-				thischarcnt += strlen(thisabbr) + 1;
-			}
-			indmap[desigidx[i]] = j;
+			addabbr(thischars, &thischarcnt, &chars[desigidx[i]]);
 		}
+
+		/*
+		 * Now that all abbrevs have been added to THISCHARS, it is safe to
+		 * set INDMAP without worrying about whether the abbrevs might move
+		 * later.
+		 */
+		for (i = 0; i < TZ_MAX_CHARS; i++)
+			indmap[i] = -1;
+		for (i = old0; i < typecnt; i++)
+			if (!omittype[i] && indmap[desigidx[i]] < 0)
+				indmap[desigidx[i]] = addabbr(thischars, &thischarcnt,
+											  &chars[desigidx[i]]);
+
 		if (pass == 1 && !want_bloat())
 		{
-			utcnt = stdcnt = thisleapcnt = 0;
-			thistimecnt = -(locut + hicut);
+			hicut = thisleapexpiry = false;
+			pretranstype = -1;
+			thistimecnt = thisleapcnt = 0;
 			thistypecnt = thischarcnt = 1;
-			thistimelim = thistimei;
 		}
 #define DO(field)	fwrite(tzh.field, sizeof tzh.field, 1, fp)
-		tzh = tzh0;
+		memset(&tzh, 0, sizeof tzh);
 		memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
 		tzh.tzh_version[0] = version;
 		convert(utcnt, tzh.tzh_ttisutcnt);
 		convert(stdcnt, tzh.tzh_ttisstdcnt);
-		convert(thisleapcnt, tzh.tzh_leapcnt);
-		convert(locut + thistimecnt + hicut, tzh.tzh_timecnt);
+		convert(thisleapcnt + thisleapexpiry, tzh.tzh_leapcnt);
+		convert((0 <= pretranstype) + thistimecnt + hicut,
+				tzh.tzh_timecnt);
 		convert(thistypecnt, tzh.tzh_typecnt);
 		convert(thischarcnt, tzh.tzh_charcnt);
 		DO(tzh_magic);
@@ -2498,32 +3407,31 @@ writezone(const char *const name, const char *const string, char version,
 			}
 		}
 
+		if (pass == 2 && noise && 50 < thischarcnt)
+			warning(_("%s: pre-2026 reference clients mishandle"
+					  " more than 50 bytes of abbreviations"),
+					name);
+
 		/*
 		 * Output a LO_TIME transition if needed; see limitrange. But do not
 		 * go below the minimum representable value for this pass.
 		 */
-		lo = pass == 1 && lo_time < INT32_MIN ? INT32_MIN : lo_time;
+		lo = pass == 1 && lo_time < ZIC32_MIN ? ZIC32_MIN : lo_time;
 
-		if (locut)
+		if (0 <= pretranstype)
 			puttzcodepass(lo, fp, pass);
 		for (i = thistimei; i < thistimelim; ++i)
 		{
-			zic_t		at = ats[i] < lo ? lo : ats[i];
-
-			puttzcodepass(at, fp, pass);
+			puttzcodepass(ats[i], fp, pass);
 		}
 		if (hicut)
 			puttzcodepass(hi_time + 1, fp, pass);
-		currenttype = 0;
-		if (locut)
-			putc(currenttype, fp);
-		for (i = thistimei; i < thistimelim; ++i)
-		{
-			currenttype = typemap[types[i]];
-			putc(currenttype, fp);
-		}
+		if (0 <= pretranstype)
+			putc(typemap[pretranstype], fp);
+		for (i = thistimei; i < thistimelim; i++)
+			putc(typemap[types[i]], fp);
 		if (hicut)
-			putc(currenttype, fp);
+			putc(typemap[unspecifiedtype], fp);
 
 		for (i = old0; i < typecnt; i++)
 		{
@@ -2540,13 +3448,14 @@ writezone(const char *const name, const char *const string, char version,
 		if (thischarcnt != 0)
 			fwrite(thischars, sizeof thischars[0],
 				   thischarcnt, fp);
+		thisleaplim = thisleapi + thisleapcnt;
 		for (i = thisleapi; i < thisleaplim; ++i)
 		{
 			zic_t		todo;
 
-			if (roll[i])
+			if (leap[i].roll)
 			{
-				if (timecnt == 0 || trans[i] < ats[0])
+				if (timecnt == 0 || leap[i].trans < ats[0])
 				{
 					j = 0;
 					while (isdsts[j])
@@ -2560,16 +3469,27 @@ writezone(const char *const name, const char *const string, char version,
 				{
 					j = 1;
 					while (j < timecnt &&
-						   trans[i] >= ats[j])
+						   ats[j] <= leap[i].trans)
 						++j;
 					j = types[j - 1];
 				}
-				todo = tadd(trans[i], -utoffs[j]);
+				todo = tadd(leap[i].trans, -utoffs[j]);
 			}
 			else
-				todo = trans[i];
+				todo = leap[i].trans;
 			puttzcodepass(todo, fp, pass);
-			puttzcode(corr[i], fp);
+			puttzcode(leap[i].corr, fp);
+		}
+		if (thisleapexpiry)
+		{
+			/*
+			 * Append a no-op leap correction indicating when the leap second
+			 * table expires.  Although this does not conform to 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);
+			puttzcode(thisleaplim ? leap[thisleaplim - 1].corr : 0, fp);
 		}
 		if (stdcnt != 0)
 			for (i = old0; i < typecnt; i++)
@@ -2581,7 +3501,8 @@ writezone(const char *const name, const char *const string, char version,
 					putc(ttisuts[i], fp);
 	}
 	fprintf(fp, "\n%s\n", string);
-	close_file(fp, directory, name);
+	close_file(fp, directory, name, tempname);
+	rename_dest(tempname, name);
 	free(ats);
 }
 
@@ -2629,16 +3550,17 @@ abbroffset(char *buf, zic_t offset)
 	}
 }
 
-static size_t
+static char const disable_percent_s[] = "";
+
+static ptrdiff_t
 doabbr(char *abbr, struct zone const *zp, char const *letters,
 	   bool isdst, zic_t save, bool doquotes)
 {
 	char	   *cp;
-	char const *slashp;
-	size_t		len;
+	ptrdiff_t	len;
 	char const *format = zp->z_format;
+	char const *slashp = strchr(format, '/');
 
-	slashp = strchr(format, '/');
 	if (slashp == NULL)
 	{
 		char		letterbuf[PERCENT_Z_LEN_BOUND + 1];
@@ -2647,16 +3569,17 @@ doabbr(char *abbr, struct zone const *zp, char const *letters,
 			letters = abbroffset(letterbuf, zp->z_stdoff + save);
 		else if (!letters)
 			letters = "%s";
+		else if (letters == disable_percent_s)
+			return 0;
 		sprintf(abbr, format, letters);
 	}
 	else if (isdst)
-	{
 		strcpy(abbr, slashp + 1);
-	}
 	else
 	{
-		memcpy(abbr, format, slashp - format);
-		abbr[slashp - format] = '\0';
+		char	   *abbrend = mempcpy(abbr, format, slashp - format);
+
+		*abbrend = '\0';
 	}
 	len = strlen(abbr);
 	if (!doquotes)
@@ -2699,12 +3622,12 @@ stringoffset(char *result, zic_t offset)
 	offset /= SECSPERMIN;
 	minutes = offset % MINSPERHOUR;
 	offset /= MINSPERHOUR;
-	hours = offset;
-	if (hours >= HOURSPERDAY * DAYSPERWEEK)
+	if (offset >= HOURSPERDAY * DAYSPERWEEK)
 	{
 		result[0] = '\0';
 		return 0;
 	}
+	hours = offset;
 	len += sprintf(result + len, "%d", hours);
 	if (minutes != 0 || seconds != 0)
 	{
@@ -2805,11 +3728,19 @@ rule_cmp(struct rule const *a, struct rule const *b)
 		return 1;
 	if (a->r_hiyear != b->r_hiyear)
 		return a->r_hiyear < b->r_hiyear ? -1 : 1;
+	if (a->r_hiyear == ZIC_MAX)
+		return 0;
 	if (a->r_month - b->r_month != 0)
 		return a->r_month - b->r_month;
 	return a->r_dayofmonth - b->r_dayofmonth;
 }
 
+/*
+ * Store into RESULT a proleptic TZ string that represent the future
+ * predictions for the zone ZPFIRST with ZONECOUNT entries.  Return a
+ * compatibility indicator (a TZDB release year) if successful, a
+ * negative integer if no such TZ string exists.
+ */
 static int
 stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 {
@@ -2818,90 +3749,96 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	struct rule *stdrp;
 	struct rule *dstrp;
 	ptrdiff_t	i;
-	const char *abbrvar;
 	int			compat = 0;
 	int			c;
-	size_t		len;
 	int			offsetlen;
 	struct rule stdr,
 				dstr;
+	ptrdiff_t	len;
+	int			dstcmp;
+	struct rule *lastrp[2] = {NULL, NULL};
+	struct zone zstr[2];
+	struct zone const *stdzp;
+	struct zone const *dstzp;
 
 	result[0] = '\0';
 
 	/*
-	 * Internet RFC 8536 section 5.1 says to use an empty TZ string if future
+	 * 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;
 
 	zp = zpfirst + zonecount - 1;
-	stdrp = dstrp = NULL;
 	for (i = 0; i < zp->z_nrules; ++i)
 	{
+		struct rule **last;
+		int			cmp;
+
 		rp = &zp->z_rules[i];
-		if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX)
-			continue;
-		if (!rp->r_isdst)
-		{
-			if (stdrp == NULL)
-				stdrp = rp;
-			else
-				return -1;
-		}
-		else
-		{
-			if (dstrp == NULL)
-				dstrp = rp;
-			else
-				return -1;
-		}
+		last = &lastrp[rp->r_isdst];
+		cmp = rule_cmp(*last, rp);
+		if (cmp < 0)
+			*last = rp;
+		else if (cmp == 0)
+			return -1;
+	}
+	stdrp = lastrp[false];
+	dstrp = lastrp[true];
+	dstcmp = zp->z_nrules ? rule_cmp(dstrp, stdrp) : zp->z_isdst ? 1 : -1;
+	stdzp = dstzp = zp;
+
+	if (dstcmp < 0)
+	{
+		/* Standard time all year.  */
+		dstrp = NULL;
 	}
-	if (stdrp == NULL && dstrp == NULL)
+	else if (0 < dstcmp)
 	{
 		/*
-		 * There are no rules running through "max". Find the latest std rule
-		 * in stdabbrrp and latest rule of any type in stdrp.
+		 * DST all year.  Use an abbreviation like "XXX3EDT4,0/0,J365/23" for
+		 * EDT (-04) all year.
 		 */
-		struct rule *stdabbrrp = NULL;
-
-		for (i = 0; i < zp->z_nrules; ++i)
-		{
-			rp = &zp->z_rules[i];
-			if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0)
-				stdabbrrp = rp;
-			if (rule_cmp(stdrp, rp) < 0)
-				stdrp = rp;
-		}
-		if (stdrp != NULL && stdrp->r_isdst)
-		{
-			/* Perpetual DST.  */
-			dstr.r_month = TM_JANUARY;
-			dstr.r_dycode = DC_DOM;
-			dstr.r_dayofmonth = 1;
-			dstr.r_tod = 0;
-			dstr.r_todisstd = dstr.r_todisut = false;
-			dstr.r_isdst = stdrp->r_isdst;
-			dstr.r_save = stdrp->r_save;
-			dstr.r_abbrvar = stdrp->r_abbrvar;
-			stdr.r_month = TM_DECEMBER;
-			stdr.r_dycode = DC_DOM;
-			stdr.r_dayofmonth = 31;
-			stdr.r_tod = SECSPERDAY + stdrp->r_save;
-			stdr.r_todisstd = stdr.r_todisut = false;
-			stdr.r_isdst = false;
-			stdr.r_save = 0;
-			stdr.r_abbrvar
-				= (stdabbrrp ? stdabbrrp->r_abbrvar : "");
-			dstrp = &dstr;
-			stdrp = &stdr;
-		}
-	}
-	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst))
-		return -1;
-	abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
-	len = doabbr(result, zp, abbrvar, false, 0, true);
-	offsetlen = stringoffset(result + len, -zp->z_stdoff);
+		zic_t		save = dstrp ? dstrp->r_save : zp->z_save;
+
+		if (0 <= save)
+		{
+			/*
+			 * Positive DST, the typical case for all-year DST. Fake a
+			 * timezone with negative DST.
+			 */
+			stdzp = &zstr[0];
+			dstzp = &zstr[1];
+			zstr[0].z_stdoff = zp->z_stdoff + 2 * save;
+			zstr[0].z_format = "XXX";	/* Any 3 letters will do.  */
+			zstr[0].z_format_specifier = 0;
+			zstr[1].z_stdoff = zstr[0].z_stdoff;
+			zstr[1].z_format = zp->z_format;
+			zstr[1].z_format_specifier = zp->z_format_specifier;
+		}
+		dstr.r_month = TM_JANUARY;
+		dstr.r_dycode = DC_DOM;
+		dstr.r_dayofmonth = 1;
+		dstr.r_tod = 0;
+		dstr.r_todisstd = dstr.r_todisut = false;
+		dstr.r_isdst = true;
+		dstr.r_save = save < 0 ? save : -save;
+		dstr.r_abbrvar = dstrp ? dstrp->r_abbrvar : NULL;
+		stdr.r_month = TM_DECEMBER;
+		stdr.r_dycode = DC_DOM;
+		stdr.r_dayofmonth = 31;
+		stdr.r_tod = SECSPERDAY + dstr.r_save;
+		stdr.r_todisstd = stdr.r_todisut = false;
+		stdr.r_isdst = false;
+		stdr.r_save = 0;
+		stdr.r_abbrvar = save < 0 && stdrp ? stdrp->r_abbrvar : NULL;
+		dstrp = &dstr;
+		stdrp = &stdr;
+	}
+	len = doabbr(result, stdzp, stdrp ? stdrp->r_abbrvar : NULL,
+				 false, 0, true);
+	offsetlen = stringoffset(result + len, -stdzp->z_stdoff);
 	if (!offsetlen)
 	{
 		result[0] = '\0';
@@ -2910,12 +3847,12 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	len += offsetlen;
 	if (dstrp == NULL)
 		return compat;
-	len += doabbr(result + len, zp, dstrp->r_abbrvar,
+	len += doabbr(result + len, dstzp, dstrp->r_abbrvar,
 				  dstrp->r_isdst, dstrp->r_save, true);
 	if (dstrp->r_save != SECSPERMIN * MINSPERHOUR)
 	{
 		offsetlen = stringoffset(result + len,
-								 -(zp->z_stdoff + dstrp->r_save));
+								 -(dstzp->z_stdoff + dstrp->r_save));
 		if (!offsetlen)
 		{
 			result[0] = '\0';
@@ -2924,7 +3861,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		len += offsetlen;
 	}
 	result[len++] = ',';
-	c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff);
+	c = stringrule(result + len, dstrp, dstrp->r_save, stdzp->z_stdoff);
 	if (c < 0)
 	{
 		result[0] = '\0';
@@ -2934,7 +3871,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		compat = c;
 	len += strlen(result + len);
 	result[len++] = ',';
-	c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff);
+	c = stringrule(result + len, stdrp, dstrp->r_save, stdzp->z_stdoff);
 	if (c < 0)
 	{
 		result[0] = '\0';
@@ -2948,41 +3885,38 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 static void
 outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 {
-	const struct zone *zp;
-	struct rule *rp;
 	ptrdiff_t	i,
 				j;
-	bool		usestart,
-				useuntil;
 	zic_t		starttime,
 				untiltime;
-	zic_t		stdoff;
-	zic_t		save;
-	zic_t		year;
-	zic_t		startoff;
 	bool		startttisstd;
 	bool		startttisut;
-	int			type;
 	char	   *startbuf;
 	char	   *ab;
 	char	   *envvar;
 	int			max_abbr_len;
 	int			max_envvar_len;
-	bool		prodstic;		/* all rules are min to max */
 	int			compat;
 	bool		do_extend;
 	char		version;
-	ptrdiff_t	lastatmax = -1;
-	zic_t		one = 1;
-	zic_t		y2038_boundary = one << 31;
+	zic_t		nonTZlimtime = ZIC_MIN;
+	int			nonTZlimtype = -1;
 	zic_t		max_year0;
 	int			defaulttype = -1;
+	int			max_stringoffset_len = sizeof "-167:59:59" - 1;
+	int			max_comma_stringrule_len = (sizeof ",M12.5.6/" - 1
+											+ max_stringoffset_len);
 
+	check_for_signal();
+
+	/* This cannot overflow; see FORMAT_LEN_GROWTH_BOUND.  */
 	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);
+	max_envvar_len = 2 * (max_abbr_len + max_stringoffset_len
+						  + max_comma_stringrule_len);
+
+	startbuf = xmalloc(max_abbr_len + 1);
+	ab = xmalloc(max_abbr_len + 1);
+	envvar = xmalloc(max_envvar_len + 1);
 	INITIALIZE(untiltime);
 	INITIALIZE(starttime);
 
@@ -2992,7 +3926,6 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	timecnt = 0;
 	typecnt = 0;
 	charcnt = 0;
-	prodstic = zonecount == 1;
 
 	/*
 	 * Thanks to Earl Chew for noting the need to unconditionally initialize
@@ -3008,18 +3941,17 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	}
 	for (i = 0; i < zonecount; ++i)
 	{
-		zp = &zpfirst[i];
+		struct zone const *zp = &zpfirst[i];
+
 		if (i < zonecount - 1)
 			updateminmax(zp->z_untilrule.r_loyear);
 		for (j = 0; j < zp->z_nrules; ++j)
 		{
-			rp = &zp->z_rules[j];
-			if (rp->r_lowasnum)
-				updateminmax(rp->r_loyear);
+			struct rule *rp = &zp->z_rules[j];
+
+			updateminmax(rp->r_loyear);
 			if (rp->r_hiwasnum)
 				updateminmax(rp->r_hiyear);
-			if (rp->r_lowasnum || rp->r_hiwasnum)
-				prodstic = false;
 		}
 	}
 
@@ -3027,13 +3959,13 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	 * Generate lots of data if a rule can't cover all future times.
 	 */
 	compat = stringzone(envvar, zpfirst, zonecount);
-	version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION;
+	version = compat < 2013 ? '2' : '3';
 	do_extend = compat < 0;
 	if (noise)
 	{
 		if (!*envvar)
 			warning("%s %s",
-					_("no POSIX environment variable for zone"),
+					_("no proleptic TZ string for zone"),
 					zpfirst->z_name);
 		else if (compat != 0)
 		{
@@ -3048,22 +3980,6 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	}
 	if (do_extend)
 	{
-		/*
-		 * Search through a couple of extra years past the obvious 400, to
-		 * avoid edge cases.  For example, suppose a non-POSIX rule applies
-		 * from 2012 onwards and has transitions in March and September, plus
-		 * some one-off transitions in November 2013.  If zic looked only at
-		 * the last 400 years, it would set max_year=2413, with the intent
-		 * that the 400 years 2014 through 2413 will be repeated.  The last
-		 * transition listed in the tzfile would be in 2413-09, less than 400
-		 * years after the last one-off transition in 2013-11.  Two years
-		 * might be overkill, but with the kind of edge cases available we're
-		 * not sure that one year would suffice.
-		 */
-		enum
-		{
-		years_of_observations = YEARSPERREPEAT + 2};
-
 		if (min_year >= ZIC_MIN + years_of_observations)
 			min_year -= years_of_observations;
 		else
@@ -3072,18 +3988,9 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 			max_year += years_of_observations;
 		else
 			max_year = ZIC_MAX;
-
-		/*
-		 * Regardless of any of the above, for a "proDSTic" zone which
-		 * specifies that its rules always have and always will be in effect,
-		 * we only need one cycle to define the zone.
-		 */
-		if (prodstic)
-		{
-			min_year = 1900;
-			max_year = min_year + years_of_observations;
-		}
 	}
+	max_year = max(max_year, (redundant_time / (SECSPERDAY * DAYSPERNYEAR)
+							  + EPOCH_YEAR + 1));
 	max_year0 = max_year;
 	if (want_bloat())
 	{
@@ -3091,31 +3998,35 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 		 * For the benefit of older systems, generate data from 1900 through
 		 * 2038.
 		 */
-		if (min_year > 1900)
-			min_year = 1900;
-		if (max_year < 2038)
-			max_year = 2038;
+		if (min_year > YEAR_32BIT_MIN - 1)
+			min_year = YEAR_32BIT_MIN - 1;
+		if (max_year < YEAR_32BIT_MAX)
+			max_year = YEAR_32BIT_MAX;
 	}
 
+	if (min_time < lo_time || hi_time < max_time)
+		unspecifiedtype = addtype(0, "-00", false, false, false);
+
 	for (i = 0; i < zonecount; ++i)
 	{
-		struct rule *prevrp = NULL;
-
 		/*
 		 * A guess that may well be corrected later.
 		 */
-		save = 0;
-		zp = &zpfirst[i];
-		usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
-		useuntil = i < (zonecount - 1);
+		zic_t		save = 0;
+		struct zone const *zp = &zpfirst[i];
+		bool		usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
+		bool		useuntil = i < (zonecount - 1);
+		zic_t		stdoff = zp->z_stdoff;
+		zic_t		startoff = stdoff;
+
 		if (useuntil && zp->z_untiltime <= min_time)
 			continue;
-		stdoff = zp->z_stdoff;
-		eat(zp->z_filename, zp->z_linenum);
+		eat(zp->z_filenum, zp->z_linenum);
 		*startbuf = '\0';
-		startoff = zp->z_stdoff;
 		if (zp->z_nrules == 0)
 		{
+			int			type;
+
 			save = zp->z_save;
 			doabbr(startbuf, zp, NULL, zp->z_isdst, save, false);
 			type = addtype(oadd(zp->z_stdoff, save),
@@ -3124,12 +4035,20 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 			if (usestart)
 			{
 				addtt(starttime, type);
+				if (nonTZlimtime < starttime)
+				{
+					nonTZlimtime = starttime;
+					nonTZlimtype = type;
+				}
 				usestart = false;
 			}
 			else
 				defaulttype = type;
 		}
 		else
+		{
+			zic_t		year;
+
 			for (year = min_year; year <= max_year; ++year)
 			{
 				if (useuntil && year > zp->z_untilrule.r_hiyear)
@@ -3142,9 +4061,12 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				 */
 				for (j = 0; j < zp->z_nrules; ++j)
 				{
-					rp = &zp->z_rules[j];
-					eats(zp->z_filename, zp->z_linenum,
-						 rp->r_filename, rp->r_linenum);
+					zic_t		one = 1;
+					zic_t		y2038_boundary = one << 31;
+					struct rule *rp = &zp->z_rules[j];
+
+					eats(zp->z_filenum, zp->z_linenum,
+						 rp->r_filenum, rp->r_linenum);
 					rp->r_todo = year >= rp->r_loyear &&
 						year <= rp->r_hiyear;
 					if (rp->r_todo)
@@ -3161,6 +4083,8 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 					zic_t		jtime,
 								ktime;
 					zic_t		offset;
+					struct rule *rp;
+					int			type;
 
 					INITIALIZE(ktime);
 					if (useuntil)
@@ -3185,18 +4109,16 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 					k = -1;
 					for (j = 0; j < zp->z_nrules; ++j)
 					{
-						rp = &zp->z_rules[j];
-						if (!rp->r_todo)
+						struct rule *r = &zp->z_rules[j];
+
+						if (!r->r_todo)
 							continue;
-						eats(zp->z_filename, zp->z_linenum,
-							 rp->r_filename, rp->r_linenum);
-						offset = rp->r_todisut ? 0 : stdoff;
-						if (!rp->r_todisstd)
+						eats(zp->z_filenum, zp->z_linenum,
+							 r->r_filenum, r->r_linenum);
+						offset = r->r_todisut ? 0 : stdoff;
+						if (!r->r_todisstd)
 							offset = oadd(offset, save);
-						jtime = rp->r_temp;
-						if (jtime == min_time ||
-							jtime == max_time)
-							continue;
+						jtime = r->r_temp;
 						jtime = tadd(jtime, -offset);
 						if (k < 0 || jtime < ktime)
 						{
@@ -3208,12 +4130,12 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 							char const *dup_rules_msg =
 								_("two rules for same instant");
 
-							eats(zp->z_filename, zp->z_linenum,
-								 rp->r_filename, rp->r_linenum);
+							eats(zp->z_filenum, zp->z_linenum,
+								 r->r_filenum, r->r_linenum);
 							warning("%s", dup_rules_msg);
-							rp = &zp->z_rules[k];
-							eats(zp->z_filename, zp->z_linenum,
-								 rp->r_filename, rp->r_linenum);
+							r = &zp->z_rules[k];
+							eats(zp->z_filenum, zp->z_linenum,
+								 r->r_filenum, r->r_linenum);
 							error("%s", dup_rules_msg);
 						}
 					}
@@ -3222,7 +4144,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 					rp = &zp->z_rules[k];
 					rp->r_todo = false;
 					if (useuntil && ktime >= untiltime)
+					{
+						if (!*startbuf
+							&& (oadd(zp->z_stdoff, rp->r_save)
+								== startoff))
+							doabbr(startbuf, zp, rp->r_abbrvar,
+								   rp->r_isdst, rp->r_save,
+								   false);
 						break;
+					}
 					save = rp->r_save;
 					if (usestart && ktime == starttime)
 						usestart = false;
@@ -3251,44 +4181,41 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 								   false);
 						}
 					}
-					eats(zp->z_filename, zp->z_linenum,
-						 rp->r_filename, rp->r_linenum);
+					eats(zp->z_filenum, zp->z_linenum,
+						 rp->r_filenum, rp->r_linenum);
 					doabbr(ab, zp, rp->r_abbrvar,
 						   rp->r_isdst, rp->r_save, false);
 					offset = oadd(zp->z_stdoff, rp->r_save);
-					if (!want_bloat() && !useuntil && !do_extend
-						&& prevrp
-						&& rp->r_hiyear == ZIC_MAX
-						&& prevrp->r_hiyear == ZIC_MAX)
-						break;
 					type = addtype(offset, ab, rp->r_isdst,
 								   rp->r_todisstd, rp->r_todisut);
 					if (defaulttype < 0 && !rp->r_isdst)
 						defaulttype = type;
-					if (rp->r_hiyear == ZIC_MAX
-						&& !(0 <= lastatmax
-							 && ktime < attypes[lastatmax].at))
-						lastatmax = timecnt;
 					addtt(ktime, type);
-					prevrp = rp;
+					if (nonTZlimtime < ktime
+						&& (useuntil || rp->r_hiyear != ZIC_MAX))
+					{
+						nonTZlimtime = ktime;
+						nonTZlimtype = type;
+					}
 				}
 			}
+		}
 		if (usestart)
 		{
-			if (*startbuf == '\0' &&
-				zp->z_format != NULL &&
-				strchr(zp->z_format, '%') == NULL &&
-				strchr(zp->z_format, '/') == NULL)
-				strcpy(startbuf, zp->z_format);
-			eat(zp->z_filename, zp->z_linenum);
+			bool		isdst = startoff != zp->z_stdoff;
+
+			if (*startbuf == '\0' && zp->z_format)
+				doabbr(startbuf, zp, disable_percent_s,
+					   isdst, save, false);
+			eat(zp->z_filenum, zp->z_linenum);
 			if (*startbuf == '\0')
-				error(_("cannot determine time zone abbreviation to use just after until time"));
+				error(_("cannot determine time zone abbreviation"
+						" to use just after until time"));
 			else
 			{
-				bool		isdst = startoff != zp->z_stdoff;
+				int			type = addtype(startoff, startbuf, isdst,
+										   startttisstd, startttisut);
 
-				type = addtype(startoff, startbuf, isdst,
-							   startttisstd, startttisut);
 				if (defaulttype < 0 && !isdst)
 					defaulttype = type;
 				addtt(starttime, type);
@@ -3311,17 +4238,50 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	}
 	if (defaulttype < 0)
 		defaulttype = 0;
-	if (0 <= lastatmax)
-		attypes[lastatmax].dontmerge = true;
+	if (!do_extend && !want_bloat())
+	{
+		/* Keep trailing transitions that are no greater than this.  */
+		zic_t		keep_at_max;
+
+		/* The earliest transition into a time governed by the TZ string.  */
+		zic_t		TZstarttime = ZIC_MAX;
+
+		for (i = 0; i < timecnt; i++)
+		{
+			zic_t		at = attypes[i].at;
+
+			if (nonTZlimtime < at && at < TZstarttime)
+				TZstarttime = at;
+		}
+		if (TZstarttime == ZIC_MAX)
+			TZstarttime = nonTZlimtime;
+
+		/*
+		 * Omit trailing transitions deducible from the TZ string, and not
+		 * needed for -r or -R.
+		 */
+		keep_at_max = max(TZstarttime, redundant_time);
+		for (i = j = 0; i < timecnt; i++)
+			if (attypes[i].at <= keep_at_max)
+			{
+				attypes[j].at = attypes[i].at;
+				attypes[j].dontmerge = (attypes[i].at == TZstarttime
+										&& (nonTZlimtype != attypes[i].type
+											|| strchr(envvar, ',')));
+				attypes[j].type = attypes[i].type;
+				j++;
+			}
+		timecnt = j;
+	}
 	if (do_extend)
 	{
 		/*
 		 * If we're extending the explicitly listed observations for 400 years
-		 * because we can't fill the POSIX-TZ field, check whether we actually
-		 * ended up explicitly listing observations through that period.  If
-		 * there aren't any near the end of the 400-year period, add a
-		 * redundant one at the end of the final year, to make it clear that
-		 * we are claiming to have definite knowledge of the lack of
+		 * because we can't fill the proleptic TZ field, check whether we
+		 * actually ended up explicitly listing observations through that
+		 * period.  If there aren't any near the end of the 400-year period,
+		 * add a redundant one at the end of the final year, to make it clear
+		 * that we are claiming to have definite knowledge of the lack of
 		 * transitions up to that point.
 		 */
 		struct rule xr;
@@ -3362,8 +4322,10 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
 {
 	int			i,
 				j;
+	int			charcnt0;
 
-	if (!(-1L - 2147483647L <= utoff && utoff <= 2147483647L))
+	/* RFC 9636 section 3.2 specifies this range for utoff.  */
+	if (!(-TWO_31_MINUS_1 <= utoff && utoff <= TWO_31_MINUS_1))
 	{
 		error(_("UT offset out of range"));
 		exit(EXIT_FAILURE);
@@ -3371,11 +4333,21 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
 	if (!want_bloat())
 		ttisstd = ttisut = false;
 
-	for (j = 0; j < charcnt; ++j)
-		if (strcmp(&chars[j], abbr) == 0)
-			break;
-	if (j == charcnt)
-		newabbr(abbr);
+	checkabbr(abbr);
+
+	charcnt0 = charcnt;
+	j = addabbr(chars, &charcnt, abbr);
+	if (charcnt0 < charcnt)
+	{
+		/*
+		 * If an abbreviation was inserted, increment indexes no earlier than
+		 * the insert by the size of the insertion, so that they continue to
+		 * point to the same contents.
+		 */
+		for (i = 0; i < typecnt; i++)
+			if (j <= desigidx[i])
+				desigidx[i] += charcnt - charcnt0;
+	}
 	else
 	{
 		/* If there's already an entry, return its index.  */
@@ -3405,29 +4377,28 @@ addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
 static void
 leapadd(zic_t t, int correction, int rolling)
 {
-	int			i;
+	ptrdiff_t	i;
 
-	if (TZ_MAX_LEAPS <= leapcnt)
+	if (rolling && (lo_time != min_time || hi_time != max_time))
 	{
-		error(_("too many leap seconds"));
+		error(_("Rolling leap seconds not supported with -r"));
 		exit(EXIT_FAILURE);
 	}
+	leap = growalloc(leap, sizeof *leap, leapcnt, &leap_alloc);
 	for (i = 0; i < leapcnt; ++i)
-		if (t <= trans[i])
+		if (t <= leap[i].trans)
 			break;
-	memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans);
-	memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr);
-	memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll);
-	trans[i] = t;
-	corr[i] = correction;
-	roll[i] = rolling;
+	memmove(&leap[i + 1], &leap[i], (leapcnt - i) * sizeof *leap);
+	leap[i].trans = t;
+	leap[i].corr = correction;
+	leap[i].roll = rolling;
 	++leapcnt;
 }
 
 static void
 adjleap(void)
 {
-	int			i;
+	ptrdiff_t	i;
 	zic_t		last = 0;
 	zic_t		prevtrans = 0;
 
@@ -3436,33 +4407,24 @@ adjleap(void)
 	 */
 	for (i = 0; i < leapcnt; ++i)
 	{
-		if (trans[i] - prevtrans < 28 * SECSPERDAY)
+		if (leap[i].trans - prevtrans < 28 * SECSPERDAY)
 		{
 			error(_("Leap seconds too close together"));
 			exit(EXIT_FAILURE);
 		}
-		prevtrans = trans[i];
-		trans[i] = tadd(trans[i], last);
-		last = corr[i] += last;
-	}
-
-	if (leapexpires < 0)
-	{
-		leapexpires = comment_leapexpires;
-		if (0 <= leapexpires)
-			warning(_("\"#expires\" is obsolescent; use \"Expires\""));
+		prevtrans = leap[i].trans;
+		leap[i].trans = tadd(prevtrans, last);
+		last = leap[i].corr += last;
 	}
 
 	if (0 <= leapexpires)
 	{
 		leapexpires = oadd(leapexpires, last);
-		if (!(leapcnt == 0 || (trans[leapcnt - 1] < leapexpires)))
+		if (!(leapcnt == 0 || (leap[leapcnt - 1].trans < leapexpires)))
 		{
 			error(_("last Leap time does not precede Expires time"));
 			exit(EXIT_FAILURE);
 		}
-		if (leapexpires <= hi_time)
-			hi_time = leapexpires - 1;
 	}
 }
 
@@ -3615,6 +4577,7 @@ lowerit(char a)
 }
 
 /* case-insensitive equality */
+ATTRIBUTE_PURE_114833
 static bool
 ciequal(const char *ap, const char *bp)
 {
@@ -3624,6 +4587,7 @@ ciequal(const char *ap, const char *bp)
 	return false;
 }
 
+ATTRIBUTE_PURE_114833
 static bool
 itsabbr(const char *abbr, const char *word)
 {
@@ -3641,6 +4605,7 @@ itsabbr(const char *abbr, const char *word)
 
 /* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case.  */
 
+ATTRIBUTE_PURE_114833
 static bool
 ciprefix(char const *abbr, char const *word)
 {
@@ -3718,24 +4683,22 @@ byword(const char *word, const struct lookup *table)
 	return foundlp;
 }
 
-static char **
-getfields(char *cp)
+static int
+getfields(char *cp, char **array, int arrayelts)
 {
 	char	   *dp;
-	char	  **array;
 	int			nsubs;
 
-	if (cp == NULL)
-		return NULL;
-	array = emalloc(size_product(strlen(cp) + 1, sizeof *array));
 	nsubs = 0;
 	for (;;)
 	{
+		char	   *dstart;
+
 		while (is_space(*cp))
 			++cp;
 		if (*cp == '\0' || *cp == '#')
 			break;
-		array[nsubs++] = dp = cp;
+		dstart = dp = cp;
 		do
 		{
 			if ((*dp = *cp++) != '"')
@@ -3753,53 +4716,79 @@ getfields(char *cp)
 		if (is_space(*cp))
 			++cp;
 		*dp = '\0';
+		if (nsubs == arrayelts)
+		{
+			error(_("Too many input fields"));
+			exit(EXIT_FAILURE);
+		}
+		array[nsubs++] = dstart + (*dstart == '-' && dp == dstart + 1);
 	}
-	array[nsubs] = NULL;
-	return array;
+	return nsubs;
 }
 
-static _Noreturn void
+ATTRIBUTE_NORETURN static void
 time_overflow(void)
 {
 	error(_("time overflow"));
 	exit(EXIT_FAILURE);
 }
 
+/* Return T1 + T2, but diagnose any overflow and exit.  */
+ATTRIBUTE_PURE_114833_HACK
 static zic_t
 oadd(zic_t t1, zic_t t2)
 {
-	if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2)
-		time_overflow();
-	return t1 + t2;
+#ifdef ckd_add
+	zic_t		sum;
+
+	if (!ckd_add(&sum, t1, t2))
+		return sum;
+#else
+	if (t1 < 0 ? ZIC_MIN - t1 <= t2 : t2 <= ZIC_MAX - t1)
+		return t1 + t2;
+#endif
+	time_overflow();
 }
 
+/*
+ * Return T1 + T2, but diagnose any overflow and exit.
+ * This is like oadd, except the result must fit in min_time..max_time range,
+ * which on oddball machines can be a smaller range than ZIC_MIN..ZIC_MAX.
+ */
+ATTRIBUTE_PURE_114833_HACK
 static zic_t
 tadd(zic_t t1, zic_t t2)
 {
-	if (t1 < 0)
-	{
-		if (t2 < min_time - t1)
-		{
-			if (t1 != min_time)
-				time_overflow();
-			return min_time;
-		}
-	}
-	else
-	{
-		if (max_time - t1 < t2)
-		{
-			if (t1 != max_time)
-				time_overflow();
-			return max_time;
-		}
-	}
-	return t1 + t2;
+	zic_t		sum = oadd(t1, t2);
+
+	if (min_time <= sum && sum <= max_time)
+		return sum;
+	time_overflow();
+}
+
+/* Return T1 * T2, but diagnose any overflow and exit.  */
+ATTRIBUTE_PURE_114833_HACK
+static zic_t
+omul(zic_t t1, zic_t t2)
+{
+#ifdef ckd_mul
+	zic_t		product;
+
+	if (!ckd_mul(&product, t1, t2))
+		return product;
+#else
+	if (t2 < 0
+		? ZIC_MAX / t2 <= t1 && (t2 == -1 || t1 <= ZIC_MIN / t2)
+		: t2 == 0 || (ZIC_MIN / t2 <= t1 && t1 <= ZIC_MAX / t2))
+		return t1 * t2;
+#endif
+	time_overflow();
 }
 
 /*
  * Given a rule, and a year, compute the date (in seconds since January 1,
  * 1970, 00:00 LOCAL time) in that year that the rule refers to.
+ * Do not count leap seconds.  On error, diagnose and exit.
  */
 
 static zic_t
@@ -3810,39 +4799,27 @@ rpytime(const struct rule *rp, zic_t wantedy)
 	zic_t		dayoff;			/* with a nod to Margaret O. */
 	zic_t		t,
 				y;
+	int			yrem;
 
-	if (wantedy == ZIC_MIN)
-		return min_time;
-	if (wantedy == ZIC_MAX)
-		return max_time;
-	dayoff = 0;
 	m = TM_JANUARY;
 	y = EPOCH_YEAR;
-	if (y < wantedy)
-	{
-		wantedy -= y;
-		dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
-		wantedy %= YEARSPERREPEAT;
-		wantedy += y;
-	}
-	else if (wantedy < 0)
-	{
-		dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
-		wantedy %= YEARSPERREPEAT;
-	}
+
+	/*
+	 * dayoff = floor((wantedy - y) / YEARSPERREPEAT) * DAYSPERREPEAT, sans
+	 * overflow.
+	 */
+	yrem = wantedy % YEARSPERREPEAT - y % YEARSPERREPEAT;
+	dayoff = ((wantedy / YEARSPERREPEAT - y / YEARSPERREPEAT
+			   + yrem / YEARSPERREPEAT - (yrem % YEARSPERREPEAT < 0))
+			  * DAYSPERREPEAT);
+	/* wantedy = y + ((wantedy - y) mod YEARSPERREPEAT), sans overflow.  */
+	wantedy = y + (yrem + 2 * YEARSPERREPEAT) % YEARSPERREPEAT;
+
 	while (wantedy != y)
 	{
-		if (wantedy > y)
-		{
-			i = len_years[isleap(y)];
-			++y;
-		}
-		else
-		{
-			--y;
-			i = -len_years[isleap(y)];
-		}
+		i = len_years[isleap(y)];
 		dayoff = oadd(dayoff, i);
+		y++;
 	}
 	while (m != rp->r_month)
 	{
@@ -3865,27 +4842,17 @@ rpytime(const struct rule *rp, zic_t wantedy)
 	dayoff = oadd(dayoff, i);
 	if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ)
 	{
-		zic_t		wday;
-
-#define LDAYSPERWEEK	((zic_t) DAYSPERWEEK)
-		wday = EPOCH_WDAY;
-
 		/*
 		 * Don't trust mod of negative numbers.
 		 */
-		if (dayoff >= 0)
-			wday = (wday + dayoff) % LDAYSPERWEEK;
-		else
-		{
-			wday -= ((-dayoff) % LDAYSPERWEEK);
-			if (wday < 0)
-				wday += LDAYSPERWEEK;
-		}
+		zic_t		wday = ((EPOCH_WDAY + dayoff % DAYSPERWEEK + DAYSPERWEEK)
+							% DAYSPERWEEK);
+
 		while (wday != rp->r_wday)
 			if (rp->r_dycode == DC_DOWGEQ)
 			{
 				dayoff = oadd(dayoff, 1);
-				if (++wday >= LDAYSPERWEEK)
+				if (++wday >= DAYSPERWEEK)
 					wday = 0;
 				++i;
 			}
@@ -3893,7 +4860,7 @@ rpytime(const struct rule *rp, zic_t wantedy)
 			{
 				dayoff = oadd(dayoff, -1);
 				if (--wday < 0)
-					wday = LDAYSPERWEEK - 1;
+					wday = DAYSPERWEEK - 1;
 				--i;
 			}
 		if (i < 0 || i >= len_months[isleap(y)][m])
@@ -3903,19 +4870,13 @@ rpytime(const struct rule *rp, zic_t wantedy)
 will not work with pre-2004 versions of zic"));
 		}
 	}
-	if (dayoff < min_time / SECSPERDAY)
-		return min_time;
-	if (dayoff > max_time / SECSPERDAY)
-		return max_time;
-	t = (zic_t) dayoff * SECSPERDAY;
+	t = omul(dayoff, SECSPERDAY);
 	return tadd(t, rp->r_tod);
 }
 
 static void
-newabbr(const char *string)
+checkabbr(char const *string)
 {
-	int			i;
-
 	if (strcmp(string, GRANDPARENTED) != 0)
 	{
 		const char *cp;
@@ -3923,7 +4884,7 @@ newabbr(const char *string)
 
 		cp = string;
 		mp = NULL;
-		while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9')
+		while (is_alpha(*cp) || is_digit(*cp)
 			   || *cp == '-' || *cp == '+')
 			++cp;
 		if (noise && cp - string < 3)
@@ -3935,73 +4896,136 @@ newabbr(const char *string)
 		if (mp != NULL)
 			warning("%s (%s)", mp, string);
 	}
-	i = strlen(string) + 1;
-	if (charcnt + i > TZ_MAX_CHARS)
+}
+
+/*
+ * Put into CHS, which currently contains *PNCHS bytes containing
+ * NUL-terminated abbreviations none of which are suffixes of another,
+ * the abbreviation ABBR including its trailing NUL.
+ * If ABBR does not already appear in CHS,
+ * possibly as a suffix of an existing abbreviation,
+ * add ABBR to CHS, remove from CHS any abbreviation
+ * that is a suffix of ABBR, and increment *PNCHS accordingly.
+ * Return the index of ABBR after any modifications to CHS are made.
+ *
+ * If all abbreviations have already been added, this function
+ * lets the caller look up the index of an existing abbreviation.
+ */
+static int
+addabbr(char chs[TZ_MAX_CHARS], int *pnchs, char const *abbr)
+{
+	int			nchs = *pnchs;
+	int			alen = strlen(abbr),
+				nchs_incr = alen + 1;
+	int			i;
+
+	for (i = 0; i < nchs;)
+	{
+		int			clen = strlen(&chs[i]);
+
+		if (alen <= clen)
+		{
+			/*
+			 * If ABBR is a suffix of an abbreviation in CHS, return the index
+			 * of ABBR in CHS.
+			 */
+			int			isuff = i + (clen - alen);
+
+			if (memcmp(&chs[isuff], abbr, alen) == 0)
+				return isuff;
+		}
+		else if (memcmp(&chs[i], &abbr[alen - clen], clen) == 0)
+		{
+			/*
+			 * An abbreviation in CHS is a substring of ABBR. Replace it with
+			 * ABBR, instead of the more-common actions of appending ABBR or
+			 * doing nothing.
+			 */
+			nchs_incr = alen - clen;
+			break;
+		}
+		i += clen + 1;
+	}
+	if (TZ_MAX_CHARS < nchs + nchs_incr)
 	{
 		error(_("too many, or too long, time zone abbreviations"));
 		exit(EXIT_FAILURE);
 	}
-	strcpy(&chars[charcnt], string);
-	charcnt += i;
+	memmove(&chs[i + nchs_incr], &chs[i], nchs - i);
+	memcpy(&chs[i], abbr, nchs_incr);
+	*pnchs = nchs + nchs_incr;
+	return i;
 }
 
 /*
  * Ensure that the directories of ARGNAME exist, by making any missing
  * ones.  If ANCESTORS, do this only for ARGNAME's ancestors; otherwise,
  * do it for ARGNAME too.  Exit with failure if there is trouble.
- * Do not consider an existing non-directory to be trouble.
+ * Do not consider an existing file to be trouble.
  */
 static void
 mkdirs(char const *argname, bool ancestors)
 {
-	char	   *name;
-	char	   *cp;
-
-	cp = name = ecpyalloc(argname);
-
 	/*
-	 * On MS-Windows systems, do not worry about drive letters or backslashes,
-	 * as this should suffice in practice.  Time zone names do not use drive
-	 * letters and backslashes.  If the -d option of zic does not name an
-	 * already-existing directory, it can use slashes to separate the
-	 * already-existing ancestor prefix from the to-be-created subdirectories.
+	 * If -D was specified, do not create directories. If a file operation's
+	 * parent directory is missing, the operation will fail and be diagnosed.
 	 */
-
-	/* Do not mkdir a root directory, as it must exist.  */
-	while (*cp == '/')
-		cp++;
-
-	while (cp && ((cp = strchr(cp, '/')) || !ancestors))
+	if (!skip_mkdir)
 	{
-		if (cp)
-			*cp = '\0';
+
+		char	   *name = xstrdup(argname);
+		char	   *cp = name;
 
 		/*
-		 * Try to create it.  It's OK if creation fails because the directory
-		 * already exists, perhaps because some other process just created it.
-		 * For simplicity do not check first whether it already exists, as
-		 * that is checked anyway if the mkdir fails.
+		 * On MS-Windows systems, do not worry about drive letters or
+		 * backslashes, as this should suffice in practice.  Time zone names
+		 * do not use drive letters and backslashes.  If the -d option of zic
+		 * does not name an already-existing directory, it can use slashes to
+		 * separate the already-existing ancestor prefix from the
+		 * to-be-created subdirectories.
 		 */
-		if (mkdir(name, MKDIR_UMASK) != 0)
+
+		/* Do not mkdir a root directory, as it must exist.  */
+		while (*cp == '/')
+			cp++;
+
+		while (cp && ((cp = strchr(cp, '/')) || !ancestors))
 		{
+			if (cp)
+				*cp = '\0';
+
 			/*
-			 * For speed, skip itsdir if errno == EEXIST.  Since mkdirs is
-			 * called only after open fails with ENOENT on a subfile, EEXIST
-			 * implies itsdir here.
+			 * Try to create it.  It's OK if creation fails because the
+			 * directory already exists, perhaps because some other process
+			 * just created it.  For simplicity do not check first whether it
+			 * already exists, as that is checked anyway if the mkdir fails.
 			 */
-			int			err = errno;
-
-			if (err != EEXIST && !itsdir(name))
+			if (mkdir(name, MKDIR_PERMS) < 0)
 			{
-				error(_("%s: Cannot create directory %s: %s"),
-					  progname, name, strerror(err));
-				exit(EXIT_FAILURE);
+				/*
+				 * Do not report an error if err == EEXIST, because some other
+				 * process might have made the directory in the meantime.
+				 * Likewise for ENOSYS, because Solaris 10 mkdir fails with
+				 * ENOSYS if the directory is an automounted mount point.
+				 * Likewise for EACCES, since mkdir can fail with EACCES
+				 * merely because the parent directory is unwritable. Likewise
+				 * for most other error numbers.
+				 */
+				int			err = errno;
+
+				if (err == ELOOP || err == ENAMETOOLONG
+					|| err == ENOENT || err == ENOTDIR)
+				{
+					error(_("%s: Cannot create directory %s: %s"),
+						  progname, name, strerror(err));
+					exit(EXIT_FAILURE);
+				}
 			}
+			if (cp)
+				*cp++ = '/';
 		}
-		if (cp)
-			*cp++ = '/';
+		free(name);
 	}
-	free(name);
 }
 
 
@@ -4009,7 +5033,7 @@ mkdirs(char const *argname, bool ancestors)
 /*
  * To run on win32
  */
-int
+static int
 link(const char *oldpath, const char *newpath)
 {
 	if (!CopyFile(oldpath, newpath, false))
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8cf40c87043..dbd14e61561 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3721,6 +3721,7 @@ deparse_context
 deparse_expr_cxt
 deparse_namespace
 derives_hash
+desigidx_type
 dev_t
 disassembledLeaf
 dlist_head
@@ -3893,6 +3894,7 @@ int64_t
 int8
 int8_t
 int8x16_t
+int_fast32_2s
 int_fast32_t
 int_fast64_t
 internalPQconninfoOption
-- 
2.52.0

