about summary refs log tree commit diff
path: root/timezone/zic.c
diff options
context:
space:
mode:
Diffstat (limited to 'timezone/zic.c')
-rw-r--r--timezone/zic.c814
1 files changed, 509 insertions, 305 deletions
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)) {