fprintf(stream,
                        _("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n"
                          "\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n"
-                         "\t[ -t localtime-link ] [ -L leapseconds ] [ filename ... ]\n\n"
+                         "\t[ -t localtime-link ] [ -L leapseconds ] [ -r '[@lo][/@hi]' ] \\\n"
+                         "\t[ filename ... ]\n\n"
                          "Report bugs to %s.\n"),
                        progname, progname, PACKAGE_BUGREPORT);
        if (status == EXIT_SUCCESS)
        }
 }
 
+#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);
+
+/* Set the time range of the output to TIMERANGE.
+   Return true if successful.  */
+static bool
+timerange_option(char *timerange)
+{
+       int64           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;
                                k;
        ptrdiff_t       i,
                                j;
+       bool            timerange_given = false;
 
 #ifndef WIN32
        umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
                {
                        usage(stdout, EXIT_SUCCESS);
                }
-       while ((c = getopt(argc, argv, "d:l:L:p:Pst:vy:")) != EOF && c != -1)
+       while ((c = getopt(argc, argv, "d:l:L:p:Pr:st:vy:")) != EOF && c != -1)
                switch (c)
                {
                        default:
                                print_abbrevs = true;
                                print_cutoff = time(NULL);
                                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;
        }
 }
 
-#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)
 }
 
 static void
-puttzcode64(const zic_t val, FILE *const fp)
+puttzcodepass(zic_t val, FILE *fp, int pass)
 {
-       char            buf[8];
+       if (pass == 1)
+               puttzcode(val, fp);
+       else
+       {
+               char            buf[8];
 
-       convert64(val, buf);
-       fwrite(buf, sizeof buf, 1, fp);
+               convert64(val, buf);
+               fwrite(buf, sizeof buf, 1, fp);
+       }
 }
 
 static int
        }
 }
 
+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)
+{
+       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
 writezone(const char *const name, const char *const string, char version,
                  int defaulttype)
        FILE       *fp;
        ptrdiff_t       i,
                                j;
-       int                     leapcnt32,
-                               leapi32;
-       ptrdiff_t       timecnt32,
-                               timei32;
        int                     pass;
        static const struct tzhead tzh0;
        static struct tzhead tzh;
        zic_t      *ats = emalloc(MAXALIGN(size_product(nats, sizeof *ats + 1)));
        void       *typesptr = ats + nats;
        unsigned char *types = typesptr;
+       struct timerange rangeall,
+                               range32,
+                               range64;
 
        /*
         * Sort.
                timecnt++;
        }
 
-       /*
-        * Figure out 32-bit-limited starts and counts.
-        */
-       timecnt32 = timecnt;
-       timei32 = 0;
-       leapcnt32 = leapcnt;
-       leapi32 = 0;
-       while (0 < timecnt32 && PG_INT32_MAX < ats[timecnt32 - 1])
-               --timecnt32;
-       while (1 < timecnt32 && ats[timei32] < PG_INT32_MIN
-                  && ats[timei32 + 1] <= PG_INT32_MIN)
-       {
-               /*
-                * Discard too-low transitions, except keep any last too-low
-                * transition if no transition is exactly at PG_INT32_MIN. The kept
-                * transition will be output as an PG_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 && PG_INT32_MAX < trans[leapcnt32 - 1])
-               --leapcnt32;
-       while (0 < leapcnt32 && trans[leapi32] < PG_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, PG_INT32_MIN, PG_INT32_MAX, ats, types);
 
        /*
         * Remove old file, if any, to snap links.
                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];
 
                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
+                        * PG_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 <= PG_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 = PG_INT32_MIN < lo_time;
+                       hicut = hi_time < PG_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 of OLD0.  TYPEMAP also omits unused types.
+                * 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);
+               swaptypes(old0, thisdefaulttype);
 
 #ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
 
                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)
                convert(thistypecnt, tzh.tzh_ttisgmtcnt);
                convert(thistypecnt, 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);
                        }
                }
 
-               for (i = thistimei; i < thistimelim; ++i)
-                       if (pass == 1)
+               /*
+                * 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 < PG_INT32_MIN ? PG_INT32_MIN : lo_time;
 
-                               /*
-                                * Output an PG_INT32_MIN "transition" if appropriate; see
-                                * above.
-                                */
-                               puttzcode(((ats[i] < PG_INT32_MIN) ?
-                                                  PG_INT32_MIN : ats[i]), fp);
-                       else
-                               puttzcode64(ats[i], fp);
+               if (locut)
+                       puttzcodepass(lo, fp, pass);
                for (i = thistimei; i < thistimelim; ++i)
                {
-                       unsigned char uc;
+                       zic_t           at = ats[i] < lo ? lo : ats[i];
 
-                       uc = typemap[types[i]];
-                       fwrite(&uc, sizeof uc, 1, fp);
+                       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)
+               {
+                       currenttype = typemap[types[i]];
+                       putc(currenttype, fp);
+               }
+               if (hicut)
+                       putc(currenttype, fp);
+
                for (i = old0; i < typecnt; i++)
                        if (!omittype[i])
                        {
                        }
                        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++)
                for (i = old0; i < typecnt; i++)
                        if (!omittype[i])
                                putc(ttisgmts[i], fp);
-               swaptypes(old0, defaulttype);
+               swaptypes(old0, thisdefaulttype);
        }
        fprintf(fp, "\n%s\n", string);
        close_file(fp, directory, name);
                                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)
                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))
+               if (!lastat || lastat->at < rpytime(&xr, max_year - 1))
                {
-                       addtt(rpytime(&xr, max_year + 1), typecnt - 1);
+                       addtt(rpytime(&xr, max_year + 1),
+                                 lastat ? lastat->type : defaulttype);
                        attypes[timecnt - 1].dontmerge = true;
                }
        }