diff mbox series

Update timezone code from tzcode 2020a

Message ID 20200425172757.3413-1-eggert@cs.ucla.edu
State New
Headers show
Series Update timezone code from tzcode 2020a | expand

Commit Message

Paul Eggert April 25, 2020, 5:27 p.m. UTC
This patch updates files coming from tzcode to tzcode 2020a.
This is mostly for better support for Internet RFC 8536, by adding
support to zic for the Expires line (new to tzcode 2020a), the -b
option (new to 2019b) and the -r option (new to 2019a).
One trivial change to other glibc was needed.
* time/tzfile.c (__tzfile_read): Adjust to tzcode private.h renaming.
* timezone/private.h, timezone/tzfile.h, timezone/version:
* timezone/zdump.c, timezone/zic.c: Update from tzcode 2020a.
---
 time/tzfile.c      |   2 +-
 timezone/private.h |  37 ++-
 timezone/tzfile.h  |  20 +-
 timezone/version   |   2 +-
 timezone/zdump.c   |   6 +-
 timezone/zic.c     | 814 ++++++++++++++++++++++++++++-----------------
 6 files changed, 554 insertions(+), 327 deletions(-)

Comments

Paul Eggert April 25, 2020, 5:33 p.m. UTC | #1
On 4/25/20 10:27 AM, Paul Eggert wrote:
> --- a/timezone/version
> +++ b/timezone/version
> @@ -1 +1 @@
> -2018i
> +2020a-dirty

That's a typo; the line should be just '2020a'. I'll fix that before installing.
Jonathan Nieder April 26, 2020, 12:21 a.m. UTC | #2
Paul Eggert wrote:

> This patch updates files coming from tzcode to tzcode 2020a.
> This is mostly for better support for Internet RFC 8536, by adding
> support to zic for the Expires line (new to tzcode 2020a), the -b
> option (new to 2019b) and the -r option (new to 2019a).
> One trivial change to other glibc was needed.
> * time/tzfile.c (__tzfile_read): Adjust to tzcode private.h renaming.
> * timezone/private.h, timezone/tzfile.h, timezone/version:
> * timezone/zdump.c, timezone/zic.c: Update from tzcode 2020a.
> ---
>  time/tzfile.c      |   2 +-
>  timezone/private.h |  37 ++-
>  timezone/tzfile.h  |  20 +-
>  timezone/version   |   2 +-
>  timezone/zdump.c   |   6 +-
>  timezone/zic.c     | 814 ++++++++++++++++++++++++++++-----------------
>  6 files changed, 554 insertions(+), 327 deletions(-)

With the version tweak you mentioned already,

Reviewed-by: Jonathan Nieder <jrnieder@gmail.com>
Thanks.
diff mbox series

Patch

diff --git a/time/tzfile.c b/time/tzfile.c
index 013b3d03f3..af6da1bf00 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -189,7 +189,7 @@  __tzfile_read (const char *file, size_t extra, char **extrap)
   chars = (size_t) decode (tzhead.tzh_charcnt);
   num_leaps = (size_t) decode (tzhead.tzh_leapcnt);
   num_isstd = (size_t) decode (tzhead.tzh_ttisstdcnt);
-  num_isgmt = (size_t) decode (tzhead.tzh_ttisgmtcnt);
+  num_isgmt = (size_t) decode (tzhead.tzh_ttisutcnt);
 
   if (__glibc_unlikely (num_isstd > num_types || num_isgmt > num_types))
     goto lose;
diff --git a/timezone/private.h b/timezone/private.h
index 1ead14793b..8513663036 100644
--- a/timezone/private.h
+++ b/timezone/private.h
@@ -132,11 +132,16 @@ 
 ** Nested includes
 */
 
-/* Avoid clashes with NetBSD by renaming NetBSD's declarations.  */
+/* Avoid clashes with NetBSD by renaming NetBSD's declarations.
+   If defining the 'timezone' variable, avoid a clash with FreeBSD's
+   'timezone' function by renaming its declaration.  */
 #define localtime_rz sys_localtime_rz
 #define mktime_z sys_mktime_z
 #define posix2time_z sys_posix2time_z
 #define time2posix_z sys_time2posix_z
+#if defined USG_COMPAT && USG_COMPAT == 2
+# define timezone sys_timezone
+#endif
 #define timezone_t sys_timezone_t
 #define tzalloc sys_tzalloc
 #define tzfree sys_tzfree
@@ -145,6 +150,9 @@ 
 #undef mktime_z
 #undef posix2time_z
 #undef time2posix_z
+#if defined USG_COMPAT && USG_COMPAT == 2
+# undef timezone
+#endif
 #undef timezone_t
 #undef tzalloc
 #undef tzfree
@@ -198,6 +206,14 @@ 
 # endif
 #endif
 
+#ifndef ALTZONE
+# if defined __sun || defined _M_XENIX
+#  define ALTZONE 1
+# else
+#  define ALTZONE 0
+# endif
+#endif
+
 #ifndef R_OK
 #define R_OK	4
 #endif /* !defined R_OK */
@@ -409,6 +425,10 @@  static time_t sys_time(time_t *x) { return time(x); }
 
 typedef time_tz tz_time_t;
 
+# undef  asctime
+# define asctime tz_asctime
+# undef  asctime_r
+# define asctime_r tz_asctime_r
 # undef  ctime
 # define ctime tz_ctime
 # undef  ctime_r
@@ -473,11 +493,13 @@  typedef time_tz tz_time_t;
 #  undef  timezone
 #  define timezone tz_timezone
 # endif
-# ifdef ALTZONE
+# if ALTZONE
 #  undef  altzone
 #  define altzone tz_altzone
 # endif
 
+char *asctime(struct tm const *);
+char *asctime_r(struct tm const *restrict, char *restrict);
 char *ctime(time_t const *);
 char *ctime_r(time_t const *, char *);
 double difftime(time_t, time_t) ATTRIBUTE_CONST;
@@ -512,17 +534,14 @@  extern char *asctime_r(struct tm const *restrict, char *restrict);
 extern char **environ;
 #endif
 
-#if TZ_TIME_T || !HAVE_POSIX_DECLS
-# if HAVE_TZNAME
+#if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS)
 extern char *tzname[];
-# endif
-# if USG_COMPAT
+#endif
+#if 2 <= USG_COMPAT + (TZ_TIME_T || !HAVE_POSIX_DECLS)
 extern long timezone;
 extern int daylight;
-# endif
 #endif
-
-#ifdef ALTZONE
+#if 2 <= ALTZONE + (TZ_TIME_T || !HAVE_POSIX_DECLS)
 extern long altzone;
 #endif
 
diff --git a/timezone/tzfile.h b/timezone/tzfile.h
index 27a38cc74d..ee91104443 100644
--- a/timezone/tzfile.h
+++ b/timezone/tzfile.h
@@ -33,6 +33,9 @@ 
 #define TZDEFRULES	"posixrules"
 #endif /* !defined TZDEFRULES */
 
+
+/* See Internet RFC 8536 for more details about the following format.  */
+
 /*
 ** Each file begins with. . .
 */
@@ -43,7 +46,7 @@  struct tzhead {
 	char	tzh_magic[4];		/* TZ_MAGIC */
 	char	tzh_version[1];		/* '\0' or '2' or '3' as of 2013 */
 	char	tzh_reserved[15];	/* reserved; must be zero */
-	char	tzh_ttisgmtcnt[4];	/* coded number of trans. time flags */
+	char	tzh_ttisutcnt[4];	/* coded number of trans. time flags */
 	char	tzh_ttisstdcnt[4];	/* coded number of trans. time flags */
 	char	tzh_leapcnt[4];		/* coded number of leap seconds */
 	char	tzh_timecnt[4];		/* coded number of transition times */
@@ -66,14 +69,15 @@  struct tzhead {
 **		one (char [4])		total correction after above
 **	tzh_ttisstdcnt (char)s		indexed by type; if 1, transition
 **					time is standard time, if 0,
-**					transition time is wall clock time
-**					if absent, transition times are
-**					assumed to be wall clock time
-**	tzh_ttisgmtcnt (char)s		indexed by type; if 1, transition
-**					time is UT, if 0,
-**					transition time is local time
-**					if absent, transition times are
+**					transition time is local (wall clock)
+**					time; if absent, transition times are
 **					assumed to be local time
+**	tzh_ttisutcnt (char)s		indexed by type; if 1, transition
+**					time is UT, if 0, transition time is
+**					local time; if absent, transition
+**					times are assumed to be local time.
+**					When this is 1, the corresponding
+**					std/wall indicator must also be 1.
 */
 
 /*
diff --git a/timezone/version b/timezone/version
index 63f58006ee..8e4c88ab81 100644
--- a/timezone/version
+++ b/timezone/version
@@ -1 +1 @@ 
-2018i
+2020a-dirty
diff --git a/timezone/zdump.c b/timezone/zdump.c
index 0fc8ced96a..b532fe3eae 100644
--- a/timezone/zdump.c
+++ b/timezone/zdump.c
@@ -328,12 +328,12 @@  abbrok(const char *const abbrp, const char *const zone)
 	cp = abbrp;
 	while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
 		++cp;
-	if (cp - abbrp < 3)
+	if (*cp)
+	  wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
+	else if (cp - abbrp < 3)
 	  wp = _("has fewer than 3 characters");
 	else if (cp - abbrp > 6)
 	  wp = _("has more than 6 characters");
-	else if (*cp)
-	  wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
 	else
 	  return;
 	fflush(stdout);
diff --git a/timezone/zic.c b/timezone/zic.c
index 2ebc66a9af..2875b5544c 100644
--- a/timezone/zic.c
+++ b/timezone/zic.c
@@ -92,13 +92,10 @@  struct rule {
 	int		r_wday;
 
 	zic_t		r_tod;		/* time from midnight */
-	bool		r_todisstd;	/* above is standard time if 1 */
-					/* or wall clock time if 0 */
-	bool		r_todisgmt;	/* above is GMT if 1 */
-					/* or local time if 0 */
+	bool		r_todisstd;	/* is r_tod standard time? */
+	bool		r_todisut;	/* is r_tod UT? */
 	bool		r_isdst;	/* is this daylight saving time? */
-	zic_t		r_stdoff;	/* offset from default time (which is
-					   usually standard time) */
+	zic_t		r_save;		/* offset from standard time */
 	const char *	r_abbrvar;	/* variable part of abbreviation */
 
 	bool		r_todo;		/* a rule to do (used in outzone) */
@@ -118,13 +115,13 @@  struct zone {
 	lineno		z_linenum;
 
 	const char *	z_name;
-	zic_t		z_gmtoff;
+	zic_t		z_stdoff;
 	char *		z_rule;
 	const char *	z_format;
 	char		z_format_specifier;
 
 	bool		z_isdst;
-	zic_t		z_stdoff;
+	zic_t		z_save;
 
 	struct rule *	z_rules;
 	ptrdiff_t	z_nrules;
@@ -156,13 +153,14 @@  extern int	optind;
 
 static void	addtt(zic_t starttime, int type);
 static int	addtype(zic_t, char const *, bool, bool, bool);
-static void	leapadd(zic_t, bool, int, int);
+static void	leapadd(zic_t, int, int);
 static void	adjleap(void);
 static void	associate(void);
 static void	dolink(const char *, const char *, bool);
 static char **	getfields(char * buf);
 static zic_t	gethms(const char * string, const char * errstring);
-static zic_t	getstdoff(char *, bool *);
+static zic_t	getsave(char *, bool *);
+static void	inexpires(char **, int);
 static void	infile(const char * filename);
 static void	inleap(char ** fields, int nfields);
 static void	inlink(char ** fields, int nfields);
@@ -227,13 +225,14 @@  static int		typecnt;
 #define LC_ZONE		1
 #define LC_LINK		2
 #define LC_LEAP		3
+#define LC_EXPIRES	4
 
 /*
 ** Which fields are which on a Zone line.
 */
 
 #define ZF_NAME		1
-#define ZF_GMTOFF	2
+#define ZF_STDOFF	2
 #define ZF_RULE		3
 #define ZF_FORMAT	4
 #define ZF_TILYEAR	5
@@ -247,7 +246,7 @@  static int		typecnt;
 ** Which fields are which on a Zone continuation line.
 */
 
-#define ZFC_GMTOFF	0
+#define ZFC_STDOFF	0
 #define ZFC_RULE	1
 #define ZFC_FORMAT	2
 #define ZFC_TILYEAR	3
@@ -268,7 +267,7 @@  static int		typecnt;
 #define RF_MONTH	5
 #define RF_DAY		6
 #define RF_TOD		7
-#define RF_STDOFF	8
+#define RF_SAVE		8
 #define RF_ABBRVAR	9
 #define RULE_FIELDS	10
 
@@ -292,6 +291,9 @@  static int		typecnt;
 #define LP_ROLL		6
 #define LEAP_FIELDS	7
 
+/* Expires lines are like Leap lines, except without CORR and ROLL fields.  */
+#define EXPIRES_FIELDS	5
+
 /*
 ** Year synonyms.
 */
@@ -335,6 +337,7 @@  static struct lookup const zi_line_codes[] = {
 };
 static struct lookup const leap_line_codes[] = {
 	{ "Leap",	LC_LEAP },
+	{ "Expires",	LC_EXPIRES },
 	{ NULL,		0}
 };
 
@@ -409,11 +412,11 @@  static struct attype {
 	bool		dontmerge;
 	unsigned char	type;
 } *			attypes;
-static zic_t		gmtoffs[TZ_MAX_TYPES];
+static zic_t		utoffs[TZ_MAX_TYPES];
 static char		isdsts[TZ_MAX_TYPES];
-static unsigned char	abbrinds[TZ_MAX_TYPES];
+static unsigned char	desigidx[TZ_MAX_TYPES];
 static bool		ttisstds[TZ_MAX_TYPES];
-static bool		ttisgmts[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];
@@ -574,8 +577,10 @@  usage(FILE *stream, int status)
 {
   fprintf(stream,
 	  _("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n"
-	    "\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n"
-	    "\t[ -t localtime-link ] [ -L leapseconds ] [ filename ... ]\n\n"
+	    "\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]"
+	    " [ -L leapseconds ] \\\n"
+	    "\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -t localtime-link ] \\\n"
+	    "\t[ filename ... ]\n\n"
 	    "Report bugs to %s.\n"),
 	  progname, progname, REPORT_BUGS_TO);
   if (status == EXIT_SUCCESS)
@@ -603,6 +608,51 @@  change_directory (char const *dir)
   }
 }
 
+#define 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);
+static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
+
+/* The minimum, and one less than the maximum, values specified by
+   the -r option.  These default to MIN_TIME and MAX_TIME.  */
+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 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.  */
+static bool
+timerange_option(char *timerange)
+{
+  intmax_t lo = min_time, hi = max_time;
+  char *lo_end = timerange, *hi_end;
+  if (*timerange == '@') {
+    errno = 0;
+    lo = strtoimax (timerange + 1, &lo_end, 10);
+    if (lo_end == timerange + 1 || (lo == INTMAX_MAX && errno == ERANGE))
+      return false;
+  }
+  hi_end = lo_end;
+  if (lo_end[0] == '/' && lo_end[1] == '@') {
+    errno = 0;
+    hi = strtoimax (lo_end + 2, &hi_end, 10);
+    if (hi_end == lo_end + 2 || hi == INTMAX_MIN)
+      return false;
+    hi -= ! (hi == INTMAX_MAX && errno == ERANGE);
+  }
+  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;
+  return true;
+}
+
 static const char *	psxrules;
 static const char *	lcltime;
 static const char *	directory;
@@ -610,11 +660,27 @@  static const char *	leapsec;
 static const char *	tzdefault;
 static const char *	yitcommand;
 
+/* -1 if the TZif output file should be slim, 0 if default, 1 if the
+   output should be fat for backward compatibility.  Currently the
+   default is fat, although this may change.  */
+static int bloat;
+
+static bool
+want_bloat(void)
+{
+  return 0 <= bloat;
+}
+
+#ifndef ZIC_BLOAT_DEFAULT
+# define ZIC_BLOAT_DEFAULT "fat"
+#endif
+
 int
 main(int argc, char **argv)
 {
 	register int c, k;
 	register ptrdiff_t i, j;
+	bool timerange_given = false;
 
 #ifdef S_IWGRP
 	umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
@@ -640,10 +706,22 @@  main(int argc, char **argv)
 		} else if (strcmp(argv[k], "--help") == 0) {
 			usage(stdout, EXIT_SUCCESS);
 		}
-	while ((c = getopt(argc, argv, "d:l:L:p:st:vy:")) != EOF && c != -1)
+	while ((c = getopt(argc, argv, "b:d:l:L:p:r:st:vy:")) != EOF && c != -1)
 		switch (c) {
 			default:
 				usage(stderr, EXIT_FAILURE);
+			case 'b':
+				if (strcmp(optarg, "slim") == 0) {
+				  if (0 < bloat)
+				    error(_("incompatible -b options"));
+				  bloat = -1;
+				} else if (strcmp(optarg, "fat") == 0) {
+				  if (bloat < 0)
+				    error(_("incompatible -b options"));
+				  bloat = 1;
+				} else
+				  error(_("invalid option: -b '%s'"), optarg);
+				break;
 			case 'd':
 				if (directory == NULL)
 					directory = optarg;
@@ -708,12 +786,29 @@  _("%s: More than one -L option specified\n"),
 			case 'v':
 				noise = true;
 				break;
+			case 'r':
+				if (timerange_given) {
+				  fprintf(stderr,
+_("%s: More than one -r option specified\n"),
+					  progname);
+				  return EXIT_FAILURE;
+				}
+				if (! timerange_option(optarg)) {
+				  fprintf(stderr,
+_("%s: invalid time range: %s\n"),
+					  progname, optarg);
+				  return EXIT_FAILURE;
+				}
+				timerange_given = true;
+				break;
 			case 's':
 				warning(_("-s ignored"));
 				break;
 		}
 	if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
 		usage(stderr, EXIT_FAILURE);	/* usage message by request */
+	if (bloat == 0)
+	  bloat = strcmp(ZIC_BLOAT_DEFAULT, "slim") == 0 ? -1 : 1;
 	if (directory == NULL)
 		directory = TZDIR;
 	if (tzdefault == NULL)
@@ -961,11 +1056,6 @@  dolink(char const *fromfield, char const *tofield, bool staysymlink)
 	}
 }
 
-#define TIME_T_BITS_IN_FILE	64
-
-static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
-static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
-
 /* Return true if NAME is a directory.  */
 static bool
 itsdir(char const *name)
@@ -1072,7 +1162,7 @@  associate(void)
 			** Maybe we have a local standard time offset.
 			*/
 			eat(zp->z_filename, zp->z_linenum);
-			zp->z_stdoff = getstdoff(zp->z_rule, &zp->z_isdst);
+			zp->z_save = getsave(zp->z_rule, &zp->z_isdst);
 			/*
 			** Note, though, that if there's no rule,
 			** a '%s' in the format is a bad thing.
@@ -1128,7 +1218,8 @@  infile(const char *name)
 			++nfields;
 		}
 		if (nfields == 0) {
-			/* nothing to do */
+		  if (name == leapsec && *buf == '#')
+		    sscanf(buf, "#expires %"SCNdZIC, &comment_leapexpires);
 		} else if (wantcont) {
 			wantcont = inzcont(fields, nfields);
 		} else {
@@ -1153,6 +1244,10 @@  infile(const char *name)
 					inleap(fields, nfields);
 					wantcont = false;
 					break;
+				case LC_EXPIRES:
+					inexpires(fields, nfields);
+					wantcont = false;
+					break;
 				default:	/* "cannot happen" */
 					fprintf(stderr,
 _("%s: panic: Invalid l_value %d\n"),
@@ -1230,10 +1325,10 @@  warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
 }
 
 static zic_t
-getstdoff(char *field, bool *isdst)
+getsave(char *field, bool *isdst)
 {
   int dst = -1;
-  zic_t stdoff;
+  zic_t save;
   size_t fieldlen = strlen(field);
   if (fieldlen != 0) {
     char *ep = field + fieldlen - 1;
@@ -1242,9 +1337,9 @@  getstdoff(char *field, bool *isdst)
       case 's': dst = 0; *ep = '\0'; break;
     }
   }
-  stdoff = gethms(field, _("invalid saved time"));
-  *isdst = dst < 0 ? stdoff != 0 : dst;
-  return stdoff;
+  save = gethms(field, _("invalid saved time"));
+  *isdst = dst < 0 ? save != 0 : dst;
+  return save;
 }
 
 static void
@@ -1267,7 +1362,7 @@  inrule(char **fields, int nfields)
 	}
 	r.r_filename = filename;
 	r.r_linenum = linenum;
-	r.r_stdoff = getstdoff(fields[RF_STDOFF], &r.r_isdst);
+	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]);
@@ -1328,13 +1423,13 @@  inzsub(char **fields, int nfields, bool iscont)
 	register char *		cp;
 	char *			cp1;
 	static struct zone	z;
-	register int		i_gmtoff, i_rule, i_format;
+	register int		i_stdoff, i_rule, i_format;
 	register int		i_untilyear, i_untilmonth;
 	register int		i_untilday, i_untiltime;
 	register bool		hasuntil;
 
 	if (iscont) {
-		i_gmtoff = ZFC_GMTOFF;
+		i_stdoff = ZFC_STDOFF;
 		i_rule = ZFC_RULE;
 		i_format = ZFC_FORMAT;
 		i_untilyear = ZFC_TILYEAR;
@@ -1345,7 +1440,7 @@  inzsub(char **fields, int nfields, bool iscont)
 	} else if (!namecheck(fields[ZF_NAME]))
 		return false;
 	else {
-		i_gmtoff = ZF_GMTOFF;
+		i_stdoff = ZF_STDOFF;
 		i_rule = ZF_RULE;
 		i_format = ZF_FORMAT;
 		i_untilyear = ZF_TILYEAR;
@@ -1356,7 +1451,7 @@  inzsub(char **fields, int nfields, bool iscont)
 	}
 	z.z_filename = filename;
 	z.z_linenum = linenum;
-	z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"));
+	z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset"));
 	if ((cp = strchr(fields[i_format], '%')) != 0) {
 		if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
 		    || strchr(fields[i_format], '/')) {
@@ -1410,8 +1505,8 @@  inzsub(char **fields, int nfields, bool iscont)
 	return hasuntil;
 }
 
-static void
-inleap(char **fields, int nfields)
+static zic_t
+getleapdatetime(char **fields, int nfields, bool expire_line)
 {
 	register const char *		cp;
 	register const struct lookup *	lp;
@@ -1422,10 +1517,6 @@  inleap(char **fields, int nfields)
 	zic_t				t;
 	char xs;
 
-	if (nfields != LEAP_FIELDS) {
-		error(_("wrong number of fields on Leap line"));
-		return;
-	}
 	dayoff = 0;
 	cp = fields[LP_YEAR];
 	if (sscanf(cp, "%"SCNdZIC"%c", &year, &xs) != 1) {
@@ -1433,13 +1524,15 @@  inleap(char **fields, int nfields)
 		** Leapin' Lizards!
 		*/
 		error(_("invalid leaping year"));
-		return;
+		return -1;
 	}
-	if (!leapseen || leapmaxyear < year)
+	if (!expire_line) {
+	    if (!leapseen || leapmaxyear < year)
 		leapmaxyear = year;
-	if (!leapseen || leapminyear > year)
+	    if (!leapseen || leapminyear > year)
 		leapminyear = year;
-	leapseen = true;
+	    leapseen = true;
+	}
 	j = EPOCH_YEAR;
 	while (j != year) {
 		if (year > j) {
@@ -1453,7 +1546,7 @@  inleap(char **fields, int nfields)
 	}
 	if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) {
 		error(_("invalid month name"));
-		return;
+		return -1;
 	}
 	month = lp->l_value;
 	j = TM_JANUARY;
@@ -1466,47 +1559,60 @@  inleap(char **fields, int nfields)
 	if (sscanf(cp, "%d%c", &day, &xs) != 1 ||
 		day <= 0 || day > len_months[isleap(year)][month]) {
 			error(_("invalid day of month"));
-			return;
+			return -1;
 	}
 	dayoff = oadd(dayoff, day - 1);
 	if (dayoff < min_time / SECSPERDAY) {
 		error(_("time too small"));
-		return;
+		return -1;
 	}
 	if (dayoff > max_time / SECSPERDAY) {
 		error(_("time too large"));
-		return;
+		return -1;
 	}
 	t = dayoff * SECSPERDAY;
 	tod = gethms(fields[LP_TIME], _("invalid time of day"));
-	cp = fields[LP_CORR];
-	{
-		register bool	positive;
-		int		count;
-
-		if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */
-			positive = false;
-			count = 1;
-		} else if (strcmp(cp, "+") == 0) {
-			positive = true;
-			count = 1;
-		} else {
-			error(_("illegal CORRECTION field on Leap line"));
-			return;
-		}
-		if ((lp = byword(fields[LP_ROLL], leap_types)) == NULL) {
-			error(_(
-				"illegal Rolling/Stationary field on Leap line"
-				));
-			return;
-		}
-		t = tadd(t, tod);
-		if (t < 0) {
-			error(_("leap second precedes Epoch"));
-			return;
-		}
-		leapadd(t, positive, lp->l_value, count);
-	}
+	t = tadd(t, tod);
+	if (t < 0)
+	  error(_("leap second precedes Epoch"));
+	return t;
+}
+
+static void
+inleap(char **fields, int nfields)
+{
+  if (nfields != LEAP_FIELDS)
+    error(_("wrong number of fields on Leap line"));
+  else {
+    zic_t t = getleapdatetime(fields, nfields, false);
+    if (0 <= t) {
+      struct lookup const *lp = byword(fields[LP_ROLL], leap_types);
+      if (!lp)
+	error(_("invalid Rolling/Stationary field on Leap line"));
+      else {
+	int correction = 0;
+	if (!fields[LP_CORR][0]) /* infile() turns "-" into "".  */
+	  correction = -1;
+	else if (strcmp(fields[LP_CORR], "+") == 0)
+	  correction = 1;
+	else
+	  error(_("invalid CORRECTION field on Leap line"));
+	if (correction)
+	  leapadd(t, correction, lp->l_value);
+      }
+    }
+  }
+}
+
+static void
+inexpires(char **fields, int nfields)
+{
+  if (nfields != EXPIRES_FIELDS)
+    error(_("wrong number of fields on Expires line"));
+  else if (0 <= leapexpires)
+    error(_("multiple Expires lines"));
+  else
+    leapexpires = getleapdatetime(fields, nfields, true);
 }
 
 static void
@@ -1549,26 +1655,26 @@  rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	}
 	rp->r_month = lp->l_value;
 	rp->r_todisstd = false;
-	rp->r_todisgmt = false;
+	rp->r_todisut = false;
 	dp = ecpyalloc(timep);
 	if (*dp != '\0') {
 		ep = dp + strlen(dp) - 1;
 		switch (lowerit(*ep)) {
 			case 's':	/* Standard */
 				rp->r_todisstd = true;
-				rp->r_todisgmt = false;
+				rp->r_todisut = false;
 				*ep = '\0';
 				break;
 			case 'w':	/* Wall */
 				rp->r_todisstd = false;
-				rp->r_todisgmt = false;
+				rp->r_todisut = false;
 				*ep = '\0';
 				break;
 			case 'g':	/* Greenwich */
 			case 'u':	/* Universal */
 			case 'z':	/* Zulu */
 				rp->r_todisstd = true;
-				rp->r_todisgmt = true;
+				rp->r_todisut = true;
 				*ep = '\0';
 				break;
 		}
@@ -1714,12 +1820,16 @@  puttzcode(const int_fast32_t val, FILE *const fp)
 }
 
 static void
-puttzcode64(const zic_t val, FILE *const fp)
+puttzcodepass(zic_t val, FILE *fp, int pass)
 {
+  if (pass == 1)
+    puttzcode(val, fp);
+  else {
 	char	buf[8];
 
 	convert64(val, buf);
 	fwrite(buf, sizeof buf, 1, fp);
+  }
 }
 
 static int
@@ -1731,15 +1841,34 @@  atcomp(const void *avp, const void *bvp)
 	return (a < b) ? -1 : (a > b);
 }
 
-static void
-swaptypes(int i, int j)
+struct timerange {
+  int defaulttype;
+  ptrdiff_t base, count;
+  int leapbase, leapcount;
+};
+
+static struct timerange
+limitrange(struct timerange r, zic_t lo, zic_t hi,
+	   zic_t const *ats, unsigned char const *types)
 {
-  { zic_t t = gmtoffs[i]; gmtoffs[i] = gmtoffs[j]; gmtoffs[j] = t; }
-  { char t = isdsts[i]; isdsts[i] = isdsts[j]; isdsts[j] = t; }
-  { unsigned char t = abbrinds[i]; abbrinds[i] = abbrinds[j];
-    abbrinds[j] = t; }
-  { bool t = ttisstds[i]; ttisstds[i] = ttisstds[j]; ttisstds[j] = t; }
-  { bool t = ttisgmts[i]; ttisgmts[i] = ttisgmts[j]; ttisgmts[j] = t; }
+  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) {
+    r.leapcount--;
+    r.leapbase++;
+  }
+
+  if (hi < ZIC_MAX) {
+    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])
+      r.leapcount--;
+  }
+
+  return r;
 }
 
 static void
@@ -1748,8 +1877,6 @@  writezone(const char *const name, const char *const string, char version,
 {
 	register FILE *			fp;
 	register ptrdiff_t		i, j;
-	register int			leapcnt32, leapi32;
-	register ptrdiff_t		timecnt32, timei32;
 	register int			pass;
 	static const struct tzhead	tzh0;
 	static struct tzhead		tzh;
@@ -1764,6 +1891,7 @@  writezone(const char *const name, const char *const string, char version,
 				      _Alignof(zic_t)));
 	void *typesptr = ats + nats;
 	unsigned char *types = typesptr;
+	struct timerange rangeall, range32, range64;
 
 	/*
 	** Sort.
@@ -1779,17 +1907,24 @@  writezone(const char *const name, const char *const string, char version,
 		toi = 0;
 		fromi = 0;
 		for ( ; fromi < timecnt; ++fromi) {
-			if (toi != 0 && ((attypes[fromi].at +
-				gmtoffs[attypes[toi - 1].type]) <=
-				(attypes[toi - 1].at + gmtoffs[toi == 1 ? 0
-				: attypes[toi - 2].type]))) {
+			if (toi != 0
+			    && ((attypes[fromi].at
+				 + utoffs[attypes[toi - 1].type])
+				<= (attypes[toi - 1].at
+				    + utoffs[toi == 1 ? 0
+					     : attypes[toi - 2].type]))) {
 					attypes[toi - 1].type =
 						attypes[fromi].type;
 					continue;
 			}
 			if (toi == 0
 			    || attypes[fromi].dontmerge
-			    || attypes[toi - 1].type != attypes[fromi].type)
+			    || (utoffs[attypes[toi - 1].type]
+				!= utoffs[attypes[fromi].type])
+			    || (isdsts[attypes[toi - 1].type]
+				!= isdsts[attypes[fromi].type])
+			    || (desigidx[attypes[toi - 1].type]
+				!= desigidx[attypes[fromi].type]))
 					attypes[toi++] = attypes[fromi];
 		}
 		timecnt = toi;
@@ -1832,39 +1967,20 @@  writezone(const char *const name, const char *const string, char version,
 	   seconds, as the idea is to insert a transition just before
 	   32-bit 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
+	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++;
 	}
 
-	/*
-	** Figure out 32-bit-limited starts and counts.
-	*/
-	timecnt32 = timecnt;
-	timei32 = 0;
-	leapcnt32 = leapcnt;
-	leapi32 = 0;
-	while (0 < timecnt32 && INT32_MAX < ats[timecnt32 - 1])
-		--timecnt32;
-	while (1 < timecnt32 && ats[timei32] < INT32_MIN
-	       && ats[timei32 + 1] <= INT32_MIN) {
-		/* Discard too-low transitions, except keep any last too-low
-		   transition if no transition is exactly at INT32_MIN.
-		   The kept transition will be output as an INT32_MIN
-		   "transition" appropriate for buggy 32-bit clients that do
-		   not use time type 0 for timestamps before the first
-		   transition; see below.  */
-		--timecnt32;
-		++timei32;
-	}
-	while (0 < leapcnt32 && INT32_MAX < trans[leapcnt32 - 1])
-		--leapcnt32;
-	while (0 < leapcnt32 && trans[leapi32] < INT32_MIN) {
-		--leapcnt32;
-		++leapi32;
-	}
+	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);
+
 	/*
 	** Remove old file, if any, to snap links.
 	*/
@@ -1894,43 +2010,84 @@  writezone(const char *const name, const char *const string, char version,
 	for (pass = 1; pass <= 2; ++pass) {
 		register ptrdiff_t thistimei, thistimecnt, thistimelim;
 		register int	thisleapi, thisleapcnt, thisleaplim;
+		int currenttype, thisdefaulttype;
+		bool locut, hicut;
+		zic_t lo;
 		int old0;
 		char		omittype[TZ_MAX_TYPES];
 		int		typemap[TZ_MAX_TYPES];
-		register int	thistypecnt;
+		int		thistypecnt, stdcnt, utcnt;
 		char		thischars[TZ_MAX_CHARS];
 		int		thischarcnt;
 		bool		toomanytimes;
 		int		indmap[TZ_MAX_CHARS];
 
 		if (pass == 1) {
-			thistimei = timei32;
-			thistimecnt = timecnt32;
+			/* 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);
+
+			thistimei = range32.base;
+			thistimecnt = range32.count;
 			toomanytimes = thistimecnt >> 31 >> 1 != 0;
-			thisleapi = leapi32;
-			thisleapcnt = leapcnt32;
+			thisleapi = range32.leapbase;
+			thisleapcnt = range32.leapcount;
+			locut = INT32_MIN < lo_time;
+			hicut = hi_time < INT32_MAX;
 		} else {
-			thistimei = 0;
-			thistimecnt = timecnt;
+			thisdefaulttype = range64.defaulttype;
+			thistimei = range64.base;
+			thistimecnt = range64.count;
 			toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
-			thisleapi = 0;
-			thisleapcnt = leapcnt;
+			thisleapi = range64.leapbase;
+			thisleapcnt = range64.leapcount;
+			locut = min_time < lo_time;
+			hicut = hi_time < max_time;
 		}
 		if (toomanytimes)
 		  error(_("too many transition times"));
+
+		/* 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.  */
+		if (0 < thistimei && ats[thistimei] != lo_time) {
+		  thistimei--;
+		  thistimecnt++;
+		  locut = 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);
-		omittype[defaulttype] = false;
+		omittype[thisdefaulttype] = false;
 		for (i = thistimei; i < thistimelim; i++)
 		  omittype[types[i]] = false;
 
-		/* Reorder types to make DEFAULTTYPE type 0.
-		   Use TYPEMAP to swap OLD0 and DEFAULTTYPE so that
-		   DEFAULTTYPE appears as type 0 in the output instead
+		/* Reorder types to make THISDEFAULTTYPE type 0.
+		   Use TYPEMAP to swap OLD0 and THISDEFAULTTYPE so that
+		   THISDEFAULTTYPE appears as type 0 in the output instead
 		   of OLD0.  TYPEMAP also omits unused types.  */
 		old0 = strlen(omittype);
-		swaptypes(old0, defaulttype);
 
 #ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
 		/*
@@ -1941,7 +2098,7 @@  writezone(const char *const name, const char *const string, char version,
 		** (to help get global "altzone" and "timezone" variables
 		** set correctly).
 		*/
-		{
+		if (want_bloat()) {
 			register int	mrudst, mrustd, hidst, histd, type;
 
 			hidst = histd = mrudst = mrustd = -1;
@@ -1949,31 +2106,35 @@  writezone(const char *const name, const char *const string, char version,
 				if (isdsts[types[i]])
 					mrudst = types[i];
 				else	mrustd = types[i];
-			for (i = old0; i < typecnt; i++)
-				if (!omittype[i]) {
-					if (isdsts[i])
-						hidst = i;
-					else	histd = i;
-				}
+			for (i = old0; i < typecnt; i++) {
+			  int h = (i == old0 ? thisdefaulttype
+				   : i == thisdefaulttype ? old0 : i);
+			  if (!omittype[h]) {
+			    if (isdsts[h])
+			      hidst = i;
+			    else
+			      histd = i;
+			  }
+			}
 			if (hidst >= 0 && mrudst >= 0 && hidst != mrudst &&
-				gmtoffs[hidst] != gmtoffs[mrudst]) {
+				utoffs[hidst] != utoffs[mrudst]) {
 					isdsts[mrudst] = -1;
-					type = addtype(gmtoffs[mrudst],
-						&chars[abbrinds[mrudst]],
+					type = addtype(utoffs[mrudst],
+						&chars[desigidx[mrudst]],
 						true,
 						ttisstds[mrudst],
-						ttisgmts[mrudst]);
+						ttisuts[mrudst]);
 					isdsts[mrudst] = 1;
 					omittype[type] = false;
 			}
 			if (histd >= 0 && mrustd >= 0 && histd != mrustd &&
-				gmtoffs[histd] != gmtoffs[mrustd]) {
+				utoffs[histd] != utoffs[mrustd]) {
 					isdsts[mrustd] = -1;
-					type = addtype(gmtoffs[mrustd],
-						&chars[abbrinds[mrustd]],
+					type = addtype(utoffs[mrustd],
+						&chars[desigidx[mrustd]],
 						false,
 						ttisstds[mrustd],
-						ttisgmts[mrustd]);
+						ttisuts[mrustd]);
 					isdsts[mrustd] = 0;
 					omittype[type] = false;
 			}
@@ -1982,21 +2143,25 @@  writezone(const char *const name, const char *const string, char version,
 		thistypecnt = 0;
 		for (i = old0; i < typecnt; i++)
 		  if (!omittype[i])
-		    typemap[i == old0 ? defaulttype
-			    : i == defaulttype ? old0 : i]
+		    typemap[i == old0 ? thisdefaulttype
+			    : i == thisdefaulttype ? old0 : i]
 		      = thistypecnt++;
 
 		for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
 			indmap[i] = -1;
-		thischarcnt = 0;
+		thischarcnt = stdcnt = utcnt = 0;
 		for (i = old0; i < typecnt; i++) {
 			register char *	thisabbr;
 
 			if (omittype[i])
 				continue;
-			if (indmap[abbrinds[i]] >= 0)
+			if (ttisstds[i])
+			  stdcnt = thistypecnt;
+			if (ttisuts[i])
+			  utcnt = thistypecnt;
+			if (indmap[desigidx[i]] >= 0)
 				continue;
-			thisabbr = &chars[abbrinds[i]];
+			thisabbr = &chars[desigidx[i]];
 			for (j = 0; j < thischarcnt; ++j)
 				if (strcmp(&thischars[j], thisabbr) == 0)
 					break;
@@ -2004,49 +2169,75 @@  writezone(const char *const name, const char *const string, char version,
 				strcpy(&thischars[thischarcnt], thisabbr);
 				thischarcnt += strlen(thisabbr) + 1;
 			}
-			indmap[abbrinds[i]] = j;
+			indmap[desigidx[i]] = j;
+		}
+		if (pass == 1 && !want_bloat()) {
+		  utcnt = stdcnt = thisleapcnt = 0;
+		  thistimecnt = - (locut + hicut);
+		  thistypecnt = thischarcnt = 1;
+		  thistimelim = thistimei;
 		}
 #define DO(field)	fwrite(tzh.field, sizeof tzh.field, 1, fp)
 		tzh = tzh0;
 		memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
 		tzh.tzh_version[0] = version;
-		convert(thistypecnt, tzh.tzh_ttisgmtcnt);
-		convert(thistypecnt, tzh.tzh_ttisstdcnt);
+		convert(utcnt, tzh.tzh_ttisutcnt);
+		convert(stdcnt, tzh.tzh_ttisstdcnt);
 		convert(thisleapcnt, tzh.tzh_leapcnt);
-		convert(thistimecnt, tzh.tzh_timecnt);
+		convert(locut + thistimecnt + hicut, tzh.tzh_timecnt);
 		convert(thistypecnt, tzh.tzh_typecnt);
 		convert(thischarcnt, tzh.tzh_charcnt);
 		DO(tzh_magic);
 		DO(tzh_version);
 		DO(tzh_reserved);
-		DO(tzh_ttisgmtcnt);
+		DO(tzh_ttisutcnt);
 		DO(tzh_ttisstdcnt);
 		DO(tzh_leapcnt);
 		DO(tzh_timecnt);
 		DO(tzh_typecnt);
 		DO(tzh_charcnt);
 #undef DO
-		for (i = thistimei; i < thistimelim; ++i)
-			if (pass == 1)
-				/*
-				** Output an INT32_MIN "transition"
-				** if appropriate; see above.
-				*/
-				puttzcode(((ats[i] < INT32_MIN) ?
-					INT32_MIN : ats[i]), fp);
-			else	puttzcode64(ats[i], fp);
+		if (pass == 1 && !want_bloat()) {
+		  /* Output a minimal data block with just one time type.  */
+		  puttzcode(0, fp);	/* utoff */
+		  putc(0, fp);		/* dst */
+		  putc(0, fp);		/* index of abbreviation */
+		  putc(0, fp);		/* empty-string abbreviation */
+		  continue;
+		}
+
+		/* 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;
+
+		if (locut)
+		  puttzcodepass(lo, fp, pass);
+		for (i = thistimei; i < thistimelim; ++i) {
+		  zic_t at = ats[i] < lo ? lo : ats[i];
+		  puttzcodepass(at, fp, pass);
+		}
+		if (hicut)
+		  puttzcodepass(hi_time + 1, fp, pass);
+		currenttype = 0;
+		if (locut)
+		  putc(currenttype, fp);
 		for (i = thistimei; i < thistimelim; ++i) {
-			unsigned char	uc;
+		  currenttype = typemap[types[i]];
+		  putc(currenttype, fp);
+		}
+		if (hicut)
+		  putc(currenttype, fp);
 
-			uc = typemap[types[i]];
-			fwrite(&uc, sizeof uc, 1, fp);
+		for (i = old0; i < typecnt; i++) {
+		  int h = (i == old0 ? thisdefaulttype
+			   : i == thisdefaulttype ? old0 : i);
+		  if (!omittype[h]) {
+		    puttzcode(utoffs[h], fp);
+		    putc(isdsts[h], fp);
+		    putc(indmap[desigidx[h]], fp);
+		  }
 		}
-		for (i = old0; i < typecnt; i++)
-			if (!omittype[i]) {
-				puttzcode(gmtoffs[i], fp);
-				putc(isdsts[i], fp);
-				putc((unsigned char) indmap[abbrinds[i]], fp);
-			}
 		if (thischarcnt != 0)
 			fwrite(thischars, sizeof thischars[0],
 				      thischarcnt, fp);
@@ -2068,20 +2259,19 @@  writezone(const char *const name, const char *const string, char version,
 							++j;
 					j = types[j - 1];
 				}
-				todo = tadd(trans[i], -gmtoffs[j]);
+				todo = tadd(trans[i], -utoffs[j]);
 			} else	todo = trans[i];
-			if (pass == 1)
-				puttzcode(todo, fp);
-			else	puttzcode64(todo, fp);
+			puttzcodepass(todo, fp, pass);
 			puttzcode(corr[i], fp);
 		}
-		for (i = old0; i < typecnt; i++)
+		if (stdcnt != 0)
+		  for (i = old0; i < typecnt; i++)
 			if (!omittype[i])
 				putc(ttisstds[i], fp);
-		for (i = old0; i < typecnt; i++)
+		if (utcnt != 0)
+		  for (i = old0; i < typecnt; i++)
 			if (!omittype[i])
-				putc(ttisgmts[i], fp);
-		swaptypes(old0, defaulttype);
+				putc(ttisuts[i], fp);
 	}
 	fprintf(fp, "\n%s\n", string);
 	close_file(fp, directory, name);
@@ -2126,7 +2316,7 @@  abbroffset(char *buf, zic_t offset)
 
 static size_t
 doabbr(char *abbr, struct zone const *zp, char const *letters,
-       bool isdst, zic_t stdoff, bool doquotes)
+       bool isdst, zic_t save, bool doquotes)
 {
 	register char *	cp;
 	register char *	slashp;
@@ -2137,7 +2327,7 @@  doabbr(char *abbr, struct zone const *zp, char const *letters,
 	if (slashp == NULL) {
 	  char letterbuf[PERCENT_Z_LEN_BOUND + 1];
 	  if (zp->z_format_specifier == 'z')
-	    letters = abbroffset(letterbuf, zp->z_gmtoff + stdoff);
+	    letters = abbroffset(letterbuf, zp->z_stdoff + save);
 	  else if (!letters)
 	    letters = "%s";
 	  sprintf(abbr, format, letters);
@@ -2202,8 +2392,7 @@  stringoffset(char *result, zic_t offset)
 }
 
 static int
-stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
-	   const zic_t gmtoff)
+stringrule(char *result, struct rule *const rp, zic_t save, zic_t stdoff)
 {
 	register zic_t	tod = rp->r_tod;
 	register int	compat = 0;
@@ -2250,10 +2439,10 @@  stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 		result += sprintf(result, "M%d.%d.%d",
 				  rp->r_month + 1, week, wday);
 	}
-	if (rp->r_todisgmt)
-		tod += gmtoff;
+	if (rp->r_todisut)
+	  tod += stdoff;
 	if (rp->r_todisstd && !rp->r_isdst)
-		tod += dstoff;
+	  tod += save;
 	if (tod != 2 * SECSPERMIN * MINSPERHOUR) {
 		*result++ = '/';
 		if (! stringoffset(result, tod))
@@ -2283,8 +2472,6 @@  rule_cmp(struct rule const *a, struct rule const *b)
 	return a->r_dayofmonth - b->r_dayofmonth;
 }
 
-enum { YEAR_BY_YEAR_ZONE = 1 };
-
 static int
 stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 {
@@ -2301,6 +2488,12 @@  stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	struct rule			stdr, dstr;
 
 	result[0] = '\0';
+
+	/* Internet RFC 8536 section 5.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) {
@@ -2333,31 +2526,23 @@  stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 			if (rule_cmp(stdrp, rp) < 0)
 				stdrp = rp;
 		}
-		/*
-		** Horrid special case: if year is 2037,
-		** presume this is a zone handled on a year-by-year basis;
-		** do not try to apply a rule to the zone.
-		*/
-		if (stdrp != NULL && stdrp->r_hiyear == 2037)
-			return YEAR_BY_YEAR_ZONE;
-
 		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_todisgmt = false;
+			dstr.r_todisstd = dstr.r_todisut = false;
 			dstr.r_isdst = stdrp->r_isdst;
-			dstr.r_stdoff = stdrp->r_stdoff;
+			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_stdoff;
-			stdr.r_todisstd = stdr.r_todisgmt = false;
+			stdr.r_tod = SECSPERDAY + stdrp->r_save;
+			stdr.r_todisstd = stdr.r_todisut = false;
 			stdr.r_isdst = false;
-			stdr.r_stdoff = 0;
+			stdr.r_save = 0;
 			stdr.r_abbrvar
 			  = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
 			dstrp = &dstr;
@@ -2368,7 +2553,7 @@  stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		return -1;
 	abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
 	len = doabbr(result, zp, abbrvar, false, 0, true);
-	offsetlen = stringoffset(result + len, -zp->z_gmtoff);
+	offsetlen = stringoffset(result + len, - zp->z_stdoff);
 	if (! offsetlen) {
 		result[0] = '\0';
 		return -1;
@@ -2377,10 +2562,10 @@  stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	if (dstrp == NULL)
 		return compat;
 	len += doabbr(result + len, zp, dstrp->r_abbrvar,
-		      dstrp->r_isdst, dstrp->r_stdoff, true);
-	if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR) {
+		      dstrp->r_isdst, dstrp->r_save, true);
+	if (dstrp->r_save != SECSPERMIN * MINSPERHOUR) {
 	  offsetlen = stringoffset(result + len,
-				   -(zp->z_gmtoff + dstrp->r_stdoff));
+				   - (zp->z_stdoff + dstrp->r_save));
 	  if (! offsetlen) {
 	    result[0] = '\0';
 	    return -1;
@@ -2388,7 +2573,7 @@  stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	  len += offsetlen;
 	}
 	result[len++] = ',';
-	c = stringrule(result + len, dstrp, dstrp->r_stdoff, zp->z_gmtoff);
+	c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff);
 	if (c < 0) {
 		result[0] = '\0';
 		return -1;
@@ -2397,7 +2582,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_stdoff, zp->z_gmtoff);
+	c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff);
 	if (c < 0) {
 		result[0] = '\0';
 		return -1;
@@ -2415,12 +2600,12 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	register ptrdiff_t		i, j;
 	register bool			usestart, useuntil;
 	register zic_t			starttime, untiltime;
-	register zic_t			gmtoff;
 	register zic_t			stdoff;
+	register zic_t			save;
 	register zic_t			year;
 	register zic_t			startoff;
 	register bool			startttisstd;
-	register bool			startttisgmt;
+	register bool			startttisut;
 	register int			type;
 	register char *			startbuf;
 	register char *			ab;
@@ -2456,7 +2641,7 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	** for noting the need to unconditionally initialize startttisstd.
 	*/
 	startttisstd = false;
-	startttisgmt = false;
+	startttisut = false;
 	min_year = max_year = EPOCH_YEAR;
 	if (leapseen) {
 		updateminmax(leapminyear);
@@ -2481,13 +2666,13 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	*/
 	compat = stringzone(envvar, zpfirst, zonecount);
 	version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION;
-	do_extend = compat < 0 || compat == YEAR_BY_YEAR_ZONE;
+	do_extend = compat < 0;
 	if (noise) {
 		if (!*envvar)
 			warning("%s %s",
 				_("no POSIX environment variable for zone"),
 				zpfirst->z_name);
-		else if (compat != 0 && compat != YEAR_BY_YEAR_ZONE) {
+		else if (compat != 0) {
 			/* Circa-COMPAT clients, and earlier clients, might
 			   not work for this zone when given dates before
 			   1970 or after 2038.  */
@@ -2529,35 +2714,37 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 			max_year = min_year + years_of_observations;
 		}
 	}
-	/*
-	** For the benefit of older systems,
-	** generate data from 1900 through 2038.
-	*/
-	if (min_year > 1900)
-		min_year = 1900;
 	max_year0 = max_year;
-	if (max_year < 2038)
+	if (want_bloat()) {
+	  /* 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;
+	}
+
 	for (i = 0; i < zonecount; ++i) {
+		struct rule *prevrp = NULL;
 		/*
 		** A guess that may well be corrected later.
 		*/
-		stdoff = 0;
+		save = 0;
 		zp = &zpfirst[i];
 		usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
 		useuntil = i < (zonecount - 1);
 		if (useuntil && zp->z_untiltime <= min_time)
 			continue;
-		gmtoff = zp->z_gmtoff;
+		stdoff = zp->z_stdoff;
 		eat(zp->z_filename, zp->z_linenum);
 		*startbuf = '\0';
-		startoff = zp->z_gmtoff;
+		startoff = zp->z_stdoff;
 		if (zp->z_nrules == 0) {
-			stdoff = zp->z_stdoff;
-			doabbr(startbuf, zp, NULL, zp->z_isdst, stdoff, false);
-			type = addtype(oadd(zp->z_gmtoff, stdoff),
+			save = zp->z_save;
+			doabbr(startbuf, zp, NULL, zp->z_isdst, save, false);
+			type = addtype(oadd(zp->z_stdoff, save),
 				startbuf, zp->z_isdst, startttisstd,
-				startttisgmt);
+				startttisut);
 			if (usestart) {
 				addtt(starttime, type);
 				usestart = false;
@@ -2593,16 +2780,16 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				if (useuntil) {
 					/*
 					** Turn untiltime into UT
-					** assuming the current gmtoff and
-					** stdoff values.
+					** assuming the current stdoff and
+					** save values.
 					*/
 					untiltime = zp->z_untiltime;
-					if (!zp->z_untilrule.r_todisgmt)
+					if (!zp->z_untilrule.r_todisut)
 						untiltime = tadd(untiltime,
-							-gmtoff);
+								 -stdoff);
 					if (!zp->z_untilrule.r_todisstd)
 						untiltime = tadd(untiltime,
-							-stdoff);
+								 -save);
 				}
 				/*
 				** Find the rule (of those to do, if any)
@@ -2615,9 +2802,9 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 						continue;
 					eats(zp->z_filename, zp->z_linenum,
 						rp->r_filename, rp->r_linenum);
-					offset = rp->r_todisgmt ? 0 : gmtoff;
+					offset = rp->r_todisut ? 0 : stdoff;
 					if (!rp->r_todisstd)
-						offset = oadd(offset, stdoff);
+						offset = oadd(offset, save);
 					jtime = rp->r_temp;
 					if (jtime == min_time ||
 						jtime == max_time)
@@ -2644,38 +2831,43 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				rp->r_todo = false;
 				if (useuntil && ktime >= untiltime)
 					break;
-				stdoff = rp->r_stdoff;
+				save = rp->r_save;
 				if (usestart && ktime == starttime)
 					usestart = false;
 				if (usestart) {
 					if (ktime < starttime) {
-						startoff = oadd(zp->z_gmtoff,
-							stdoff);
+						startoff = oadd(zp->z_stdoff,
+								save);
 						doabbr(startbuf, zp,
 							rp->r_abbrvar,
 							rp->r_isdst,
-							rp->r_stdoff,
+							rp->r_save,
 							false);
 						continue;
 					}
-					if (*startbuf == '\0' &&
-						startoff == oadd(zp->z_gmtoff,
-						stdoff)) {
+					if (*startbuf == '\0'
+					    && startoff == oadd(zp->z_stdoff,
+								save)) {
 							doabbr(startbuf,
 								zp,
 								rp->r_abbrvar,
 								rp->r_isdst,
-								rp->r_stdoff,
+								rp->r_save,
 								false);
 					}
 				}
 				eats(zp->z_filename, zp->z_linenum,
 					rp->r_filename, rp->r_linenum);
 				doabbr(ab, zp, rp->r_abbrvar,
-				       rp->r_isdst, rp->r_stdoff, false);
-				offset = oadd(zp->z_gmtoff, rp->r_stdoff);
+				       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_todisgmt);
+					rp->r_todisstd, rp->r_todisut);
 				if (defaulttype < 0 && !rp->r_isdst)
 				  defaulttype = type;
 				if (rp->r_hiyear == ZIC_MAX
@@ -2683,6 +2875,7 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 					  && ktime < attypes[lastatmax].at))
 				  lastatmax = timecnt;
 				addtt(ktime, type);
+				prevrp = rp;
 			}
 		}
 		if (usestart) {
@@ -2695,9 +2888,9 @@  outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 			if (*startbuf == '\0')
 error(_("can't determine time zone abbreviation to use just after until time"));
 			else {
-			  bool isdst = startoff != zp->z_gmtoff;
+			  bool isdst = startoff != zp->z_stdoff;
 			  type = addtype(startoff, startbuf, isdst,
-					 startttisstd, startttisgmt);
+					 startttisstd, startttisut);
 			  if (defaulttype < 0 && !isdst)
 			    defaulttype = type;
 			  addtt(starttime, type);
@@ -2708,12 +2901,12 @@  error(_("can't determine time zone abbreviation to use just after until time"));
 		*/
 		if (useuntil) {
 			startttisstd = zp->z_untilrule.r_todisstd;
-			startttisgmt = zp->z_untilrule.r_todisgmt;
+			startttisut = zp->z_untilrule.r_todisut;
 			starttime = zp->z_untiltime;
 			if (!startttisstd)
-				starttime = tadd(starttime, -stdoff);
-			if (!startttisgmt)
-				starttime = tadd(starttime, -gmtoff);
+			  starttime = tadd(starttime, -save);
+			if (!startttisut)
+			  starttime = tadd(starttime, -stdoff);
 		}
 	}
 	if (defaulttype < 0)
@@ -2737,11 +2930,12 @@  error(_("can't determine time zone abbreviation to use just after until time"));
 		xr.r_dycode = DC_DOM;
 		xr.r_dayofmonth = 1;
 		xr.r_tod = 0;
-		for (lastat = &attypes[0], i = 1; i < timecnt; i++)
+		for (lastat = attypes, i = 1; i < timecnt; i++)
 			if (attypes[i].at > lastat->at)
 				lastat = &attypes[i];
-		if (lastat->at < rpytime(&xr, max_year - 1)) {
-			addtt(rpytime(&xr, max_year + 1), lastat->type);
+		if (!lastat || lastat->at < rpytime(&xr, max_year - 1)) {
+			addtt(rpytime(&xr, max_year + 1),
+			      lastat ? lastat->type : defaulttype);
 			attypes[timecnt - 1].dontmerge = true;
 		}
 	}
@@ -2762,20 +2956,28 @@  addtt(zic_t starttime, int type)
 }
 
 static int
-addtype(zic_t gmtoff, char const *abbr, bool isdst, bool ttisstd, bool ttisgmt)
+addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
 {
 	register int	i, j;
 
-	/*
-	** See if there's already an entry for this zone type.
-	** If so, just return its index.
-	*/
-	for (i = 0; i < typecnt; ++i) {
-		if (gmtoff == gmtoffs[i] && isdst == isdsts[i] &&
-			strcmp(abbr, &chars[abbrinds[i]]) == 0 &&
-			ttisstd == ttisstds[i] &&
-			ttisgmt == ttisgmts[i])
-				return i;
+	if (! (-1L - 2147483647L <= utoff && utoff <= 2147483647L)) {
+		error(_("UT offset out of range"));
+		exit(EXIT_FAILURE);
+	}
+	if (!want_bloat())
+	  ttisstd = ttisut = false;
+
+	for (j = 0; j < charcnt; ++j)
+		if (strcmp(&chars[j], abbr) == 0)
+			break;
+	if (j == charcnt)
+		newabbr(abbr);
+	else {
+	  /* If there's already an entry, return its index.  */
+	  for (i = 0; i < typecnt; i++)
+	    if (utoff == utoffs[i] && isdst == isdsts[i] && j == desigidx[i]
+		&& ttisstd == ttisstds[i] && ttisut == ttisuts[i])
+	      return i;
 	}
 	/*
 	** There isn't one; add a new one, unless there are already too
@@ -2785,48 +2987,34 @@  addtype(zic_t gmtoff, char const *abbr, bool isdst, bool ttisstd, bool ttisgmt)
 		error(_("too many local time types"));
 		exit(EXIT_FAILURE);
 	}
-	if (! (-1L - 2147483647L <= gmtoff && gmtoff <= 2147483647L)) {
-		error(_("UT offset out of range"));
-		exit(EXIT_FAILURE);
-	}
-	gmtoffs[i] = gmtoff;
+	i = typecnt++;
+	utoffs[i] = utoff;
 	isdsts[i] = isdst;
 	ttisstds[i] = ttisstd;
-	ttisgmts[i] = ttisgmt;
-
-	for (j = 0; j < charcnt; ++j)
-		if (strcmp(&chars[j], abbr) == 0)
-			break;
-	if (j == charcnt)
-		newabbr(abbr);
-	abbrinds[i] = j;
-	++typecnt;
+	ttisuts[i] = ttisut;
+	desigidx[i] = j;
 	return i;
 }
 
 static void
-leapadd(zic_t t, bool positive, int rolling, int count)
+leapadd(zic_t t, int correction, int rolling)
 {
-	register int	i, j;
+	register int i;
 
-	if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS) {
+	if (TZ_MAX_LEAPS <= leapcnt) {
 		error(_("too many leap seconds"));
 		exit(EXIT_FAILURE);
 	}
 	for (i = 0; i < leapcnt; ++i)
 		if (t <= trans[i])
 			break;
-	do {
-		for (j = leapcnt; j > i; --j) {
-			trans[j] = trans[j - 1];
-			corr[j] = corr[j - 1];
-			roll[j] = roll[j - 1];
-		}
-		trans[i] = t;
-		corr[i] = positive ? 1 : -count;
-		roll[i] = rolling;
-		++leapcnt;
-	} while (positive && --count != 0);
+	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;
+	++leapcnt;
 }
 
 static void
@@ -2848,6 +3036,22 @@  adjleap(void)
 		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\""));
+	}
+
+	if (0 <= leapexpires) {
+	  leapexpires = oadd(leapexpires, last);
+	  if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) {
+	    error(_("last Leap time does not precede Expires time"));
+	    exit(EXIT_FAILURE);
+	  }
+	  if (leapexpires <= hi_time)
+	    hi_time = leapexpires - 1;
+	}
 }
 
 static char *
@@ -3020,8 +3224,8 @@  byword(const char *word, const struct lookup *table)
 			else	return NULL;	/* multiple inexact matches */
 		}
 
-	/* Warn about any backward-compatibility issue with pre-2017c zic.  */
-	if (foundlp) {
+	if (foundlp && noise) {
+	  /* Warn about any backward-compatibility issue with pre-2017c zic.  */
 	  bool pre_2017c_match = false;
 	  for (lp = table; lp->l_word; lp++)
 	    if (itsabbr(word, lp->l_word)) {