]> granicus.if.org Git - postgresql/commitdiff
Sync our copy of the timezone library with IANA release tzcode2016c.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 28 Mar 2016 19:10:17 +0000 (15:10 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 28 Mar 2016 19:10:17 +0000 (15:10 -0400)
We hadn't done this in about six years, which proves to have been a mistake
because there's been a lot of code churn upstream, making the merge rather
painful.  But putting it off any further isn't going to lessen the pain,
and there are at least two incompatible changes that we need to absorb
before someone starts complaining that --with-system-tzdata doesn't work
at all on their platform, or we get blindsided by a tzdata release that
our out-of-date zic can't compile.  Last week's "time zone abbreviation
differs from POSIX standard" mess was a wake-up call in that regard.

This is a sufficiently large patch that I'm afraid to back-patch it
immediately, though the foregoing considerations imply that we probably
should do so eventually.  For the moment, just put it in HEAD so that
it can get some testing.  Maybe we can wait till the end of the 9.6
beta cycle before deeming it okay.

src/timezone/Makefile
src/timezone/README
src/timezone/ialloc.c [deleted file]
src/timezone/localtime.c
src/timezone/pgtz.c
src/timezone/pgtz.h
src/timezone/private.h
src/timezone/scheck.c [deleted file]
src/timezone/strftime.c
src/timezone/tzfile.h
src/timezone/zic.c

index a289050d82cb98b18c8af6bd0afdd14bd7f5b46f..bed5727f532b7d3b6777c97770e0e8fb4f9de753 100644 (file)
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS= localtime.o strftime.o pgtz.o
 
 # files needed to build zic utility program
-ZICOBJS= zic.o ialloc.o scheck.o localtime.o $(WIN32RES)
+ZICOBJS= zic.o $(WIN32RES)
 
 # timezone data files
 TZDATA = africa antarctica asia australasia europe northamerica southamerica \
index 98d98f235f36183f23e81caf4899e8fe2f6a54ee..1df4ce1f69a1c2d26d8ceba471efd966b575c254 100644 (file)
@@ -1,26 +1,24 @@
 src/timezone/README
 
-Timezone
-========
+This is a PostgreSQL adapted version of the IANA timezone library from
 
-This is a PostgreSQL adapted version of the timezone library from
-http://www.iana.org/time-zones
+       http://www.iana.org/time-zones
 
-The source code can be found at:
+The latest versions of both the tzdata and tzcode tarballs are normally
+available right from that page.  Historical versions can be found
+elsewhere on the site.
 
-       ftp://ftp.iana.org/tz/releases/tzcode*.tar.gz
-
-The code is currently synced with release 2010c.  There are many cosmetic
-(and not so cosmetic) differences from the original tzcode library, but
-diffs in the upstream version should usually be propagated to our version.
+Since time zone rules change frequently in some parts of the world,
+we should endeavor to update the data files before each PostgreSQL
+release.  The code need not be updated as often, but we must track
+changes that might affect interpretation of the data files.
 
-The data files under data/ are an exact copy of the latest data set from:
 
-       ftp://ftp.iana.org/tz/releases/tzdata*.tar.gz
+Time Zone data
+==============
 
-Since time zone rules change frequently in some parts of the world,
-we should endeavor to update the data files before each PostgreSQL
-release.
+The data files under data/ are an exact copy of the latest tzdata set,
+except that we omit some files that are not of interest for our purposes.
 
 While the files under data/ can just be duplicated when updating, manual
 effort is needed to update the time zone abbreviation lists under tznames/.
@@ -32,7 +30,9 @@ which will produce a file showing all abbreviations that are in current
 use according to the data/ files.  Compare this to known_abbrevs.txt,
 which is the list that existed last time the tznames/ files were updated.
 Update tznames/ as seems appropriate, then replace known_abbrevs.txt
-in the same commit.
+in the same commit.  Usually, if a known abbreviation has changed meaning,
+the appropriate fix is to make it refer to a long-form zone name instead
+of a fixed GMT offset.
 
 When there has been a new release of Windows (probably including Service
 Packs), the list of matching timezones need to be updated. Run the
@@ -40,3 +40,64 @@ script in src/tools/win32tzlist.pl on a Windows machine running this new
 release and apply any new timezones that it detects. Never remove any
 mappings in case they are removed in Windows, since we still need to
 match properly on the old version.
+
+
+Time Zone code
+==============
+
+The code in this directory is currently synced with tzcode release 2016c.
+There are many cosmetic (and not so cosmetic) differences from the
+original tzcode library, but diffs in the upstream version should usually
+be propagated to our version.  Here are some notes about that.
+
+For the most part we want to use the upstream code as-is, but there are
+several considerations preventing an exact match:
+
+* For readability/maintainability we reformat the code to match our own
+conventions; this includes pgindent'ing it and getting rid of upstream's
+overuse of "register" declarations.  (It used to include conversion of
+old-style function declarations to C89 style, but thank goodness they
+fixed that.)
+
+* We need the code to follow Postgres' portability conventions; this
+includes relying on configure's results rather than hand-hacked #defines,
+and not relying on <stdint.h> features that may not exist on old systems.
+(In particular this means using Postgres' definitions of the int32 and
+int64 typedefs, not int_fast32_t/int_fast64_t.)
+
+* Since Postgres is typically built on a system that has its own copy
+of the <time.h> functions, we must avoid conflicting with those.  This
+mandates renaming typedef time_t to pg_time_t, and similarly for most
+other exposed names.
+
+* We have exposed the tzload() and tzparse() internal functions, and
+slightly modified the API of the former, in part because it now relies
+on our own pg_open_tzfile() rather than opening files for itself.
+
+* There's a fair amount of code we don't need and have removed,
+including all the nonstandard optional APIs.  We have also added
+a few functions of our own at the bottom of localtime.c.
+
+* In zic.c, we have added support for a -P (print_abbrevs) switch, which
+is used to create the "abbrevs.txt" summary of currently-in-use zone
+abbreviations that was described above.
+
+
+The most convenient way to compare a new tzcode release to our code is
+to first run the tzcode source files through a sed filter like this:
+
+    sed -r \
+        -e 's/^([ \t]*)\*\*([ \t])/\1 *\2/' \
+        -e 's/^([ \t]*)\*\*$/\1 */' \
+        -e 's|^\*/| */|' \
+        -e 's/\bregister[ \t]//g' \
+        -e 's/int_fast32_t/int32/g' \
+        -e 's/int_fast64_t/int64/g' \
+        -e 's/struct[ \t]+tm\b/struct pg_tm/g' \
+        -e 's/\btime_t\b/pg_time_t/g' \
+
+and then run them through pgindent.  (The first three sed patterns deal
+with conversion of their block comment style to something pgindent
+won't make a hash of; the remainder address other points noted above.)
+After that, the files can be diff'd directly against our corresponding
+files.
diff --git a/src/timezone/ialloc.c b/src/timezone/ialloc.c
deleted file mode 100644 (file)
index 05faafe..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * This file is in the public domain, so clarified as of
- * 2006-07-17 by Arthur David Olson.
- *
- * IDENTIFICATION
- *       src/timezone/ialloc.c
- */
-
-#include "postgres_fe.h"
-
-#include "private.h"
-
-
-#define nonzero(n)     (((n) == 0) ? 1 : (n))
-
-char *
-imalloc(int n)
-{
-       return malloc((size_t) nonzero(n));
-}
-
-char *
-icalloc(int nelem, int elsize)
-{
-       if (nelem == 0 || elsize == 0)
-               nelem = elsize = 1;
-       return calloc((size_t) nelem, (size_t) elsize);
-}
-
-void *
-irealloc(void *pointer, int size)
-{
-       if (pointer == NULL)
-               return imalloc(size);
-       return realloc((void *) pointer, (size_t) nonzero(size));
-}
-
-char *
-icatalloc(char *old, const char *new)
-{
-       char       *result;
-       int                     oldsize,
-                               newsize;
-
-       newsize = (new == NULL) ? 0 : strlen(new);
-       if (old == NULL)
-               oldsize = 0;
-       else if (newsize == 0)
-               return old;
-       else
-               oldsize = strlen(old);
-       if ((result = irealloc(old, oldsize + newsize + 1)) != NULL)
-               if (new != NULL)
-                       (void) strcpy(result + oldsize, new);
-       return result;
-}
-
-char *
-icpyalloc(const char *string)
-{
-       return icatalloc((char *) NULL, string);
-}
-
-void
-ifree(char *p)
-{
-       if (p != NULL)
-               (void) free(p);
-}
-
-void
-icfree(char *p)
-{
-       if (p != NULL)
-               (void) free(p);
-}
index 19a24e1d960d9aab0fc2c41013c80e7da0c69f5e..23e022695fa191b4ec33cd33fc12276219f310ab 100644 (file)
@@ -23,7 +23,7 @@
 
 
 #ifndef WILDABBR
-/*----------
+/*
  * Someone might make incorrect use of a time zone abbreviation:
  *     1.  They might reference tzname[0] before calling tzset (explicitly
  *             or implicitly).
  *     5.  They might reference tm.TM_ZONE after calling offtime.
  * What's best to do in the above cases is open to debate;
  * for now, we just set things up so that in any of the five cases
- * WILDABBR is used. Another possibility:      initialize tzname[0] to the
+ * WILDABBR is used. Another possibility: initialize tzname[0] to the
  * string "tzname[0] used before set", and similarly for the other cases.
  * And another: initialize tzname[0] to "ERA", with an explanation in the
  * manual page of what this "time zone abbreviation" means (doing this so
  * that tzname[0] has the "normal" length of three characters).
- *----------
  */
 #define WILDABBR       "   "
 #endif   /* !defined WILDABBR */
 
-static char wildabbr[] = WILDABBR;
+static const char wildabbr[] = WILDABBR;
 
 static const char gmt[] = "GMT";
 
+/* The minimum and maximum finite time values.  This assumes no padding.  */
+static const pg_time_t time_t_min = MINVAL(pg_time_t, TYPE_BIT(pg_time_t));
+static const pg_time_t time_t_max = MAXVAL(pg_time_t, TYPE_BIT(pg_time_t));
+
 /*
  * The DST rules to use if TZ has no rules and we can't load TZDEFRULES.
  * We default to US rules as of 1999-08-17.
@@ -59,51 +62,35 @@ static const char gmt[] = "GMT";
  */
 #define TZDEFRULESTRING ",M4.1.0,M10.5.0"
 
+/* structs ttinfo, lsinfo, state have been moved to pgtz.h */
+
+enum r_type
+{
+       JULIAN_DAY,                                     /* Jn = Julian day */
+       DAY_OF_YEAR,                            /* n = day of year */
+       MONTH_NTH_DAY_OF_WEEK           /* Mm.n.d = month, week, day of week */
+};
+
 struct rule
 {
-       int                     r_type;                 /* type of rule--see below */
+       enum r_type r_type;                     /* type of rule */
        int                     r_day;                  /* day number of rule */
        int                     r_week;                 /* week number of rule */
        int                     r_mon;                  /* month number of rule */
-       long            r_time;                 /* transition time of rule */
+       int32           r_time;                 /* transition time of rule */
 };
 
-#define JULIAN_DAY             0               /* Jn - Julian day */
-#define DAY_OF_YEAR            1               /* n - day of year */
-#define MONTH_NTH_DAY_OF_WEEK  2               /* Mm.n.d - month, week, day of week */
-
 /*
  * Prototypes for static functions.
  */
 
-static long detzcode(const char *codep);
-static pg_time_t detzcode64(const char *codep);
-static int     differ_by_repeat(pg_time_t t1, pg_time_t t0);
-static const char *getzname(const char *strp);
-static const char *getqzname(const char *strp, int delim);
-static const char *getnum(const char *strp, int *nump, int min, int max);
-static const char *getsecs(const char *strp, long *secsp);
-static const char *getoffset(const char *strp, long *offsetp);
-static const char *getrule(const char *strp, struct rule * rulep);
-static void gmtload(struct state * sp);
-static struct pg_tm *gmtsub(const pg_time_t *timep, long offset,
-          struct pg_tm * tmp);
-static struct pg_tm *localsub(const pg_time_t *timep, long offset,
-                struct pg_tm * tmp, const pg_tz *tz);
-static int     increment_overflow(int *number, int delta);
-static pg_time_t transtime(pg_time_t janfirst, int year,
-                 const struct rule * rulep, long offset);
-static int     typesequiv(const struct state * sp, int a, int b);
-static struct pg_tm *timesub(const pg_time_t *timep, long offset,
-               const struct state * sp, struct pg_tm * tmp);
-
-/* GMT timezone */
-static struct state gmtmem;
+static struct pg_tm *gmtsub(pg_time_t const *, int32, struct pg_tm *);
+static bool increment_overflow(int *, int);
+static bool increment_overflow_time(pg_time_t *, int32);
+static struct pg_tm *timesub(pg_time_t const *, int32, struct state const *,
+               struct pg_tm *);
+static bool typesequiv(struct state const *, int, int);
 
-#define gmtptr         (&gmtmem)
-
-
-static int     gmt_is_set = 0;
 
 /*
  * Section 4.12.3 of X3.159-1989 requires that
@@ -115,147 +102,281 @@ static int      gmt_is_set = 0;
 
 static struct pg_tm tm;
 
+/* Initialize *S to a value based on GMTOFF, ISDST, and ABBRIND.  */
+static void
+init_ttinfo(struct ttinfo * s, int32 gmtoff, bool isdst, int abbrind)
+{
+       s->tt_gmtoff = gmtoff;
+       s->tt_isdst = isdst;
+       s->tt_abbrind = abbrind;
+       s->tt_ttisstd = false;
+       s->tt_ttisgmt = false;
+}
 
-static long
+static int32
 detzcode(const char *codep)
 {
-       long            result;
+       int32           result;
        int                     i;
+       int32           one = 1;
+       int32           halfmaxval = one << (32 - 2);
+       int32           maxval = halfmaxval - 1 + halfmaxval;
+       int32           minval = -1 - maxval;
 
-       result = (codep[0] & 0x80) ? ~0L : 0;
-       for (i = 0; i < 4; ++i)
+       result = codep[0] & 0x7f;
+       for (i = 1; i < 4; ++i)
                result = (result << 8) | (codep[i] & 0xff);
+
+       if (codep[0] & 0x80)
+       {
+               /*
+                * Do two's-complement negation even on non-two's-complement machines.
+                * If the result would be minval - 1, return minval.
+                */
+               result -= !TWOS_COMPLEMENT(int32) &&result != 0;
+               result += minval;
+       }
        return result;
 }
 
-static pg_time_t
+static int64
 detzcode64(const char *codep)
 {
-       pg_time_t       result;
+       uint64          result;
        int                     i;
+       int64           one = 1;
+       int64           halfmaxval = one << (64 - 2);
+       int64           maxval = halfmaxval - 1 + halfmaxval;
+       int64           minval = -TWOS_COMPLEMENT(int64) -maxval;
+
+       result = codep[0] & 0x7f;
+       for (i = 1; i < 8; ++i)
+               result = (result << 8) | (codep[i] & 0xff);
 
-       result = (codep[0] & 0x80) ? (~(int64) 0) : 0;
-       for (i = 0; i < 8; ++i)
-               result = result * 256 + (codep[i] & 0xff);
+       if (codep[0] & 0x80)
+       {
+               /*
+                * Do two's-complement negation even on non-two's-complement machines.
+                * If the result would be minval - 1, return minval.
+                */
+               result -= !TWOS_COMPLEMENT(int64) &&result != 0;
+               result += minval;
+       }
        return result;
 }
 
-static int
-differ_by_repeat(pg_time_t t1, pg_time_t t0)
+static bool
+differ_by_repeat(const pg_time_t t1, const pg_time_t t0)
 {
-       if (TYPE_INTEGRAL(pg_time_t) &&
-               TYPE_BIT(pg_time_t) -TYPE_SIGNED(pg_time_t) <SECSPERREPEAT_BITS)
+       if (TYPE_BIT(pg_time_t) -TYPE_SIGNED(pg_time_t) <SECSPERREPEAT_BITS)
                return 0;
        return t1 - t0 == SECSPERREPEAT;
 }
 
-int
-tzload(const char *name, char *canonname, struct state * sp, int doextend)
+/* Input buffer for data read from a compiled tz file.  */
+union input_buffer
+{
+       /* The first part of the buffer, interpreted as a header.  */
+       struct tzhead tzhead;
+
+       /* The entire buffer.  */
+       char            buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state)
+                                       +                       4 * TZ_MAX_TIMES];
+};
+
+/* Local storage needed for 'tzloadbody'.  */
+union local_storage
+{
+       /* We don't need the "fullname" member */
+
+       /* The results of analyzing the file's contents after it is opened.  */
+       struct
+       {
+               /* The input buffer.  */
+               union input_buffer u;
+
+               /* A temporary state used for parsing a TZ string in the file.  */
+               struct state st;
+       }                       u;
+};
+
+/* Load tz data from the file named NAME into *SP.  Read extended
+ * format if DOEXTEND.  Use *LSP for temporary storage.  Return 0 on
+ * success, an errno value on failure.
+ * PG: If "canonname" is not NULL, then on success the canonical spelling of
+ * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
+ */
+static int
+tzloadbody(char const * name, char *canonname, struct state * sp, bool doextend,
+                  union local_storage * lsp)
 {
-       const char *p;
        int                     i;
        int                     fid;
        int                     stored;
-       int                     nread;
-       union
+       ssize_t         nread;
+       union input_buffer *up = &lsp->u.u;
+       int                     tzheadsize = sizeof(struct tzhead);
+
+       sp->goback = sp->goahead = false;
+
+       if (!name)
        {
-               struct tzhead tzhead;
-               char            buf[2 * sizeof(struct tzhead) +
-                                                                       2 * sizeof *sp +
-                                                                       4 * TZ_MAX_TIMES];
-       }                       u;
+               name = TZDEFAULT;
+               if (!name)
+                       return EINVAL;
+       }
 
-       sp->goback = sp->goahead = FALSE;
-       if (name == NULL && (name = TZDEFAULT) == NULL)
-               return -1;
        if (name[0] == ':')
                ++name;
+
        fid = pg_open_tzfile(name, canonname);
        if (fid < 0)
-               return -1;
-       nread = read(fid, u.buf, sizeof u.buf);
-       if (close(fid) != 0 || nread <= 0)
-               return -1;
+               return ENOENT;                  /* pg_open_tzfile may not set errno */
+
+       nread = read(fid, up->buf, sizeof up->buf);
+       if (nread < tzheadsize)
+       {
+               int                     err = nread < 0 ? errno : EINVAL;
+
+               close(fid);
+               return err;
+       }
+       if (close(fid) < 0)
+               return errno;
        for (stored = 4; stored <= 8; stored *= 2)
        {
-               int                     ttisstdcnt;
-               int                     ttisgmtcnt;
-
-               ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt);
-               ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt);
-               sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt);
-               sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt);
-               sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt);
-               sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt);
-               p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt;
-               if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
-                       sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
-                       sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
-                       sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
-                       (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
-                       (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
-                       return -1;
-               if (nread - (p - u.buf) <
-                       sp->timecnt * stored +          /* ats */
-                       sp->timecnt +           /* types */
-                       sp->typecnt * 6 +       /* ttinfos */
-                       sp->charcnt +           /* chars */
-                       sp->leapcnt * (stored + 4) +            /* lsinfos */
-                       ttisstdcnt +            /* ttisstds */
-                       ttisgmtcnt)                     /* ttisgmts */
-                       return -1;
+               int32           ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
+               int32           ttisgmtcnt = detzcode(up->tzhead.tzh_ttisgmtcnt);
+               int32           leapcnt = detzcode(up->tzhead.tzh_leapcnt);
+               int32           timecnt = detzcode(up->tzhead.tzh_timecnt);
+               int32           typecnt = detzcode(up->tzhead.tzh_typecnt);
+               int32           charcnt = detzcode(up->tzhead.tzh_charcnt);
+               char const *p = up->buf + tzheadsize;
+
+               if (!(0 <= leapcnt && leapcnt < TZ_MAX_LEAPS
+                         && 0 < typecnt && typecnt < TZ_MAX_TYPES
+                         && 0 <= timecnt && timecnt < TZ_MAX_TIMES
+                         && 0 <= charcnt && charcnt < TZ_MAX_CHARS
+                         && (ttisstdcnt == typecnt || ttisstdcnt == 0)
+                         && (ttisgmtcnt == typecnt || ttisgmtcnt == 0)))
+                       return EINVAL;
+               if (nread
+                       < (tzheadsize           /* struct tzhead */
+                          + timecnt * stored           /* ats */
+                          + timecnt            /* types */
+                          + typecnt * 6        /* ttinfos */
+                          + charcnt            /* chars */
+                          + leapcnt * (stored + 4) /* lsinfos */
+                          + ttisstdcnt         /* ttisstds */
+                          + ttisgmtcnt))       /* ttisgmts */
+                       return EINVAL;
+               sp->leapcnt = leapcnt;
+               sp->timecnt = timecnt;
+               sp->typecnt = typecnt;
+               sp->charcnt = charcnt;
+
+               /*
+                * Read transitions, discarding those out of pg_time_t range. But
+                * pretend the last transition before time_t_min occurred at
+                * time_t_min.
+                */
+               timecnt = 0;
                for (i = 0; i < sp->timecnt; ++i)
                {
-                       sp->ats[i] = (stored == 4) ? detzcode(p) : detzcode64(p);
+                       int64           at
+                       = stored == 4 ? detzcode(p) : detzcode64(p);
+
+                       sp->types[i] = at <= time_t_max;
+                       if (sp->types[i])
+                       {
+                               pg_time_t       attime
+                               = ((TYPE_SIGNED(pg_time_t) ? at < time_t_min : at < 0)
+                                  ? time_t_min : at);
+
+                               if (timecnt && attime <= sp->ats[timecnt - 1])
+                               {
+                                       if (attime < sp->ats[timecnt - 1])
+                                               return EINVAL;
+                                       sp->types[i - 1] = 0;
+                                       timecnt--;
+                               }
+                               sp->ats[timecnt++] = attime;
+                       }
                        p += stored;
                }
+
+               timecnt = 0;
                for (i = 0; i < sp->timecnt; ++i)
                {
-                       sp->types[i] = (unsigned char) *p++;
-                       if (sp->types[i] >= sp->typecnt)
-                               return -1;
+                       unsigned char typ = *p++;
+
+                       if (sp->typecnt <= typ)
+                               return EINVAL;
+                       if (sp->types[i])
+                               sp->types[timecnt++] = typ;
                }
+               sp->timecnt = timecnt;
                for (i = 0; i < sp->typecnt; ++i)
                {
                        struct ttinfo *ttisp;
+                       unsigned char isdst,
+                                               abbrind;
 
                        ttisp = &sp->ttis[i];
                        ttisp->tt_gmtoff = detzcode(p);
                        p += 4;
-                       ttisp->tt_isdst = (unsigned char) *p++;
-                       if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
-                               return -1;
-                       ttisp->tt_abbrind = (unsigned char) *p++;
-                       if (ttisp->tt_abbrind < 0 ||
-                               ttisp->tt_abbrind > sp->charcnt)
-                               return -1;
+                       isdst = *p++;
+                       if (!(isdst < 2))
+                               return EINVAL;
+                       ttisp->tt_isdst = isdst;
+                       abbrind = *p++;
+                       if (!(abbrind < sp->charcnt))
+                               return EINVAL;
+                       ttisp->tt_abbrind = abbrind;
                }
                for (i = 0; i < sp->charcnt; ++i)
                        sp->chars[i] = *p++;
                sp->chars[i] = '\0';    /* ensure '\0' at end */
+
+               /* Read leap seconds, discarding those out of pg_time_t range.  */
+               leapcnt = 0;
                for (i = 0; i < sp->leapcnt; ++i)
                {
-                       struct lsinfo *lsisp;
+                       int64           tr = stored == 4 ? detzcode(p) : detzcode64(p);
+                       int32           corr = detzcode(p + stored);
 
-                       lsisp = &sp->lsis[i];
-                       lsisp->ls_trans = (stored == 4) ? detzcode(p) : detzcode64(p);
-                       p += stored;
-                       lsisp->ls_corr = detzcode(p);
-                       p += 4;
+                       p += stored + 4;
+                       if (tr <= time_t_max)
+                       {
+                               pg_time_t       trans
+                               = ((TYPE_SIGNED(pg_time_t) ? tr < time_t_min : tr < 0)
+                                  ? time_t_min : tr);
+
+                               if (leapcnt && trans <= sp->lsis[leapcnt - 1].ls_trans)
+                               {
+                                       if (trans < sp->lsis[leapcnt - 1].ls_trans)
+                                               return EINVAL;
+                                       leapcnt--;
+                               }
+                               sp->lsis[leapcnt].ls_trans = trans;
+                               sp->lsis[leapcnt].ls_corr = corr;
+                               leapcnt++;
+                       }
                }
+               sp->leapcnt = leapcnt;
+
                for (i = 0; i < sp->typecnt; ++i)
                {
                        struct ttinfo *ttisp;
 
                        ttisp = &sp->ttis[i];
                        if (ttisstdcnt == 0)
-                               ttisp->tt_ttisstd = FALSE;
+                               ttisp->tt_ttisstd = false;
                        else
                        {
+                               if (*p != true && *p != false)
+                                       return EINVAL;
                                ttisp->tt_ttisstd = *p++;
-                               if (ttisp->tt_ttisstd != TRUE &&
-                                       ttisp->tt_ttisstd != FALSE)
-                                       return -1;
                        }
                }
                for (i = 0; i < sp->typecnt; ++i)
@@ -264,99 +385,87 @@ tzload(const char *name, char *canonname, struct state * sp, int doextend)
 
                        ttisp = &sp->ttis[i];
                        if (ttisgmtcnt == 0)
-                               ttisp->tt_ttisgmt = FALSE;
+                               ttisp->tt_ttisgmt = false;
                        else
                        {
+                               if (*p != true && *p != false)
+                                       return EINVAL;
                                ttisp->tt_ttisgmt = *p++;
-                               if (ttisp->tt_ttisgmt != TRUE &&
-                                       ttisp->tt_ttisgmt != FALSE)
-                                       return -1;
                        }
                }
 
-               /*
-                * Out-of-sort ats should mean we're running on a signed time_t system
-                * but using a data file with unsigned values (or vice versa).
-                */
-               for (i = 0; i < sp->timecnt - 2; ++i)
-                       if (sp->ats[i] > sp->ats[i + 1])
-                       {
-                               ++i;
-                               if (TYPE_SIGNED(pg_time_t))
-                               {
-                                       /*
-                                        * Ignore the end (easy).
-                                        */
-                                       sp->timecnt = i;
-                               }
-                               else
-                               {
-                                       /*
-                                        * Ignore the beginning (harder).
-                                        */
-                                       int                     j;
-
-                                       for (j = 0; j + i < sp->timecnt; ++j)
-                                       {
-                                               sp->ats[j] = sp->ats[j + i];
-                                               sp->types[j] = sp->types[j + i];
-                                       }
-                                       sp->timecnt = j;
-                               }
-                               break;
-                       }
-
                /*
                 * If this is an old file, we're done.
                 */
-               if (u.tzhead.tzh_version[0] == '\0')
-                       break;
-               nread -= p - u.buf;
-               for (i = 0; i < nread; ++i)
-                       u.buf[i] = p[i];
-
-               /*
-                * If this is a narrow integer time_t system, we're done.
-                */
-               if (stored >= (int) sizeof(pg_time_t) && TYPE_INTEGRAL(pg_time_t))
+               if (up->tzhead.tzh_version[0] == '\0')
                        break;
+               nread -= p - up->buf;
+               memmove(up->buf, p, nread);
        }
        if (doextend && nread > 2 &&
-               u.buf[0] == '\n' && u.buf[nread - 1] == '\n' &&
+               up->buf[0] == '\n' && up->buf[nread - 1] == '\n' &&
                sp->typecnt + 2 <= TZ_MAX_TYPES)
        {
-               struct state ts;
-               int                     result;
+               struct state *ts = &lsp->u.st;
 
-               u.buf[nread - 1] = '\0';
-               result = tzparse(&u.buf[1], &ts, FALSE);
-               if (result == 0 && ts.typecnt == 2 &&
-                       sp->charcnt + ts.charcnt <= TZ_MAX_CHARS)
+               up->buf[nread - 1] = '\0';
+               if (tzparse(&up->buf[1], ts, false) == 0
+                       && ts->typecnt == 2)
                {
-                       for (i = 0; i < 2; ++i)
-                               ts.ttis[i].tt_abbrind +=
-                                       sp->charcnt;
-                       for (i = 0; i < ts.charcnt; ++i)
-                               sp->chars[sp->charcnt++] =
-                                       ts.chars[i];
-                       i = 0;
-                       while (i < ts.timecnt &&
-                                  ts.ats[i] <=
-                                  sp->ats[sp->timecnt - 1])
-                               ++i;
-                       while (i < ts.timecnt &&
-                                  sp->timecnt < TZ_MAX_TIMES)
+                       /*
+                        * Attempt to reuse existing abbreviations. Without this,
+                        * America/Anchorage would stop working after 2037 when
+                        * TZ_MAX_CHARS is 50, as sp->charcnt equals 42 (for LMT CAT CAWT
+                        * CAPT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for
+                        * AKST AKDT).  Reusing means sp->charcnt can stay 42 in this
+                        * example.
+                        */
+                       int                     gotabbr = 0;
+                       int                     charcnt = sp->charcnt;
+
+                       for (i = 0; i < 2; i++)
                        {
-                               sp->ats[sp->timecnt] =
-                                       ts.ats[i];
-                               sp->types[sp->timecnt] =
-                                       sp->typecnt +
-                                       ts.types[i];
-                               ++sp->timecnt;
-                               ++i;
+                               char       *tsabbr = ts->chars + ts->ttis[i].tt_abbrind;
+                               int                     j;
+
+                               for (j = 0; j < charcnt; j++)
+                                       if (strcmp(sp->chars + j, tsabbr) == 0)
+                                       {
+                                               ts->ttis[i].tt_abbrind = j;
+                                               gotabbr++;
+                                               break;
+                                       }
+                               if (!(j < charcnt))
+                               {
+                                       int                     tsabbrlen = strlen(tsabbr);
+
+                                       if (j + tsabbrlen < TZ_MAX_CHARS)
+                                       {
+                                               strcpy(sp->chars + j, tsabbr);
+                                               charcnt = j + tsabbrlen + 1;
+                                               ts->ttis[i].tt_abbrind = j;
+                                               gotabbr++;
+                                       }
+                               }
+                       }
+                       if (gotabbr == 2)
+                       {
+                               sp->charcnt = charcnt;
+                               for (i = 0; i < ts->timecnt; i++)
+                                       if (sp->ats[sp->timecnt - 1] < ts->ats[i])
+                                               break;
+                               while (i < ts->timecnt
+                                          && sp->timecnt < TZ_MAX_TIMES)
+                               {
+                                       sp->ats[sp->timecnt] = ts->ats[i];
+                                       sp->types[sp->timecnt] = (sp->typecnt
+                                                                                         + ts->types[i]);
+                                       sp->timecnt++;
+                                       i++;
+                               }
+                               sp->ttis[sp->typecnt++] = ts->ttis[0];
+                               sp->ttis[sp->typecnt++] = ts->ttis[1];
                        }
-                       sp->ttis[sp->typecnt++] = ts.ttis[0];
-                       sp->ttis[sp->typecnt++] = ts.ttis[1];
                }
        }
        if (sp->timecnt > 1)
@@ -365,7 +474,7 @@ tzload(const char *name, char *canonname, struct state * sp, int doextend)
                        if (typesequiv(sp, sp->types[i], sp->types[0]) &&
                                differ_by_repeat(sp->ats[i], sp->ats[0]))
                        {
-                               sp->goback = TRUE;
+                               sp->goback = true;
                                break;
                        }
                for (i = sp->timecnt - 2; i >= 0; --i)
@@ -374,22 +483,73 @@ tzload(const char *name, char *canonname, struct state * sp, int doextend)
                                differ_by_repeat(sp->ats[sp->timecnt - 1],
                                                                 sp->ats[i]))
                        {
-                               sp->goahead = TRUE;
+                               sp->goahead = true;
+                               break;
+                       }
+       }
+
+       /*
+        * If type 0 is is unused in transitions, it's the type to use for early
+        * times.
+        */
+       for (i = 0; i < sp->timecnt; ++i)
+               if (sp->types[i] == 0)
+                       break;
+       i = i < sp->timecnt ? -1 : 0;
+
+       /*
+        * Absent the above, if there are transition times and the first
+        * transition is to a daylight time find the standard type less than and
+        * closest to the type of the first transition.
+        */
+       if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst)
+       {
+               i = sp->types[0];
+               while (--i >= 0)
+                       if (!sp->ttis[i].tt_isdst)
+                               break;
+       }
+
+       /*
+        * If no result yet, find the first standard type. If there is none, punt
+        * to type zero.
+        */
+       if (i < 0)
+       {
+               i = 0;
+               while (sp->ttis[i].tt_isdst)
+                       if (++i >= sp->typecnt)
+                       {
+                               i = 0;
                                break;
                        }
        }
+       sp->defaulttype = i;
        return 0;
 }
 
-static int
+/* Load tz data from the file named NAME into *SP.  Read extended
+ * format if DOEXTEND.  Return 0 on success, an errno value on failure.
+ * PG: If "canonname" is not NULL, then on success the canonical spelling of
+ * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
+ */
+int
+tzload(const char *name, char *canonname, struct state * sp, int doextend)
+{
+       union local_storage ls;
+
+       return tzloadbody(name, canonname, sp, doextend, &ls);
+}
+
+static bool
 typesequiv(const struct state * sp, int a, int b)
 {
-       int                     result;
+       bool            result;
 
        if (sp == NULL ||
                a < 0 || a >= sp->typecnt ||
                b < 0 || b >= sp->typecnt)
-               result = FALSE;
+               result = false;
        else
        {
                const struct ttinfo *ap = &sp->ttis[a];
@@ -484,19 +644,19 @@ getnum(const char *strp, int *nump, int min, int max)
  * of seconds.
  */
 static const char *
-getsecs(const char *strp, long *secsp)
+getsecs(const char *strp, int32 *secsp)
 {
        int                     num;
 
        /*
-        * `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
+        * 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
         * "M10.4.6/26", which does not conform to Posix, but which specifies the
-        * equivalent of ``02:00 on the first Sunday on or after 23 Oct''.
+        * equivalent of "02:00 on the first Sunday on or after 23 Oct".
         */
        strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
        if (strp == NULL)
                return NULL;
-       *secsp = num * (long) SECSPERHOUR;
+       *secsp = num * (int32) SECSPERHOUR;
        if (*strp == ':')
        {
                ++strp;
@@ -507,7 +667,7 @@ getsecs(const char *strp, long *secsp)
                if (*strp == ':')
                {
                        ++strp;
-                       /* `SECSPERMIN' allows for leap seconds. */
+                       /* 'SECSPERMIN' allows for leap seconds.  */
                        strp = getnum(strp, &num, 0, SECSPERMIN);
                        if (strp == NULL)
                                return NULL;
@@ -524,13 +684,13 @@ getsecs(const char *strp, long *secsp)
  * Otherwise, return a pointer to the first character not part of the time.
  */
 static const char *
-getoffset(const char *strp, long *offsetp)
+getoffset(const char *strp, int32 *offsetp)
 {
-       int                     neg = 0;
+       bool            neg = false;
 
        if (*strp == '-')
        {
-               neg = 1;
+               neg = true;
                ++strp;
        }
        else if (*strp == '+')
@@ -598,7 +758,7 @@ getrule(const char *strp, struct rule * rulep)
                 * Time specified.
                 */
                ++strp;
-               strp = getsecs(strp, &rulep->r_time);
+               strp = getoffset(strp, &rulep->r_time);
        }
        else
                rulep->r_time = 2 * SECSPERHOUR;                /* default = 2:00:00 */
@@ -606,16 +766,15 @@ getrule(const char *strp, struct rule * rulep)
 }
 
 /*
- * Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
- * year, a rule, and the offset from UTC at the time that rule takes effect,
- * calculate the Epoch-relative time that rule takes effect.
+ * Given a year, a rule, and the offset from UT at the time that rule takes
+ * effect, calculate the year-relative time that rule takes effect.
  */
-static pg_time_t
-transtime(pg_time_t janfirst, int year,
-                 const struct rule * rulep, long offset)
+static int32
+transtime(int year, const struct rule * rulep,
+                 int32 offset)
 {
-       int                     leapyear;
-       pg_time_t       value = 0;
+       bool            leapyear;
+       int32           value;
        int                     i,
                                d,
                                m1,
@@ -624,6 +783,7 @@ transtime(pg_time_t janfirst, int year,
                                yy2,
                                dow;
 
+       INITIALIZE(value);
        leapyear = isleap(year);
        switch (rulep->r_type)
        {
@@ -636,7 +796,7 @@ transtime(pg_time_t janfirst, int year,
                         * just add SECSPERDAY times the day number-1 to the time of
                         * January 1, midnight, to get the day.
                         */
-                       value = janfirst + (rulep->r_day - 1) * SECSPERDAY;
+                       value = (rulep->r_day - 1) * SECSPERDAY;
                        if (leapyear && rulep->r_day >= 60)
                                value += SECSPERDAY;
                        break;
@@ -647,7 +807,7 @@ transtime(pg_time_t janfirst, int year,
                         * n - day of year. Just add SECSPERDAY times the day number to
                         * the time of January 1, midnight, to get the day.
                         */
-                       value = janfirst + rulep->r_day * SECSPERDAY;
+                       value = rulep->r_day * SECSPERDAY;
                        break;
 
                case MONTH_NTH_DAY_OF_WEEK:
@@ -655,9 +815,6 @@ transtime(pg_time_t janfirst, int year,
                        /*
                         * Mm.n.d - nth "dth day" of month m.
                         */
-                       value = janfirst;
-                       for (i = 0; i < rulep->r_mon - 1; ++i)
-                               value += mon_lengths[leapyear][i] * SECSPERDAY;
 
                        /*
                         * Use Zeller's Congruence to get day-of-week of first day of
@@ -682,7 +839,7 @@ transtime(pg_time_t janfirst, int year,
                        for (i = 1; i < rulep->r_week; ++i)
                        {
                                if (d + DAYSPERWEEK >=
-                                       mon_lengths[leapyear][rulep->r_mon - 1])
+                                       mon_lengths[(int) leapyear][rulep->r_mon - 1])
                                        break;
                                d += DAYSPERWEEK;
                        }
@@ -690,14 +847,16 @@ transtime(pg_time_t janfirst, int year,
                        /*
                         * "d" is the day-of-month (zero-origin) of the day we want.
                         */
-                       value += d * SECSPERDAY;
+                       value = d * SECSPERDAY;
+                       for (i = 0; i < rulep->r_mon - 1; ++i)
+                               value += mon_lengths[(int) leapyear][i] * SECSPERDAY;
                        break;
        }
 
        /*
-        * "value" is the Epoch-relative time of 00:00:00 UTC on the day in
-        * question.  To get the Epoch-relative time of the specified local time
-        * on that day, add the transition time and the current offset from UTC.
+        * "value" is the year-relative time of 00:00:00 UT on the day in
+        * question. To get the year-relative time of the specified local time on
+        * that day, add the transition time and the current offset from UT.
         */
        return value + rulep->r_time + offset;
 }
@@ -705,8 +864,11 @@ transtime(pg_time_t janfirst, int year,
 /*
  * Given a POSIX section 8-style TZ string, fill in the rule tables as
  * appropriate.
+ *
+ * Returns 0 on success, -1 on failure.  (Note: tzcode has converted this
+ * to a bool true-on-success convention, but we're holding the line in PG
+ * for the moment, to avoid external API changes.)
  */
-
 int
 tzparse(const char *name, struct state * sp, int lastditch)
 {
@@ -714,29 +876,29 @@ tzparse(const char *name, struct state * sp, int lastditch)
        const char *dstname = NULL;
        size_t          stdlen;
        size_t          dstlen;
-       long            stdoffset;
-       long            dstoffset;
-       pg_time_t  *atp;
-       unsigned char *typep;
+       size_t          charcnt;
+       int32           stdoffset;
+       int32           dstoffset;
        char       *cp;
-       int                     load_result;
+       bool            load_ok;
 
        stdname = name;
        if (lastditch)
        {
+               /*
+                * This is intentionally somewhat different from the IANA code.  We do
+                * not want to invoke tzload() in the lastditch case: we can't assume
+                * pg_open_tzfile() is sane yet, and we don't care about leap seconds
+                * anyway.
+                */
                stdlen = strlen(name);  /* length of standard zone name */
                name += stdlen;
                if (stdlen >= sizeof sp->chars)
                        stdlen = (sizeof sp->chars) - 1;
+               charcnt = stdlen + 1;
                stdoffset = 0;
-
-               /*
-                * Unlike the original zic library, do NOT invoke tzload() here; we
-                * can't assume pg_open_tzfile() is sane yet, and we don't care about
-                * leap seconds anyway.
-                */
-               sp->goback = sp->goahead = FALSE;
-               load_result = -1;
+               sp->goback = sp->goahead = false;               /* simulate failed tzload() */
+               load_ok = false;
        }
        else
        {
@@ -746,7 +908,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
                        stdname = name;
                        name = getqzname(name, '>');
                        if (*name != '>')
-                               return (-1);
+                               return -1;
                        stdlen = name - stdname;
                        name++;
                }
@@ -755,14 +917,17 @@ tzparse(const char *name, struct state * sp, int lastditch)
                        name = getzname(name);
                        stdlen = name - stdname;
                }
-               if (*name == '\0')
+               if (*name == '\0')              /* we allow empty STD abbrev, unlike IANA */
                        return -1;
                name = getoffset(name, &stdoffset);
                if (name == NULL)
                        return -1;
-               load_result = tzload(TZDEFRULES, NULL, sp, FALSE);
+               charcnt = stdlen + 1;
+               if (sizeof sp->chars < charcnt)
+                       return -1;
+               load_ok = tzload(TZDEFRULES, NULL, sp, false) == 0;
        }
-       if (load_result != 0)
+       if (!load_ok)
                sp->leapcnt = 0;                /* so, we're off a little */
        if (*name != '\0')
        {
@@ -781,6 +946,11 @@ tzparse(const char *name, struct state * sp, int lastditch)
                        name = getzname(name);
                        dstlen = name - dstname;        /* length of DST zone name */
                }
+               if (!dstlen)
+                       return -1;
+               charcnt += dstlen + 1;
+               if (sizeof sp->chars < charcnt)
+                       return -1;
                if (*name != '\0' && *name != ',' && *name != ';')
                {
                        name = getoffset(name, &dstoffset);
@@ -789,16 +959,16 @@ tzparse(const char *name, struct state * sp, int lastditch)
                }
                else
                        dstoffset = stdoffset - SECSPERHOUR;
-               if (*name == '\0' && load_result != 0)
+               if (*name == '\0' && !load_ok)
                        name = TZDEFRULESTRING;
                if (*name == ',' || *name == ';')
                {
                        struct rule start;
                        struct rule end;
                        int                     year;
+                       int                     yearlim;
+                       int                     timecnt;
                        pg_time_t       janfirst;
-                       pg_time_t       starttime;
-                       pg_time_t       endtime;
 
                        ++name;
                        if ((name = getrule(name, &start)) == NULL)
@@ -814,55 +984,62 @@ tzparse(const char *name, struct state * sp, int lastditch)
                        /*
                         * Two transitions per year, from EPOCH_YEAR forward.
                         */
-                       sp->ttis[0].tt_gmtoff = -dstoffset;
-                       sp->ttis[0].tt_isdst = 1;
-                       sp->ttis[0].tt_abbrind = stdlen + 1;
-                       sp->ttis[1].tt_gmtoff = -stdoffset;
-                       sp->ttis[1].tt_isdst = 0;
-                       sp->ttis[1].tt_abbrind = 0;
-                       atp = sp->ats;
-                       typep = sp->types;
+                       init_ttinfo(&sp->ttis[0], -dstoffset, true, stdlen + 1);
+                       init_ttinfo(&sp->ttis[1], -stdoffset, false, 0);
+                       sp->defaulttype = 0;
+                       timecnt = 0;
                        janfirst = 0;
-                       sp->timecnt = 0;
-                       for (year = EPOCH_YEAR;
-                                sp->timecnt + 2 <= TZ_MAX_TIMES;
-                                ++year)
+                       yearlim = EPOCH_YEAR + YEARSPERREPEAT;
+                       for (year = EPOCH_YEAR; year < yearlim; year++)
                        {
-                               pg_time_t       newfirst;
-
-                               starttime = transtime(janfirst, year, &start,
-                                                                         stdoffset);
-                               endtime = transtime(janfirst, year, &end,
-                                                                       dstoffset);
-                               if (starttime > endtime)
+                               int32
+                                                       starttime = transtime(year, &start, stdoffset),
+                                                       endtime = transtime(year, &end, dstoffset);
+                               int32
+                                                       yearsecs = (year_lengths[isleap(year)]
+                                                                               * SECSPERDAY);
+                               bool            reversed = endtime < starttime;
+
+                               if (reversed)
                                {
-                                       *atp++ = endtime;
-                                       *typep++ = 1;           /* DST ends */
-                                       *atp++ = starttime;
-                                       *typep++ = 0;           /* DST begins */
+                                       int32           swap = starttime;
+
+                                       starttime = endtime;
+                                       endtime = swap;
                                }
-                               else
+                               if (reversed
+                                       || (starttime < endtime
+                                               && (endtime - starttime
+                                                       < (yearsecs
+                                                          + (stdoffset - dstoffset)))))
                                {
-                                       *atp++ = starttime;
-                                       *typep++ = 0;           /* DST begins */
-                                       *atp++ = endtime;
-                                       *typep++ = 1;           /* DST ends */
+                                       if (TZ_MAX_TIMES - 2 < timecnt)
+                                               break;
+                                       yearlim = year + YEARSPERREPEAT + 1;
+                                       sp->ats[timecnt] = janfirst;
+                                       if (increment_overflow_time
+                                               (&sp->ats[timecnt], starttime))
+                                               break;
+                                       sp->types[timecnt++] = reversed;
+                                       sp->ats[timecnt] = janfirst;
+                                       if (increment_overflow_time
+                                               (&sp->ats[timecnt], endtime))
+                                               break;
+                                       sp->types[timecnt++] = !reversed;
                                }
-                               sp->timecnt += 2;
-                               newfirst = janfirst;
-                               newfirst += year_lengths[isleap(year)] *
-                                       SECSPERDAY;
-                               if (newfirst <= janfirst)
+                               if (increment_overflow_time(&janfirst, yearsecs))
                                        break;
-                               janfirst = newfirst;
                        }
+                       sp->timecnt = timecnt;
+                       if (!timecnt)
+                               sp->typecnt = 1;        /* Perpetual DST.  */
                }
                else
                {
-                       long            theirstdoffset;
-                       long            theirdstoffset;
-                       long            theiroffset;
-                       int                     isdst;
+                       int32           theirstdoffset;
+                       int32           theirdstoffset;
+                       int32           theiroffset;
+                       bool            isdst;
                        int                     i;
                        int                     j;
 
@@ -898,7 +1075,7 @@ tzparse(const char *name, struct state * sp, int lastditch)
                        /*
                         * Initially we're assumed to be in standard time.
                         */
-                       isdst = FALSE;
+                       isdst = false;
                        theiroffset = theirstdoffset;
 
                        /*
@@ -945,15 +1122,12 @@ tzparse(const char *name, struct state * sp, int lastditch)
                        }
 
                        /*
-                        * Finally, fill in ttis. ttisstd and ttisgmt need not be handled.
+                        * Finally, fill in ttis.
                         */
-                       sp->ttis[0].tt_gmtoff = -stdoffset;
-                       sp->ttis[0].tt_isdst = FALSE;
-                       sp->ttis[0].tt_abbrind = 0;
-                       sp->ttis[1].tt_gmtoff = -dstoffset;
-                       sp->ttis[1].tt_isdst = TRUE;
-                       sp->ttis[1].tt_abbrind = stdlen + 1;
+                       init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
+                       init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
                        sp->typecnt = 2;
+                       sp->defaulttype = 0;
                }
        }
        else
@@ -961,22 +1135,17 @@ tzparse(const char *name, struct state * sp, int lastditch)
                dstlen = 0;
                sp->typecnt = 1;                /* only standard time */
                sp->timecnt = 0;
-               sp->ttis[0].tt_gmtoff = -stdoffset;
-               sp->ttis[0].tt_isdst = 0;
-               sp->ttis[0].tt_abbrind = 0;
+               init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
+               sp->defaulttype = 0;
        }
-       sp->charcnt = stdlen + 1;
-       if (dstlen != 0)
-               sp->charcnt += dstlen + 1;
-       if ((size_t) sp->charcnt > sizeof sp->chars)
-               return -1;
+       sp->charcnt = charcnt;
        cp = sp->chars;
-       (void) strncpy(cp, stdname, stdlen);
+       memcpy(cp, stdname, stdlen);
        cp += stdlen;
        *cp++ = '\0';
        if (dstlen != 0)
        {
-               (void) strncpy(cp, dstname, dstlen);
+               memcpy(cp, dstname, dstlen);
                *(cp + dstlen) = '\0';
        }
        return 0;
@@ -985,51 +1154,42 @@ tzparse(const char *name, struct state * sp, int lastditch)
 static void
 gmtload(struct state * sp)
 {
-       if (tzload(gmt, NULL, sp, TRUE) != 0)
-               (void) tzparse(gmt, sp, TRUE);
+       if (tzload(gmt, NULL, sp, true) != 0)
+               tzparse(gmt, sp, true);
 }
 
 
 /*
  * The easy way to behave "as if no library function calls" localtime
- * is to not call it--so we drop its guts into "localsub", which can be
- * freely called. (And no, the PANS doesn't require the above behavior--
+ * is to not call itso we drop its guts into "localsub", which can be
+ * freely called. (And no, the PANS doesn't require the above behavior,
  * but it *is* desirable.)
- *
- * The unused offset argument is for the benefit of mktime variants.
  */
 static struct pg_tm *
-localsub(const pg_time_t *timep, long offset,
-                struct pg_tm * tmp, const pg_tz *tz)
+localsub(struct state const * sp, pg_time_t const * timep,
+                struct pg_tm * tmp)
 {
-       const struct state *sp;
        const struct ttinfo *ttisp;
        int                     i;
        struct pg_tm *result;
        const pg_time_t t = *timep;
 
-       sp = &tz->state;
+       if (sp == NULL)
+               return gmtsub(timep, 0, tmp);
        if ((sp->goback && t < sp->ats[0]) ||
                (sp->goahead && t > sp->ats[sp->timecnt - 1]))
        {
                pg_time_t       newt = t;
                pg_time_t       seconds;
-               pg_time_t       tcycles;
-               int64           icycles;
+               pg_time_t       years;
 
                if (t < sp->ats[0])
                        seconds = sp->ats[0] - t;
                else
                        seconds = t - sp->ats[sp->timecnt - 1];
                --seconds;
-               tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR;
-               ++tcycles;
-               icycles = tcycles;
-               if (tcycles - icycles >= 1 || icycles - tcycles >= 1)
-                       return NULL;
-               seconds = icycles;
-               seconds *= YEARSPERREPEAT;
-               seconds *= AVGSECSPERYEAR;
+               years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT;
+               seconds = years * AVGSECSPERYEAR;
                if (t < sp->ats[0])
                        newt += seconds;
                else
@@ -1037,31 +1197,25 @@ localsub(const pg_time_t *timep, long offset,
                if (newt < sp->ats[0] ||
                        newt > sp->ats[sp->timecnt - 1])
                        return NULL;            /* "cannot happen" */
-               result = localsub(&newt, offset, tmp, tz);
-               if (result == tmp)
+               result = localsub(sp, &newt, tmp);
+               if (result)
                {
-                       pg_time_t       newy;
+                       int64           newy;
 
-                       newy = tmp->tm_year;
+                       newy = result->tm_year;
                        if (t < sp->ats[0])
-                               newy -= icycles * YEARSPERREPEAT;
+                               newy -= years;
                        else
-                               newy += icycles * YEARSPERREPEAT;
-                       tmp->tm_year = newy;
-                       if (tmp->tm_year != newy)
+                               newy += years;
+                       if (!(INT_MIN <= newy && newy <= INT_MAX))
                                return NULL;
+                       result->tm_year = newy;
                }
                return result;
        }
        if (sp->timecnt == 0 || t < sp->ats[0])
        {
-               i = 0;
-               while (sp->ttis[i].tt_isdst)
-                       if (++i >= sp->typecnt)
-                       {
-                               i = 0;
-                               break;
-                       }
+               i = sp->defaulttype;
        }
        else
        {
@@ -1082,8 +1236,11 @@ localsub(const pg_time_t *timep, long offset,
        ttisp = &sp->ttis[i];
 
        result = timesub(&t, ttisp->tt_gmtoff, sp, tmp);
-       tmp->tm_isdst = ttisp->tt_isdst;
-       tmp->tm_zone = &sp->chars[ttisp->tt_abbrind];
+       if (result)
+       {
+               result->tm_isdst = ttisp->tt_isdst;
+               result->tm_zone = (char *) &sp->chars[ttisp->tt_abbrind];
+       }
        return result;
 }
 
@@ -1091,28 +1248,35 @@ localsub(const pg_time_t *timep, long offset,
 struct pg_tm *
 pg_localtime(const pg_time_t *timep, const pg_tz *tz)
 {
-       return localsub(timep, 0L, &tm, tz);
+       return localsub(&tz->state, timep, &tm);
 }
 
 
 /*
  * gmtsub is to gmtime as localsub is to localtime.
+ *
+ * Except we have a private "struct state" for GMT, so no sp is passed in.
  */
 static struct pg_tm *
-gmtsub(const pg_time_t *timep, long offset, struct pg_tm * tmp)
+gmtsub(pg_time_t const * timep, int32 offset, struct pg_tm * tmp)
 {
        struct pg_tm *result;
 
+       /* GMT timezone state data is kept here */
+       static struct state gmtmem;
+       static bool gmt_is_set = false;
+#define gmtptr         (&gmtmem)
+
        if (!gmt_is_set)
        {
-               gmt_is_set = TRUE;
+               gmt_is_set = true;
                gmtload(gmtptr);
        }
        result = timesub(timep, offset, gmtptr, tmp);
 
        /*
-        * Could get fancy here and deliver something such as "UTC+xxxx" or
-        * "UTC-xxxx" if offset is non-zero, but this is no time for a treasure
+        * Could get fancy here and deliver something such as "UT+xxxx" or
+        * "UT-xxxx" if offset is non-zero, but this is no time for a treasure
         * hunt.
         */
        if (offset != 0)
@@ -1126,7 +1290,7 @@ gmtsub(const pg_time_t *timep, long offset, struct pg_tm * tmp)
 struct pg_tm *
 pg_gmtime(const pg_time_t *timep)
 {
-       return gmtsub(timep, 0L, &tm);
+       return gmtsub(timep, 0, &tm);
 }
 
 /*
@@ -1140,24 +1304,23 @@ leaps_thru_end_of(const int y)
                -(leaps_thru_end_of(-(y + 1)) + 1);
 }
 
-
 static struct pg_tm *
-timesub(const pg_time_t *timep, long offset,
+timesub(const pg_time_t *timep, int32 offset,
                const struct state * sp, struct pg_tm * tmp)
 {
        const struct lsinfo *lp;
        pg_time_t       tdays;
        int                     idays;                  /* unsigned would be so 2003 */
-       long            rem;
+       int64           rem;
        int                     y;
        const int  *ip;
-       long            corr;
-       int                     hit;
+       int64           corr;
+       bool            hit;
        int                     i;
 
        corr = 0;
-       hit = 0;
-       i = sp->leapcnt;
+       hit = false;
+       i = (sp == NULL) ? 0 : sp->leapcnt;
        while (--i >= 0)
        {
                lp = &sp->lsis[i];
@@ -1184,7 +1347,7 @@ timesub(const pg_time_t *timep, long offset,
        }
        y = EPOCH_YEAR;
        tdays = *timep / SECSPERDAY;
-       rem = *timep - tdays * SECSPERDAY;
+       rem = *timep % SECSPERDAY;
        while (tdays < 0 || tdays >= year_lengths[isleap(y)])
        {
                int                     newy;
@@ -1193,27 +1356,21 @@ timesub(const pg_time_t *timep, long offset,
                int                     leapdays;
 
                tdelta = tdays / DAYSPERLYEAR;
+               if (!((!TYPE_SIGNED(pg_time_t) ||INT_MIN <= tdelta)
+                         && tdelta <= INT_MAX))
+                       goto out_of_range;
                idelta = tdelta;
-               if (tdelta - idelta >= 1 || idelta - tdelta >= 1)
-                       return NULL;
                if (idelta == 0)
                        idelta = (tdays < 0) ? -1 : 1;
                newy = y;
                if (increment_overflow(&newy, idelta))
-                       return NULL;
+                       goto out_of_range;
                leapdays = leaps_thru_end_of(newy - 1) -
                        leaps_thru_end_of(y - 1);
                tdays -= ((pg_time_t) newy - y) * DAYSPERNYEAR;
                tdays -= leapdays;
                y = newy;
        }
-       {
-               long            seconds;
-
-               seconds = tdays * SECSPERDAY + 0.5;
-               tdays = seconds / SECSPERDAY;
-               rem += seconds - tdays * SECSPERDAY;
-       }
 
        /*
         * Given the range, we can now fearlessly cast...
@@ -1233,18 +1390,18 @@ timesub(const pg_time_t *timep, long offset,
        while (idays < 0)
        {
                if (increment_overflow(&y, -1))
-                       return NULL;
+                       goto out_of_range;
                idays += year_lengths[isleap(y)];
        }
        while (idays >= year_lengths[isleap(y)])
        {
                idays -= year_lengths[isleap(y)];
                if (increment_overflow(&y, 1))
-                       return NULL;
+                       goto out_of_range;
        }
        tmp->tm_year = y;
        if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
-               return NULL;
+               goto out_of_range;
        tmp->tm_yday = idays;
 
        /*
@@ -1275,20 +1432,49 @@ timesub(const pg_time_t *timep, long offset,
        tmp->tm_isdst = 0;
        tmp->tm_gmtoff = offset;
        return tmp;
+
+out_of_range:
+       errno = EOVERFLOW;
+       return NULL;
 }
 
 /*
- * Simplified normalize logic courtesy Paul Eggert.
+ * Normalize logic courtesy Paul Eggert.
  */
 
-static int
-increment_overflow(int *number, int delta)
+static bool
+increment_overflow(int *ip, int j)
 {
-       int                     number0;
+       int const       i = *ip;
+
+       /*----------
+        * If i >= 0 there can only be overflow if i + j > INT_MAX
+        * or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow.
+        * If i < 0 there can only be overflow if i + j < INT_MIN
+        * or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow.
+        *----------
+        */
+       if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i))
+               return true;
+       *ip += j;
+       return false;
+}
 
-       number0 = *number;
-       *number += delta;
-       return (*number < number0) != (delta < 0);
+static bool
+increment_overflow_time(pg_time_t *tp, int32 j)
+{
+       /*----------
+        * This is like
+        * 'if (! (time_t_min <= *tp + j && *tp + j <= time_t_max)) ...',
+        * except that it does the right thing even if *tp + j would overflow.
+        *----------
+        */
+       if (!(j < 0
+                 ? (TYPE_SIGNED(pg_time_t) ? time_t_min - j <= *tp : -1 - j < *tp)
+                 : *tp <= time_t_max - j))
+               return true;
+       *tp += j;
+       return false;
 }
 
 /*
@@ -1296,7 +1482,7 @@ increment_overflow(int *number, int delta)
  *
  * *timep and *tz are input arguments, the other parameters are output values.
  *
- * When the function result is 1, *boundary is set to the time_t
+ * When the function result is 1, *boundary is set to the pg_time_t
  * representation of the next DST transition time after *timep,
  * *before_gmtoff and *before_isdst are set to the GMT offset and isdst
  * state prevailing just before that boundary (in particular, the state
@@ -1453,8 +1639,8 @@ pg_next_dst_boundary(const pg_time_t *timep,
  * the meaning in use at or most recently before that time, or the meaning
  * in first use after that time if the abbrev was never used before that.
  *
- * On success, returns TRUE and sets *gmtoff and *isdst.  If the abbreviation
- * was never used at all in this zone, returns FALSE.
+ * On success, returns true and sets *gmtoff and *isdst.  If the abbreviation
+ * was never used at all in this zone, returns false.
  *
  * Note: abbrev is matched case-sensitively; it should be all-upper-case.
  */
@@ -1490,7 +1676,7 @@ pg_interpret_timezone_abbrev(const char *abbrev,
                abbrind++;
        }
        if (abbrind >= sp->charcnt)
-               return FALSE;                   /* not there! */
+               return false;                   /* not there! */
 
        /*
         * Unlike pg_next_dst_boundary, we needn't sweat about extrapolation
@@ -1527,7 +1713,7 @@ pg_interpret_timezone_abbrev(const char *abbrev,
                {
                        *gmtoff = ttisp->tt_gmtoff;
                        *isdst = ttisp->tt_isdst;
-                       return TRUE;
+                       return true;
                }
        }
 
@@ -1541,23 +1727,23 @@ pg_interpret_timezone_abbrev(const char *abbrev,
                {
                        *gmtoff = ttisp->tt_gmtoff;
                        *isdst = ttisp->tt_isdst;
-                       return TRUE;
+                       return true;
                }
        }
 
-       return FALSE;                           /* hm, not actually used in any interval? */
+       return false;                           /* hm, not actually used in any interval? */
 }
 
 /*
  * If the given timezone uses only one GMT offset, store that offset
- * into *gmtoff and return TRUE, else return FALSE.
+ * into *gmtoff and return true, else return false.
  */
 bool
 pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff)
 {
        /*
         * The zone could have more than one ttinfo, if it's historically used
-        * more than one abbreviation.  We return TRUE as long as they all have
+        * more than one abbreviation.  We return true as long as they all have
         * the same gmtoff.
         */
        const struct state *sp;
index baf9733395d73d200ed7502304cbd8caf0a754a1..c28e6dbfad8149efa2218b6a48d134101f692bf1 100644 (file)
@@ -256,7 +256,7 @@ pg_tzset(const char *name)
         */
        if (strcmp(uppername, "GMT") == 0)
        {
-               if (tzparse(uppername, &tzstate, TRUE) != 0)
+               if (tzparse(uppername, &tzstate, true) != 0)
                {
                        /* This really, really should not happen ... */
                        elog(ERROR, "could not initialize GMT time zone");
@@ -264,9 +264,9 @@ pg_tzset(const char *name)
                /* Use uppercase name as canonical */
                strcpy(canonname, uppername);
        }
-       else if (tzload(uppername, canonname, &tzstate, TRUE) != 0)
+       else if (tzload(uppername, canonname, &tzstate, true) != 0)
        {
-               if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0)
+               if (uppername[0] == ':' || tzparse(uppername, &tzstate, false) != 0)
                {
                        /* Unknown timezone. Fail our call instead of loading GMT! */
                        return NULL;
@@ -460,7 +460,7 @@ pg_tzenumerate_next(pg_tzenum *dir)
                 * the cache
                 */
                if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state,
-                                  TRUE) != 0)
+                                  true) != 0)
                {
                        /* Zone could not be loaded, ignore it */
                        continue;
index ad93291d0ac98ac4d4dd3dc23d890f841a120362..deee7464ed51d3eadb61a93a34e5c27049288f83 100644 (file)
 #ifndef _PGTZ_H
 #define _PGTZ_H
 
-#include "tzfile.h"
 #include "pgtime.h"
+#include "tzfile.h"
 
 
+#define SMALLEST(a, b) (((a) < (b)) ? (a) : (b))
 #define BIGGEST(a, b)  (((a) > (b)) ? (a) : (b))
 
 struct ttinfo
 {                                                              /* time type information */
-       long            tt_gmtoff;              /* UTC offset in seconds */
-       int                     tt_isdst;               /* used to set tm_isdst */
+       int32           tt_gmtoff;              /* UT offset in seconds */
+       bool            tt_isdst;               /* used to set tm_isdst */
        int                     tt_abbrind;             /* abbreviation list index */
-       int                     tt_ttisstd;             /* TRUE if transition is std time */
-       int                     tt_ttisgmt;             /* TRUE if transition is UTC */
+       bool            tt_ttisstd;             /* transition is std time */
+       bool            tt_ttisgmt;             /* transition is UT */
 };
 
 struct lsinfo
 {                                                              /* leap second information */
        pg_time_t       ls_trans;               /* transition time */
-       long            ls_corr;                /* correction to apply */
+       int64           ls_corr;                /* correction to apply */
 };
 
 struct state
@@ -43,14 +44,15 @@ struct state
        int                     timecnt;
        int                     typecnt;
        int                     charcnt;
-       int                     goback;
-       int                     goahead;
+       bool            goback;
+       bool            goahead;
        pg_time_t       ats[TZ_MAX_TIMES];
        unsigned char types[TZ_MAX_TIMES];
        struct ttinfo ttis[TZ_MAX_TYPES];
        char            chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, 3 /* sizeof gmt */ ),
                                                                                  (2 * (TZ_STRLEN_MAX + 1)))];
        struct lsinfo lsis[TZ_MAX_LEAPS];
+       int                     defaulttype;    /* for early times or if no transitions */
 };
 
 
index b1ac9edfcf842a1d00b5b25261e9754a3405771a..8480d389612243adc322dc3e80129d12afaa6a16 100644 (file)
 
 #define GRANDPARENTED  "Local time zone must be set--see zic manual page"
 
+/*
+ * IANA has a bunch of HAVE_FOO #defines here, but in PG we want pretty
+ * much all of that to be done by PG's configure script.
+ */
+
+#ifndef ENOTSUP
+#define ENOTSUP EINVAL
+#endif
+#ifndef EOVERFLOW
+#define EOVERFLOW EINVAL
+#endif
+
 #ifndef WIFEXITED
 #define WIFEXITED(status)      (((status) & 0xff) == 0)
 #endif   /* !defined WIFEXITED */
 /* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
 #define is_digit(c) ((unsigned)(c) - '0' <= 9)
 
+#ifndef SIZE_MAX
+#define SIZE_MAX ((size_t) -1)
+#endif
+
 /*
  * SunOS 4.1.1 libraries lack remove.
  */
@@ -45,31 +61,11 @@ extern int  unlink(const char *filename);
 #define remove unlink
 #endif   /* !defined remove */
 
-/*
- * Private function declarations.
- */
-extern char *icalloc(int nelem, int elsize);
-extern char *icatalloc(char *old, const char *new);
-extern char *icpyalloc(const char *string);
-extern char *imalloc(int n);
-extern void *irealloc(void *pointer, int size);
-extern void icfree(char *pointer);
-extern void ifree(char *pointer);
-extern const char *scheck(const char *string, const char *format);
-
 
 /*
  * Finally, some convenience items.
  */
 
-#ifndef TRUE
-#define TRUE   1
-#endif   /* !defined TRUE */
-
-#ifndef FALSE
-#define FALSE  0
-#endif   /* !defined FALSE */
-
 #ifndef TYPE_BIT
 #define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
 #endif   /* !defined TYPE_BIT */
@@ -78,14 +74,18 @@ extern const char *scheck(const char *string, const char *format);
 #define TYPE_SIGNED(type) (((type) -1) < 0)
 #endif   /* !defined TYPE_SIGNED */
 
+#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0)
+
 /*
- * Since the definition of TYPE_INTEGRAL contains floating point numbers,
- * it cannot be used in preprocessor directives.
+ * Max and min values of the integer type T, of which only the bottom
+ * B bits are used, and where the highest-order used bit is considered
+ * to be a sign bit if T is signed.
  */
-
-#ifndef TYPE_INTEGRAL
-#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5)
-#endif   /* !defined TYPE_INTEGRAL */
+#define MAXVAL(t, b)                                           \
+  ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t)))                  \
+       - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t)))))
+#define MINVAL(t, b)                                           \
+  ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0))
 
 #ifndef INT_STRLEN_MAXIMUM
 /*
@@ -95,34 +95,36 @@ extern const char *scheck(const char *string, const char *format);
  * add one more for a minus sign if the type is signed.
  */
 #define INT_STRLEN_MAXIMUM(type) \
-       ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + 1 + TYPE_SIGNED(type))
+       ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
+       1 + TYPE_SIGNED(type))
 #endif   /* !defined INT_STRLEN_MAXIMUM */
 
+/*
+ * INITIALIZE(x)
+ */
+#define INITIALIZE(x)  ((x) = 0)
+
 #undef _
 #define _(msgid) (msgid)
 
 #ifndef YEARSPERREPEAT
-#define YEARSPERREPEAT                 400             /* years before a Gregorian repeat */
+#define YEARSPERREPEAT         400 /* years before a Gregorian repeat */
 #endif   /* !defined YEARSPERREPEAT */
 
 /*
-** The Gregorian year averages 365.2425 days, which is 31556952 seconds.
-*/
+ * The Gregorian year averages 365.2425 days, which is 31556952 seconds.
+ */
 
 #ifndef AVGSECSPERYEAR
-#define AVGSECSPERYEAR                 31556952L
+#define AVGSECSPERYEAR         31556952L
 #endif   /* !defined AVGSECSPERYEAR */
 
 #ifndef SECSPERREPEAT
-#define SECSPERREPEAT                  ((int64) YEARSPERREPEAT * (int64) AVGSECSPERYEAR)
+#define SECSPERREPEAT          ((int64) YEARSPERREPEAT * (int64) AVGSECSPERYEAR)
 #endif   /* !defined SECSPERREPEAT */
 
 #ifndef SECSPERREPEAT_BITS
-#define SECSPERREPEAT_BITS             34              /* ceil(log2(SECSPERREPEAT)) */
+#define SECSPERREPEAT_BITS     34      /* ceil(log2(SECSPERREPEAT)) */
 #endif   /* !defined SECSPERREPEAT_BITS */
 
-/*
- * UNIX was a registered trademark of The Open Group in 2003.
- */
-
 #endif   /* !defined PRIVATE_H */
diff --git a/src/timezone/scheck.c b/src/timezone/scheck.c
deleted file mode 100644 (file)
index 67bf2bc..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * This file is in the public domain, so clarified as of
- * 2006-07-17 by Arthur David Olson.
- *
- * IDENTIFICATION
- *       src/timezone/scheck.c
- */
-
-#include "postgres_fe.h"
-
-#include "private.h"
-
-
-const char *
-scheck(const char *string, const char *format)
-{
-       char       *fbuf;
-       const char *fp;
-       char       *tp;
-       int                     c;
-       const char *result;
-       char            dummy;
-
-       result = "";
-       if (string == NULL || format == NULL)
-               return result;
-       fbuf = imalloc((int) (2 * strlen(format) + 4));
-       if (fbuf == NULL)
-               return result;
-       fp = format;
-       tp = fbuf;
-       while ((*tp++ = c = *fp++) != '\0')
-       {
-               if (c != '%')
-                       continue;
-               if (*fp == '%')
-               {
-                       *tp++ = *fp++;
-                       continue;
-               }
-               *tp++ = '*';
-               if (*fp == '*')
-                       ++fp;
-               while (is_digit(*fp))
-                       *tp++ = *fp++;
-               if (*fp == 'l' || *fp == 'h')
-                       *tp++ = *fp++;
-               else if (*fp == '[')
-                       do
-                               *tp++ = *fp++;
-                       while (*fp != '\0' && *fp != ']');
-               if ((*tp++ = *fp++) == '\0')
-                       break;
-       }
-       *(tp - 1) = '%';
-       *tp++ = 'c';
-       *tp = '\0';
-       if (sscanf(string, fbuf, &dummy) != 1)
-               result = (char *) format;
-       ifree(fbuf);
-       return result;
-}
index bdc8dd32408ff1cb64c23948428486af8e9de9a0..5630619321312c00fe59a008d0448f783c347e7c 100644 (file)
@@ -1,18 +1,38 @@
+/* Convert a broken-down time stamp to a string. */
+
 /*
- * Copyright (c) 1989 The Regents of the University of California.
+ * Copyright 1989 The Regents of the University of California.
  * All rights reserved.
  *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley. The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *       may be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Based on the UCB version with the copyright notice appearing above.
+ *
+ * This is ANSIish only when "multibyte character == plain character".
  *
  * IDENTIFICATION
  *       src/timezone/strftime.c
@@ -92,8 +112,7 @@ static char *_add(const char *, char *, const char *);
 static char *_conv(int, const char *, char *, const char *);
 static char *_fmt(const char *, const struct pg_tm *, char *,
         const char *, int *);
-static char *_yconv(const int, const int, const int, const int,
-          char *, const char *const);
+static char *_yconv(int, int, bool, bool, char *, const char *);
 
 #define IN_NONE 0
 #define IN_SOME 1
@@ -162,8 +181,8 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
                                         * ...whereas now POSIX 1003.2 calls for something
                                         * completely different. (ado, 1993-05-24)
                                         */
-                                       pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
-                                                               pt, ptlim);
+                                       pt = _yconv(t->tm_year, TM_YEAR_BASE,
+                                                               true, false, pt, ptlim);
                                        continue;
                                case 'c':
                                        {
@@ -224,7 +243,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
                                case 'K':
 
                                        /*
-                                        * After all this time, still unclaimed!
+                                        * After all this time, still unclaimed!
                                         */
                                        pt = _add("kitchen sink", pt, ptlim);
                                        continue;
@@ -296,7 +315,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
  * (01-53)."
  * (ado, 1993-05-24)
  *
- * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
+ * From <http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html> by Markus Kuhn:
  * "Week 01 of a year is per definition the first week which has the
  * Thursday in this year, which is equivalent to the week which contains
  * the fourth day of January. In other words, the first week of a new year
@@ -367,11 +386,13 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
                                                else if (*format == 'g')
                                                {
                                                        *warnp = IN_ALL;
-                                                       pt = _yconv(year, base, 0, 1,
+                                                       pt = _yconv(year, base,
+                                                                               false, true,
                                                                                pt, ptlim);
                                                }
                                                else
-                                                       pt = _yconv(year, base, 1, 1,
+                                                       pt = _yconv(year, base,
+                                                                               true, true,
                                                                                pt, ptlim);
                                        }
                                        continue;
@@ -409,11 +430,13 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
                                        continue;
                                case 'y':
                                        *warnp = IN_ALL;
-                                       pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
+                                       pt = _yconv(t->tm_year, TM_YEAR_BASE,
+                                                               false, true,
                                                                pt, ptlim);
                                        continue;
                                case 'Y':
-                                       pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
+                                       pt = _yconv(t->tm_year, TM_YEAR_BASE,
+                                                               true, true,
                                                                pt, ptlim);
                                        continue;
                                case 'Z':
@@ -427,7 +450,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
                                        continue;
                                case 'z':
                                        {
-                                               int                     diff;
+                                               long            diff;
                                                char const *sign;
 
                                                if (t->tm_isdst < 0)
@@ -441,9 +464,10 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
                                                else
                                                        sign = "+";
                                                pt = _add(sign, pt, ptlim);
-                                               diff /= 60;
-                                               pt = _conv((diff / 60) * 100 + diff % 60,
-                                                                  "%04d", pt, ptlim);
+                                               diff /= SECSPERMIN;
+                                               diff = (diff / MINSPERHOUR) * 100 +
+                                                       (diff % MINSPERHOUR);
+                                               pt = _conv(diff, "%04d", pt, ptlim);
                                        }
                                        continue;
                                case '+':
@@ -473,7 +497,7 @@ _conv(int n, const char *format, char *pt, const char *ptlim)
 {
        char            buf[INT_STRLEN_MAXIMUM(int) +1];
 
-       (void) sprintf(buf, format, n);
+       sprintf(buf, format, n);
        return _add(buf, pt, ptlim);
 }
 
@@ -493,13 +517,13 @@ _add(const char *str, char *pt, const char *ptlim)
  * with more only if necessary.
  */
 static char *
-_yconv(const int a, const int b, const int convert_top,
-          const int convert_yy, char *pt, const char *const ptlim)
+_yconv(int a, int b, bool convert_top, bool convert_yy,
+          char *pt, const char *ptlim)
 {
        int                     lead;
        int                     trail;
 
-#define DIVISOR                  100
+#define DIVISOR 100
        trail = a % DIVISOR + b % DIVISOR;
        lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
        trail %= DIVISOR;
index 065db9032eea05512e385ac0032de183d81cfdba..32d237b8270ae1960449f096c94adc236eccbbb5 100644 (file)
@@ -33,8 +33,8 @@
 struct tzhead
 {
        char            tzh_magic[4];   /* TZ_MAGIC */
-       char            tzh_version[1]; /* '\0' or '2' as of 2005 */
-       char            tzh_reserved[15];               /* reserved--must be zero */
+       char            tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */
+       char            tzh_reserved[15];               /* reservedmust be zero */
        char            tzh_ttisgmtcnt[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 */
@@ -43,30 +43,29 @@ struct tzhead
        char            tzh_charcnt[4]; /* coded number of abbr. chars */
 };
 
-/*----------
+/*
  * . . .followed by. . .
  *
  *     tzh_timecnt (char [4])s         coded transition times a la time(2)
  *     tzh_timecnt (unsigned char)s    types of local time starting at above
  *     tzh_typecnt repetitions of
- *             one (char [4])          coded UTC offset in seconds
+ *             one (char [4])          coded UT offset in seconds
  *             one (unsigned char) used to set tm_isdst
  *             one (unsigned char) that's an abbreviation list index
  *     tzh_charcnt (char)s             '\0'-terminated zone abbreviations
  *     tzh_leapcnt repetitions of
  *             one (char [4])          coded leap second transition times
  *             one (char [4])          total correction after above
- *     tzh_ttisstdcnt (char)s          indexed by type; if TRUE, transition
- *                                     time is standard time, if FALSE,
+ *     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 TRUE, transition
- *                                     time is UTC, if FALSE,
+ *     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
  *                                     assumed to be local time
- *----------
  */
 
 /*
@@ -77,6 +76,13 @@ struct tzhead
  * instants after the last transition time stored in the file
  * (with nothing between the newlines if there is no POSIX representation for
  * such instants).
+ *
+ * If tz_version is '3' or greater, the above is extended as follows.
+ * First, the POSIX TZ string's hour offset may range from -167
+ * through 167 as compared to the POSIX-required 0 through 24.
+ * Second, its DST start time may be January 1 at 00:00 and its stop
+ * time December 31 at 24:00 plus the difference between DST and
+ * standard time, indicating DST all year.
  */
 
 /*
@@ -84,8 +90,9 @@ struct tzhead
  * exceed any of the limits below.
  */
 
-#define TZ_MAX_TIMES   1200
+#define TZ_MAX_TIMES   2000
 
+/* This must be at least 17 for Europe/Samara and Europe/Vilnius.  */
 #define TZ_MAX_TYPES   256             /* Limited by what (unsigned char)'s can hold */
 
 #define TZ_MAX_CHARS   50              /* Maximum number of abbreviation characters */
@@ -100,7 +107,7 @@ struct tzhead
 #define DAYSPERNYEAR   365
 #define DAYSPERLYEAR   366
 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
-#define SECSPERDAY     ((long) SECSPERHOUR * HOURSPERDAY)
+#define SECSPERDAY     ((int32) SECSPERHOUR * HOURSPERDAY)
 #define MONSPERYEAR 12
 
 #define TM_SUNDAY      0
@@ -143,6 +150,6 @@ struct tzhead
  * We use this to avoid addition overflow problems.
  */
 
-#define isleap_sum(a, b)         isleap((a) % 400 + (b) % 400)
+#define isleap_sum(a, b)       isleap((a) % 400 + (b) % 400)
 
 #endif   /* !defined TZFILE_H */
index 9fc20c6bac7bf405e6ac0b52f2feded79179597d..8d4347a47a31fbd27452b76bae74b6fd3acd12dc 100644 (file)
@@ -8,28 +8,27 @@
 
 #include "postgres_fe.h"
 
-#include <limits.h>
 #include <locale.h>
+#include <sys/stat.h>
 #include <time.h>
 
 #include "pg_getopt.h"
 
 #include "private.h"
-#include "pgtz.h"
 #include "tzfile.h"
 
-#define                  ZIC_VERSION     '2'
+#define ZIC_VERSION_PRE_2013 '2'
+#define ZIC_VERSION '3'
 
 typedef int64 zic_t;
+#define ZIC_MIN PG_INT64_MIN
+#define ZIC_MAX PG_INT64_MAX
+#define SCNdZIC INT64_MODIFIER "d"
 
 #ifndef ZIC_MAX_ABBR_LEN_WO_WARN
 #define ZIC_MAX_ABBR_LEN_WO_WARN         6
 #endif   /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
 
-#ifdef HAVE_SYS_STAT_H
-#include <sys/stat.h>
-#endif
-
 #ifndef WIN32
 #ifdef S_IRUSR
 #define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
@@ -38,37 +37,17 @@ typedef int64 zic_t;
 #endif
 #endif
 
-static char elsieid[] = "@(#)zic.c     8.20";
-
-/*
- * On some ancient hosts, predicates like `isspace(C)' are defined
- * only if isascii(C) || C == EOF. Modern hosts obey the C Standard,
- * which says they are defined only if C == ((unsigned char) C) || C == EOF.
- * Neither the C Standard nor Posix require that `isascii' exist.
- * For portability, we check both ancient and modern requirements.
- * If isascii is not defined, the isascii check succeeds trivially.
- */
-#include <ctype.h>
-#ifndef isascii
-#define isascii(x) 1
-#endif
-
-#define OFFSET_STRLEN_MAXIMUM (7 + INT_STRLEN_MAXIMUM(long))
-#define RULE_STRLEN_MAXIMUM   8 /* "Mdd.dd.d" */
-
-#define end(cp)                  (strchr((cp), '\0'))
-
 struct rule
 {
        const char *r_filename;
        int                     r_linenum;
        const char *r_name;
 
-       int                     r_loyear;               /* for example, 1986 */
-       int                     r_hiyear;               /* for example, 1986 */
+       zic_t           r_loyear;               /* for example, 1986 */
+       zic_t           r_hiyear;               /* for example, 1986 */
        const char *r_yrtype;
-       int                     r_lowasnum;
-       int                     r_hiwasnum;
+       bool            r_lowasnum;
+       bool            r_hiwasnum;
 
        int                     r_month;                /* 0..11 */
 
@@ -76,12 +55,11 @@ struct rule
        int                     r_dayofmonth;
        int                     r_wday;
 
-       long            r_tod;                  /* time from midnight */
-       int                     r_todisstd;             /* above is standard time if TRUE */
-       /* or wall clock time if FALSE */
-       int                     r_todisgmt;             /* above is GMT if TRUE */
-       /* or local time if FALSE */
-       long            r_stdoff;               /* offset from standard time */
+       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 */
+       zic_t           r_stdoff;               /* offset from standard time */
        const char *r_abbrvar;          /* variable part of abbreviation */
 
        int                     r_todo;                 /* a rule to do (used in outzone) */
@@ -102,11 +80,12 @@ struct zone
        int                     z_linenum;
 
        const char *z_name;
-       long            z_gmtoff;
+       zic_t           z_gmtoff;
        const char *z_rule;
        const char *z_format;
+       char            z_format_specifier;
 
-       long            z_stdoff;
+       zic_t           z_stdoff;
 
        struct rule *z_rules;
        int                     z_nrules;
@@ -116,74 +95,69 @@ struct zone
 };
 
 extern int     link(const char *fromname, const char *toname);
-static void addtt(const pg_time_t starttime, int type);
-static int addtype(long gmtoff, const char *abbr, int isdst,
-               int ttisstd, int ttisgmt);
-static void leapadd(const pg_time_t t, int positive, int rolling, int count);
+
+static void memory_exhausted(const char *msg) pg_attribute_noreturn();
+static void verror(const char *string, va_list args) pg_attribute_printf(1, 0);
+static void error(const char *string,...) pg_attribute_printf(1, 2);
+static void warning(const char *string,...) pg_attribute_printf(1, 2);
+static void usage(FILE *stream, int status) pg_attribute_noreturn();
+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 adjleap(void);
 static void associate(void);
-static int     ciequal(const char *ap, const char *bp);
-static void convert(long val, char *buf);
-static void dolink(const char *fromfile, const char *tofile);
-static void doabbr(char *abbr, const char *format,
-          const char *letters, int isdst, int doquotes);
-static void eat(const char *name, int num);
-static void eats(const char *name, int num,
-        const char *rname, int rnum);
-static long eitol(int i);
-static void error(const char *message);
+static void dolink(const char *fromfield, const char *tofield);
 static char **getfields(char *buf);
-static long gethms(const char *string, const char *errstrng,
-          int signable);
+static zic_t gethms(const char *string, const char *errstring,
+          bool);
 static void infile(const char *filename);
 static void inleap(char **fields, int nfields);
 static void inlink(char **fields, int nfields);
 static void inrule(char **fields, int nfields);
-static int     inzcont(char **fields, int nfields);
-static int     inzone(char **fields, int nfields);
-static int     inzsub(char **fields, int nfields, int iscont);
-static int     itsabbr(const char *abbr, const char *word);
+static bool inzcont(char **fields, int nfields);
+static bool inzone(char **fields, int nfields);
+static bool inzsub(char **, int, bool);
 static int     itsdir(const char *name);
-static int     lowerit(int c);
-static char *memcheck(char *tocheck);
-static int     mkdirs(char *filename);
+static bool is_alpha(char a);
+static char lowerit(char);
+static bool mkdirs(char *);
 static void newabbr(const char *abbr);
-static long oadd(long t1, long t2);
+static zic_t oadd(zic_t t1, zic_t t2);
 static void outzone(const struct zone * zp, int ntzones);
-static void puttzcode(long code, FILE *fp);
-static int     rcomp(const void *leftp, const void *rightp);
-static pg_time_t rpytime(const struct rule * rp, int wantedy);
+static zic_t rpytime(const struct rule * rp, zic_t wantedy);
 static void rulesub(struct rule * rp,
                const char *loyearp, const char *hiyearp,
                const char *typep, const char *monthp,
                const char *dayp, const char *timep);
-static void setboundaries(void);
-static pg_time_t tadd(const pg_time_t t1, long t2);
-static void usage(FILE *stream, int status);
-static void writezone(const char *name, const char *string);
-static int     yearistype(int year, const char *type);
+static zic_t tadd(zic_t t1, zic_t t2);
+static bool yearistype(int year, const char *type);
+
+/* Bound on length of what %z can expand to.  */
+enum
+{
+PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};
 
 static int     charcnt;
-static int     errors;
+static bool errors;
+static bool warnings;
 static const char *filename;
 static int     leapcnt;
-static int     leapseen;
-static int     leapminyear;
-static int     leapmaxyear;
+static bool leapseen;
+static zic_t leapminyear;
+static zic_t leapmaxyear;
 static int     linenum;
-static int     max_abbrvar_len;
+static int     max_abbrvar_len = PERCENT_Z_LEN_BOUND;
 static int     max_format_len;
-static zic_t max_time;
-static int     max_year;
-static zic_t min_time;
-static int     min_year;
-static int     noise;
-static int     print_abbrevs;
+static zic_t max_year;
+static zic_t min_year;
+static bool noise;
+static bool print_abbrevs;
 static zic_t print_cutoff;
 static const char *rfilename;
 static int     rlinenum;
 static const char *progname;
 static int     timecnt;
+static int     timecnt_alloc;
 static int     typecnt;
 
 /*
@@ -269,9 +243,11 @@ static int typecnt;
 
 static struct rule *rules;
 static int     nrules;                         /* number of rules */
+static int     nrules_alloc;
 
 static struct zone *zones;
 static int     nzones;                         /* number of zones */
+static int     nzones_alloc;
 
 struct link
 {
@@ -283,6 +259,7 @@ struct link
 
 static struct link *links;
 static int     nlinks;
+static int     nlinks_alloc;
 
 struct lookup
 {
@@ -353,8 +330,8 @@ static struct lookup const end_years[] = {
 };
 
 static struct lookup const leap_types[] = {
-       {"Rolling", TRUE},
-       {"Stationary", FALSE},
+       {"Rolling", true},
+       {"Stationary", false},
        {NULL, 0}
 };
 
@@ -371,40 +348,78 @@ static struct attype
 {
        zic_t           at;
        unsigned char type;
-}      attypes[TZ_MAX_TIMES];
-static long gmtoffs[TZ_MAX_TYPES];
+}      *attypes;
+static zic_t gmtoffs[TZ_MAX_TYPES];
 static char isdsts[TZ_MAX_TYPES];
 static unsigned char abbrinds[TZ_MAX_TYPES];
-static char ttisstds[TZ_MAX_TYPES];
-static char ttisgmts[TZ_MAX_TYPES];
+static bool ttisstds[TZ_MAX_TYPES];
+static bool ttisgmts[TZ_MAX_TYPES];
 static char chars[TZ_MAX_CHARS];
 static zic_t trans[TZ_MAX_LEAPS];
-static long corr[TZ_MAX_LEAPS];
+static zic_t corr[TZ_MAX_LEAPS];
 static char roll[TZ_MAX_LEAPS];
 
 /*
  * Memory allocation.
  */
 
-static char *
-memcheck(char *ptr)
+static void
+memory_exhausted(const char *msg)
+{
+       fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
+       exit(EXIT_FAILURE);
+}
+
+static size_t
+size_product(size_t nitems, size_t itemsize)
+{
+       if (SIZE_MAX / itemsize < nitems)
+               memory_exhausted(_("size overflow"));
+       return nitems * itemsize;
+}
+
+static void *
+memcheck(void *ptr)
 {
        if (ptr == NULL)
+               memory_exhausted(strerror(errno));
+       return ptr;
+}
+
+static void *
+emalloc(size_t size)
+{
+       return memcheck(malloc(size));
+}
+
+static void *
+erealloc(void *ptr, size_t size)
+{
+       return memcheck(realloc(ptr, size));
+}
+
+static char *
+ecpyalloc(char const * str)
+{
+       return memcheck(strdup(str));
+}
+
+static void *
+growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc)
+{
+       if (nitems < *nitems_alloc)
+               return ptr;
+       else
        {
-               const char *e = strerror(errno);
+               int                     amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX;
 
-               (void) fprintf(stderr, _("%s: Memory exhausted: %s\n"),
-                                          progname, e);
-               exit(EXIT_FAILURE);
+               if ((amax - 1) / 3 * 2 < *nitems_alloc)
+                       memory_exhausted(_("int overflow"));
+               *nitems_alloc = *nitems_alloc + (*nitems_alloc >> 1) + 1;
+               return erealloc(ptr, size_product(*nitems_alloc, itemsize));
        }
-       return ptr;
 }
 
-#define emalloc(size)          memcheck(imalloc(size))
-#define erealloc(ptr, size) memcheck(irealloc((ptr), (size)))
-#define ecpyalloc(ptr)         memcheck(icpyalloc(ptr))
-#define ecatalloc(oldp, newp)  memcheck(icatalloc((oldp), (newp)))
-
 /*
  * Error handling.
  */
@@ -421,46 +436,75 @@ eats(const char *name, int num, const char *rname, int rnum)
 static void
 eat(const char *name, int num)
 {
-       eats(name, num, (char *) NULL, -1);
+       eats(name, num, NULL, -1);
 }
 
 static void
-error(const char *string)
+verror(const char *string, va_list args)
 {
        /*
         * Match the format of "cc" to allow sh users to  zic ... 2>&1 | error -t
         * "*" -v on BSD systems.
         */
-       (void) fprintf(stderr, _("\"%s\", line %d: %s"),
-                                  filename, linenum, string);
+       if (filename)
+               fprintf(stderr, _("\"%s\", line %d: "), filename, linenum);
+       vfprintf(stderr, string, args);
        if (rfilename != NULL)
-               (void) fprintf(stderr, _(" (rule from \"%s\", line %d)"),
-                                          rfilename, rlinenum);
-       (void) fprintf(stderr, "\n");
-       ++errors;
+               fprintf(stderr, _(" (rule from \"%s\", line %d)"),
+                               rfilename, rlinenum);
+       fprintf(stderr, "\n");
 }
 
 static void
-warning(const char *string)
+error(const char *string,...)
 {
-       char       *cp;
+       va_list         args;
+
+       va_start(args, string);
+       verror(string, args);
+       va_end(args);
+       errors = true;
+}
+
+static void
+warning(const char *string,...)
+{
+       va_list         args;
+
+       fprintf(stderr, _("warning: "));
+       va_start(args, string);
+       verror(string, args);
+       va_end(args);
+       warnings = true;
+}
+
+static void
+close_file(FILE *stream, char const * name)
+{
+       char const *e = (ferror(stream) ? _("I/O error")
+                                        : fclose(stream) != 0 ? strerror(errno) : NULL);
 
-       cp = ecpyalloc(_("warning: "));
-       cp = ecatalloc(cp, string);
-       error(cp);
-       ifree(cp);
-       --errors;
+       if (e)
+       {
+               fprintf(stderr, "%s: ", progname);
+               if (name)
+                       fprintf(stderr, "%s: ", name);
+               fprintf(stderr, "%s\n", e);
+               exit(EXIT_FAILURE);
+       }
 }
 
 static void
 usage(FILE *stream, int status)
 {
-       (void) fprintf(stream, _("%s: usage is %s \
-[ --version ] [ --help ] [ -v ] [ -P ] [ -l localtime ] [ -p posixrules ] \\\n\
-\t[ -d directory ] [ -L leapseconds ] [ -y yearistype ] [ filename ... ]\n\
-\n\
-Report bugs to tz@elsie.nci.nih.gov.\n"),
-                                  progname, progname);
+       fprintf(stream,
+                       _("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n"
+                         "\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n"
+                         "\t[ -L leapseconds ] [ filename ... ]\n\n"
+                         "Report bugs to %s.\n"),
+                       progname, progname, PACKAGE_BUGREPORT);
+       if (status == EXIT_SUCCESS)
+               close_file(stream, NULL);
        exit(status);
 }
 
@@ -478,20 +522,21 @@ main(int argc, char *argv[])
        int                     c;
 
 #ifndef WIN32
-       (void) umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
+       umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
 #endif   /* !WIN32 */
        progname = argv[0];
        if (TYPE_BIT(zic_t) <64)
        {
-               (void) fprintf(stderr, "%s: %s\n", progname,
-                                          _("wild compilation-time specification of zic_t"));
-               exit(EXIT_FAILURE);
+               fprintf(stderr, "%s: %s\n", progname,
+                               _("wild compilation-time specification of zic_t"));
+               return EXIT_FAILURE;
        }
        for (i = 1; i < argc; ++i)
                if (strcmp(argv[i], "--version") == 0)
                {
-                       (void) printf("%s\n", elsieid);
-                       exit(EXIT_SUCCESS);
+                       printf("zic %s\n", PG_VERSION);
+                       close_file(stdout, NULL);
+                       return EXIT_SUCCESS;
                }
                else if (strcmp(argv[i], "--help") == 0)
                {
@@ -507,10 +552,10 @@ main(int argc, char *argv[])
                                        directory = strdup(optarg);
                                else
                                {
-                                       (void) fprintf(stderr,
-                                                               _("%s: More than one -d option specified\n"),
-                                                                  progname);
-                                       exit(EXIT_FAILURE);
+                                       fprintf(stderr,
+                                                       _("%s: More than one -d option specified\n"),
+                                                       progname);
+                                       return EXIT_FAILURE;
                                }
                                break;
                        case 'l':
@@ -518,10 +563,10 @@ main(int argc, char *argv[])
                                        lcltime = strdup(optarg);
                                else
                                {
-                                       (void) fprintf(stderr,
-                                                               _("%s: More than one -l option specified\n"),
-                                                                  progname);
-                                       exit(EXIT_FAILURE);
+                                       fprintf(stderr,
+                                                       _("%s: More than one -l option specified\n"),
+                                                       progname);
+                                       return EXIT_FAILURE;
                                }
                                break;
                        case 'p':
@@ -529,10 +574,10 @@ main(int argc, char *argv[])
                                        psxrules = strdup(optarg);
                                else
                                {
-                                       (void) fprintf(stderr,
-                                                               _("%s: More than one -p option specified\n"),
-                                                                  progname);
-                                       exit(EXIT_FAILURE);
+                                       fprintf(stderr,
+                                                       _("%s: More than one -p option specified\n"),
+                                                       progname);
+                                       return EXIT_FAILURE;
                                }
                                break;
                        case 'y':
@@ -540,10 +585,10 @@ main(int argc, char *argv[])
                                        yitcommand = strdup(optarg);
                                else
                                {
-                                       (void) fprintf(stderr,
-                                                               _("%s: More than one -y option specified\n"),
-                                                                  progname);
-                                       exit(EXIT_FAILURE);
+                                       fprintf(stderr,
+                                                       _("%s: More than one -y option specified\n"),
+                                                       progname);
+                                       return EXIT_FAILURE;
                                }
                                break;
                        case 'L':
@@ -551,21 +596,21 @@ main(int argc, char *argv[])
                                        leapsec = strdup(optarg);
                                else
                                {
-                                       (void) fprintf(stderr,
-                                                               _("%s: More than one -L option specified\n"),
-                                                                  progname);
-                                       exit(EXIT_FAILURE);
+                                       fprintf(stderr,
+                                                       _("%s: More than one -L option specified\n"),
+                                                       progname);
+                                       return EXIT_FAILURE;
                                }
                                break;
                        case 'v':
-                               noise = TRUE;
+                               noise = true;
                                break;
                        case 'P':
-                               print_abbrevs = TRUE;
+                               print_abbrevs = true;
                                print_cutoff = time(NULL);
                                break;
                        case 's':
-                               (void) printf("%s: -s ignored\n", progname);
+                               warning(_("-s ignored"));
                                break;
                }
        if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
@@ -575,8 +620,6 @@ main(int argc, char *argv[])
        if (yitcommand == NULL)
                yitcommand = "yearistype";
 
-       setboundaries();
-
        if (optind < argc && leapsec != NULL)
        {
                infile(leapsec);
@@ -586,7 +629,7 @@ main(int argc, char *argv[])
        for (i = optind; i < argc; ++i)
                infile(argv[i]);
        if (errors)
-               exit(EXIT_FAILURE);
+               return EXIT_FAILURE;
        associate();
        for (i = 0; i < nzones; i = j)
        {
@@ -613,110 +656,284 @@ main(int argc, char *argv[])
        }
        if (lcltime != NULL)
        {
-               eat("command line", 1);
+               eat(_("command line"), 1);
                dolink(lcltime, TZDEFAULT);
        }
        if (psxrules != NULL)
        {
-               eat("command line", 1);
+               eat(_("command line"), 1);
                dolink(psxrules, TZDEFRULES);
        }
-       return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+       if (warnings && (ferror(stderr) || fclose(stderr) != 0))
+               return EXIT_FAILURE;
+       return errors ? EXIT_FAILURE : EXIT_SUCCESS;
 }
 
-static void
-dolink(const char *fromfield, const char *tofield)
+static bool
+componentcheck(char const * name, char const * component,
+                          char const * component_end)
 {
-       char       *fromname;
-       char       *toname;
+       enum
+       {
+       component_len_max = 14};
+       size_t          component_len = component_end - component;
 
-       if (fromfield[0] == '/')
-               fromname = ecpyalloc(fromfield);
-       else
+       if (component_len == 0)
+       {
+               if (!*name)
+                       error(_("empty file name"));
+               else
+                       error(_(component == name
+                                       ? "file name '%s' begins with '/'"
+                                       : *component_end
+                                       ? "file name '%s' contains '//'"
+                                       : "file name '%s' ends with '/'"),
+                                 name);
+               return false;
+       }
+       if (0 < component_len && component_len <= 2
+               && component[0] == '.' && component_end[-1] == '.')
+       {
+               error(_("file name '%s' contains '%.*s' component"),
+                         name, (int) component_len, component);
+               return false;
+       }
+       if (noise)
+       {
+               if (0 < component_len && component[0] == '-')
+                       warning(_("file name '%s' component contains leading '-'"),
+                                       name);
+               if (component_len_max < component_len)
+                       warning(_("file name '%s' contains overlength component"
+                                         " '%.*s...'"),
+                                       name, component_len_max, component);
+       }
+       return true;
+}
+
+static bool
+namecheck(const char *name)
+{
+       char const *cp;
+
+       /* Benign characters in a portable file name.  */
+       static char const benign[] =
+       "-/_"
+       "abcdefghijklmnopqrstuvwxyz"
+       "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+       /*
+        * Non-control chars in the POSIX portable character set, excluding the
+        * benign characters.
+        */
+       static char const printable_and_not_benign[] =
+       " !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";
+
+       char const *component = name;
+
+       for (cp = name; *cp; cp++)
        {
-               fromname = ecpyalloc(directory);
-               fromname = ecatalloc(fromname, "/");
-               fromname = ecatalloc(fromname, fromfield);
+               unsigned char c = *cp;
+
+               if (noise && !strchr(benign, c))
+               {
+                       warning((strchr(printable_and_not_benign, c)
+                                        ? _("file name '%s' contains byte '%c'")
+                                        : _("file name '%s' contains byte '\\%o'")),
+                                       name, c);
+               }
+               if (c == '/')
+               {
+                       if (!componentcheck(name, component, cp))
+                               return false;
+                       component = cp + 1;
+               }
        }
-       if (tofield[0] == '/')
-               toname = ecpyalloc(tofield);
+       return componentcheck(name, component, cp);
+}
+
+static char *
+relname(char const * dir, char const * base)
+{
+       if (*base == '/')
+               return ecpyalloc(base);
        else
        {
-               toname = ecpyalloc(directory);
-               toname = ecatalloc(toname, "/");
-               toname = ecatalloc(toname, tofield);
+               size_t          dir_len = strlen(dir);
+               bool            needs_slash = dir_len && dir[dir_len - 1] != '/';
+               char       *result = emalloc(dir_len + needs_slash + strlen(base) + 1);
+
+               result[dir_len] = '/';
+               strcpy(result + dir_len + needs_slash, base);
+               return memcpy(result, dir, dir_len);
        }
+}
+
+static void
+dolink(char const * fromfield, char const * tofield)
+{
+       char       *fromname;
+       char       *toname;
+       int                     fromisdir;
+
+       fromname = relname(directory, fromfield);
+       toname = relname(directory, tofield);
 
        /*
         * We get to be careful here since there's a fair chance of root running
         * us.
         */
-       if (!itsdir(toname))
-               (void) remove(toname);
-       if (link(fromname, toname) != 0)
+       fromisdir = itsdir(fromname);
+       if (fromisdir)
        {
-               int                     result;
+               char const *e = strerror(fromisdir < 0 ? errno : EPERM);
 
-               if (mkdirs(toname) != 0)
-                       exit(EXIT_FAILURE);
+               fprintf(stderr, _("%s: link from %s failed: %s"),
+                               progname, fromname, e);
+               exit(EXIT_FAILURE);
+       }
+       if (link(fromname, toname) != 0)
+       {
+               int                     link_errno = errno;
+               bool            retry_if_link_supported = false;
 
-               result = link(fromname, toname);
-#ifdef HAVE_SYMLINK
-               if (result != 0 &&
-                       access(fromname, F_OK) == 0 &&
-                       !itsdir(fromname))
+               if (link_errno == ENOENT || link_errno == ENOTSUP)
                {
-                       const char *s = tofield;
-                       char       *symlinkcontents = NULL;
-
-                       while ((s = strchr(s + 1, '/')) != NULL)
-                               symlinkcontents = ecatalloc(symlinkcontents, "../");
-                       symlinkcontents = ecatalloc(symlinkcontents, fromfield);
-
-                       result = symlink(symlinkcontents, toname);
-                       if (result == 0)
-                               warning(_("hard link failed, symbolic link used"));
-                       ifree(symlinkcontents);
+                       if (!mkdirs(toname))
+                               exit(EXIT_FAILURE);
+                       retry_if_link_supported = true;
                }
-#endif
-               if (result != 0)
+               if ((link_errno == EEXIST || link_errno == ENOTSUP)
+                       && itsdir(toname) == 0
+                       && (remove(toname) == 0 || errno == ENOENT))
+                       retry_if_link_supported = true;
+               if (retry_if_link_supported && link_errno != ENOTSUP)
+                       link_errno = link(fromname, toname) == 0 ? 0 : errno;
+               if (link_errno != 0)
                {
-                       const char *e = strerror(errno);
+                       const char *s = fromfield;
+                       const char *t;
+                       char       *p;
+                       size_t          dotdots = 0;
+                       char       *symlinkcontents;
+                       int                     symlink_result;
+
+                       do
+                               t = s;
+                       while ((s = strchr(s, '/'))
+                                  && strncmp(fromfield, tofield, ++s - fromfield) == 0);
+
+                       for (s = tofield + (t - fromfield); *s; s++)
+                               dotdots += *s == '/';
+                       symlinkcontents = emalloc(3 * dotdots + strlen(t) + 1);
+                       for (p = symlinkcontents; dotdots-- != 0; p += 3)
+                               memcpy(p, "../", 3);
+                       strcpy(p, t);
+                       symlink_result = symlink(symlinkcontents, toname);
+                       free(symlinkcontents);
+                       if (symlink_result == 0)
+                       {
+                               if (link_errno != ENOTSUP)
+                                       warning(_("symbolic link used because hard link failed: %s"),
+                                                       strerror(link_errno));
+                       }
+                       else
+                       {
+                               FILE       *fp,
+                                                  *tp;
+                               int                     c;
 
-                       (void) fprintf(stderr,
-                                                  _("%s: Cannot link from %s to %s: %s\n"),
-                                                  progname, fromname, toname, e);
-                       exit(EXIT_FAILURE);
+                               fp = fopen(fromname, "rb");
+                               if (!fp)
+                               {
+                                       const char *e = strerror(errno);
+
+                                       fprintf(stderr,
+                                                       _("%s: Can't read %s: %s\n"),
+                                                       progname, fromname, e);
+                                       exit(EXIT_FAILURE);
+                               }
+                               tp = fopen(toname, "wb");
+                               if (!tp)
+                               {
+                                       const char *e = strerror(errno);
+
+                                       fprintf(stderr,
+                                                       _("%s: Can't create %s: %s\n"),
+                                                       progname, toname, e);
+                                       exit(EXIT_FAILURE);
+                               }
+                               while ((c = getc(fp)) != EOF)
+                                       putc(c, tp);
+                               close_file(fp, fromname);
+                               close_file(tp, toname);
+                               if (link_errno != ENOTSUP)
+                                       warning(_("copy used because hard link failed: %s"),
+                                                       strerror(link_errno));
+                       }
                }
        }
-       ifree(fromname);
-       ifree(toname);
+       free(fromname);
+       free(toname);
 }
 
-#define TIME_T_BITS_IN_FILE   64
+#define TIME_T_BITS_IN_FILE 64
 
-static void
-setboundaries(void)
-{
-       int                     i;
+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);
 
-       min_time = -1;
-       for (i = 0; i < TIME_T_BITS_IN_FILE - 1; ++i)
-               min_time *= 2;
-       max_time = -(min_time + 1);
-}
+/*
+ * Estimated time of the Big Bang, in seconds since the POSIX epoch.
+ * rounded downward to the negation of a power of two that is
+ * comfortably outside the error bounds.
+ *
+ * zic does not output time stamps before this, partly because they
+ * are physically suspect, and partly because GNOME mishandles them; see
+ * GNOME bug 730332 <https://bugzilla.gnome.org/show_bug.cgi?id=730332>.
+ *
+ * For the time of the Big Bang, see:
+ *
+ * Ade PAR, Aghanim N, Armitage-Caplan C et al.  Planck 2013 results.
+ * I. Overview of products and scientific results.
+ * arXiv:1303.5062 2013-03-20 20:10:01 UTC
+ * <http://arxiv.org/pdf/1303.5062v1> [PDF]
+ *
+ * Page 36, Table 9, row Age/Gyr, column Planck+WP+highL+BAO 68% limits
+ * gives the value 13.798 plus-or-minus 0.037 billion years.
+ * Multiplying this by 1000000000 and then by 31557600 (the number of
+ * seconds in an astronomical year) gives a value that is comfortably
+ * less than 2**59, so BIG_BANG is - 2**59.
+ *
+ * BIG_BANG is approximate, and may change in future versions.
+ * Please do not rely on its exact value.
+ */
 
+#ifndef BIG_BANG
+#define BIG_BANG (- (((zic_t) 1) << 59))
+#endif
+
+static const zic_t big_bang_time = BIG_BANG;
+
+/* Return 1 if NAME is a directory, 0 if it's something else, -1 if trouble.  */
 static int
-itsdir(const char *name)
+itsdir(char const * name)
 {
-       char       *myname;
-       int                     accres;
+       struct stat st;
+       int                     res = stat(name, &st);
+
+#ifdef S_ISDIR
+       if (res == 0)
+               return S_ISDIR(st.st_mode) != 0;
+#endif
+       if (res == 0 || errno == EOVERFLOW)
+       {
+               char       *nameslashdot = relname(name, ".");
+               bool            dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW;
 
-       myname = ecpyalloc(name);
-       myname = ecatalloc(myname, "/.");
-       accres = access(myname, F_OK);
-       ifree(myname);
-       return accres == 0;
+               free(nameslashdot);
+               return dir;
+       }
+       return -1;
 }
 
 /*
@@ -746,8 +963,7 @@ associate(void)
 
        if (nrules != 0)
        {
-               (void) qsort((void *) rules, (size_t) nrules,
-                                        (size_t) sizeof *rules, rcomp);
+               qsort(rules, nrules, sizeof *rules, rcomp);
                for (i = 0; i < nrules - 1; ++i)
                {
                        if (strcmp(rules[i].r_name,
@@ -807,14 +1023,14 @@ associate(void)
                         */
                        eat(zp->z_filename, zp->z_linenum);
                        zp->z_stdoff = gethms(zp->z_rule, _("unruly zone"),
-                                                                 TRUE);
+                                                                 true);
 
                        /*
                         * Note, though, that if there's no rule, a '%s' in the format is
                         * a bad thing.
                         */
-                       if (strchr(zp->z_format, '%') != NULL)
-                               error(_("%s in ruleless zone"));
+                       if (zp->z_format_specifier == 's')
+                               error("%s", _("%s in ruleless zone"));
                }
        }
        if (errors)
@@ -829,7 +1045,7 @@ infile(const char *name)
        char       *cp;
        const struct lookup *lp;
        int                     nfields;
-       int                     wantcont;
+       bool            wantcont;
        int                     num;
        char            buf[BUFSIZ];
 
@@ -842,15 +1058,15 @@ infile(const char *name)
        {
                const char *e = strerror(errno);
 
-               (void) fprintf(stderr, _("%s: Cannot open %s: %s\n"),
-                                          progname, name, e);
+               fprintf(stderr, _("%s: Cannot open %s: %s\n"),
+                               progname, name, e);
                exit(EXIT_FAILURE);
        }
-       wantcont = FALSE;
+       wantcont = false;
        for (num = 1;; ++num)
        {
                eat(name, num);
-               if (fgets(buf, (int) sizeof buf, fp) != buf)
+               if (fgets(buf, sizeof buf, fp) != buf)
                        break;
                cp = strchr(buf, '\n');
                if (cp == NULL)
@@ -885,66 +1101,53 @@ infile(const char *name)
                                {
                                        case LC_RULE:
                                                inrule(fields, nfields);
-                                               wantcont = FALSE;
+                                               wantcont = false;
                                                break;
                                        case LC_ZONE:
                                                wantcont = inzone(fields, nfields);
                                                break;
                                        case LC_LINK:
                                                inlink(fields, nfields);
-                                               wantcont = FALSE;
+                                               wantcont = false;
                                                break;
                                        case LC_LEAP:
                                                if (name != leapsec)
-                                                       (void) fprintf(stderr,
-                                                       _("%s: Leap line in non leap seconds file %s\n"),
-                                                                                  progname, name);
+                                                       warning(_("%s: Leap line in non leap"
+                                                                         " seconds file %s"),
+                                                                       progname, name);
                                                else
                                                        inleap(fields, nfields);
-                                               wantcont = FALSE;
+                                               wantcont = false;
                                                break;
                                        default:        /* "cannot happen" */
-                                               (void) fprintf(stderr,
-                                                                          _("%s: panic: Invalid l_value %d\n"),
-                                                                          progname, lp->l_value);
+                                               fprintf(stderr,
+                                                               _("%s: panic: Invalid l_value %d\n"),
+                                                               progname, lp->l_value);
                                                exit(EXIT_FAILURE);
                                }
                }
-               ifree((char *) fields);
-       }
-       if (ferror(fp))
-       {
-               (void) fprintf(stderr, _("%s: Error reading %s\n"),
-                                          progname, filename);
-               exit(EXIT_FAILURE);
-       }
-       if (fp != stdin && fclose(fp))
-       {
-               const char *e = strerror(errno);
-
-               (void) fprintf(stderr, _("%s: Error closing %s: %s\n"),
-                                          progname, filename, e);
-               exit(EXIT_FAILURE);
+               free(fields);
        }
+       close_file(fp, filename);
        if (wantcont)
                error(_("expected continuation line not found"));
 }
 
-/*----------
+/*
  * Convert a string of one of the forms
  *     h       -h      hh:mm   -hh:mm  hh:mm:ss        -hh:mm:ss
  * into a number of seconds.
  * A null string maps to zero.
  * Call error with errstring and return zero on errors.
- *----------
  */
-static long
-gethms(const char *string, const char *errstring, int signable)
+static zic_t
+gethms(char const * string, char const * errstring, bool signable)
 {
-       long            hh;
+       zic_t           hh;
        int                     mm,
                                ss,
                                sign;
+       char            xs;
 
        if (string == NULL || *string == '\0')
                return 0;
@@ -957,35 +1160,33 @@ gethms(const char *string, const char *errstring, int signable)
        }
        else
                sign = 1;
-       if (sscanf(string, scheck(string, "%ld"), &hh) == 1)
+       if (sscanf(string, "%" SCNdZIC "%c", &hh, &xs) == 1)
                mm = ss = 0;
-       else if (sscanf(string, scheck(string, "%ld:%d"), &hh, &mm) == 2)
+       else if (sscanf(string, "%" SCNdZIC ":%d%c", &hh, &mm, &xs) == 2)
                ss = 0;
-       else if (sscanf(string, scheck(string, "%ld:%d:%d"),
-                                       &hh, &mm, &ss) != 3)
+       else if (sscanf(string, "%" SCNdZIC ":%d:%d%c", &hh, &mm, &ss, &xs)
+                        != 3)
        {
-               error(errstring);
+               error("%s", errstring);
                return 0;
        }
        if (hh < 0 ||
                mm < 0 || mm >= MINSPERHOUR ||
                ss < 0 || ss > SECSPERMIN)
        {
-               error(errstring);
+               error("%s", errstring);
                return 0;
        }
-       if (LONG_MAX / SECSPERHOUR < hh)
+       if (ZIC_MAX / SECSPERHOUR < hh)
        {
                error(_("time overflow"));
                return 0;
        }
-       if (noise && hh == HOURSPERDAY && mm == 0 && ss == 0)
-               warning(_("24:00 not handled by pre-1998 versions of zic"));
        if (noise && (hh > HOURSPERDAY ||
                                  (hh == HOURSPERDAY && (mm != 0 || ss != 0))))
                warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
-       return oadd(eitol(sign) * hh * eitol(SECSPERHOUR),
-                               eitol(sign) * (eitol(mm) * eitol(SECSPERMIN) + eitol(ss)));
+       return oadd(sign * hh * SECSPERHOUR,
+                               sign * (mm * SECSPERMIN + ss));
 }
 
 static void
@@ -1005,80 +1206,71 @@ inrule(char **fields, int nfields)
        }
        r.r_filename = filename;
        r.r_linenum = linenum;
-       r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), TRUE);
+       r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), true);
        rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
                        fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
        r.r_name = ecpyalloc(fields[RF_NAME]);
        r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
        if (max_abbrvar_len < strlen(r.r_abbrvar))
                max_abbrvar_len = strlen(r.r_abbrvar);
-       rules = (struct rule *) (void *) erealloc((char *) rules,
-                                                                          (int) ((nrules + 1) * sizeof *rules));
+       rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc);
        rules[nrules++] = r;
 }
 
-static int
+static bool
 inzone(char **fields, int nfields)
 {
        int                     i;
-       static char *buf;
 
        if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS)
        {
                error(_("wrong number of fields on Zone line"));
-               return FALSE;
+               return false;
        }
        if (strcmp(fields[ZF_NAME], TZDEFAULT) == 0 && lcltime != NULL)
        {
-               buf = erealloc(buf, (int) (132 + strlen(TZDEFAULT)));
-               (void) sprintf(buf,
-                                 _("\"Zone %s\" line and -l option are mutually exclusive"),
-                                          TZDEFAULT);
-               error(buf);
-               return FALSE;
+               error(
+                         _("\"Zone %s\" line and -l option are mutually exclusive"),
+                         TZDEFAULT);
+               return false;
        }
        if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL)
        {
-               buf = erealloc(buf, (int) (132 + strlen(TZDEFRULES)));
-               (void) sprintf(buf,
-                                 _("\"Zone %s\" line and -p option are mutually exclusive"),
-                                          TZDEFRULES);
-               error(buf);
-               return FALSE;
+               error(
+                         _("\"Zone %s\" line and -p option are mutually exclusive"),
+                         TZDEFRULES);
+               return false;
        }
        for (i = 0; i < nzones; ++i)
                if (zones[i].z_name != NULL &&
                        strcmp(zones[i].z_name, fields[ZF_NAME]) == 0)
                {
-                       buf = erealloc(buf, (int) (132 +
-                                                                          strlen(fields[ZF_NAME]) +
-                                                                          strlen(zones[i].z_filename)));
-                       (void) sprintf(buf,
-                                                  _("duplicate zone name %s (file \"%s\", line %d)"),
-                                                  fields[ZF_NAME],
-                                                  zones[i].z_filename,
-                                                  zones[i].z_linenum);
-                       error(buf);
-                       return FALSE;
+                       error(
+                                 _("duplicate zone name %s (file \"%s\", line %d)"),
+                                 fields[ZF_NAME],
+                                 zones[i].z_filename,
+                                 zones[i].z_linenum);
+                       return false;
                }
-       return inzsub(fields, nfields, FALSE);
+       return inzsub(fields, nfields, false);
 }
 
-static int
+static bool
 inzcont(char **fields, int nfields)
 {
        if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS)
        {
                error(_("wrong number of fields on Zone continuation line"));
-               return FALSE;
+               return false;
        }
-       return inzsub(fields, nfields, TRUE);
+       return inzsub(fields, nfields, true);
 }
 
-static int
-inzsub(char **fields, int nfields, int iscont)
+static bool
+inzsub(char **fields, int nfields, bool iscont)
 {
        char       *cp;
+       char       *cp1;
        static struct zone z;
        int                     i_gmtoff,
                                i_rule,
@@ -1087,7 +1279,7 @@ inzsub(char **fields, int nfields, int iscont)
                                i_untilmonth;
        int                     i_untilday,
                                i_untiltime;
-       int                     hasuntil;
+       bool            hasuntil;
 
        if (iscont)
        {
@@ -1100,6 +1292,8 @@ inzsub(char **fields, int nfields, int iscont)
                i_untiltime = ZFC_TILTIME;
                z.z_name = NULL;
        }
+       else if (!namecheck(fields[ZF_NAME]))
+               return false;
        else
        {
                i_gmtoff = ZF_GMTOFF;
@@ -1113,17 +1307,26 @@ inzsub(char **fields, int nfields, int iscont)
        }
        z.z_filename = filename;
        z.z_linenum = linenum;
-       z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UTC offset"), TRUE);
-       if ((cp = strchr(fields[i_format], '%')) != NULL)
+       z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"), true);
+       if ((cp = strchr(fields[i_format], '%')) != 0)
        {
-               if (*++cp != 's' || strchr(cp, '%') != NULL)
+               if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
+                       || strchr(fields[i_format], '/'))
                {
                        error(_("invalid abbreviation format"));
-                       return FALSE;
+                       return false;
                }
        }
        z.z_rule = ecpyalloc(fields[i_rule]);
-       z.z_format = ecpyalloc(fields[i_format]);
+       z.z_format = cp1 = ecpyalloc(fields[i_format]);
+       z.z_format_specifier = cp ? *cp : '\0';
+       if (z.z_format_specifier == 'z')
+       {
+               if (noise)
+                       warning(_("format '%s' not handled by pre-2015 versions of zic"),
+                                       z.z_format);
+               cp1[cp - fields[i_format]] = 's';
+       }
        if (max_format_len < strlen(z.z_format))
                max_format_len = strlen(z.z_format);
        hasuntil = nfields > i_untilyear;
@@ -1149,11 +1352,10 @@ inzsub(char **fields, int nfields, int iscont)
                        zones[nzones - 1].z_untiltime >= z.z_untiltime)
                {
                        error(_("Zone continuation line end time is not after end time of previous line"));
-                       return FALSE;
+                       return false;
                }
        }
-       zones = (struct zone *) (void *) erealloc((char *) zones,
-                                                                          (int) ((nzones + 1) * sizeof *zones));
+       zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc);
        zones[nzones++] = z;
 
        /*
@@ -1170,12 +1372,13 @@ inleap(char **fields, int nfields)
        const struct lookup *lp;
        int                     i,
                                j;
-       int                     year,
-                               month,
+       zic_t           year;
+       int                     month,
                                day;
-       long            dayoff,
+       zic_t           dayoff,
                                tod;
        zic_t           t;
+       char            xs;
 
        if (nfields != LEAP_FIELDS)
        {
@@ -1184,7 +1387,7 @@ inleap(char **fields, int nfields)
        }
        dayoff = 0;
        cp = fields[LP_YEAR];
-       if (sscanf(cp, scheck(cp, "%d"), &year) != 1)
+       if (sscanf(cp, "%" SCNdZIC "%c", &year, &xs) != 1)
        {
                /*
                 * Leapin' Lizards!
@@ -1196,7 +1399,7 @@ inleap(char **fields, int nfields)
                leapmaxyear = year;
        if (!leapseen || leapminyear > year)
                leapminyear = year;
-       leapseen = TRUE;
+       leapseen = true;
        j = EPOCH_YEAR;
        while (j != year)
        {
@@ -1210,7 +1413,7 @@ inleap(char **fields, int nfields)
                        --j;
                        i = -len_years[isleap(j)];
                }
-               dayoff = oadd(dayoff, eitol(i));
+               dayoff = oadd(dayoff, i);
        }
        if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL)
        {
@@ -1222,17 +1425,17 @@ inleap(char **fields, int nfields)
        while (j != month)
        {
                i = len_months[isleap(year)][j];
-               dayoff = oadd(dayoff, eitol(i));
+               dayoff = oadd(dayoff, i);
                ++j;
        }
        cp = fields[LP_DAY];
-       if (sscanf(cp, scheck(cp, "%d"), &day) != 1 ||
+       if (sscanf(cp, "%d%c", &day, &xs) != 1 ||
                day <= 0 || day > len_months[isleap(year)][month])
        {
                error(_("invalid day of month"));
                return;
        }
-       dayoff = oadd(dayoff, eitol(day - 1));
+       dayoff = oadd(dayoff, day - 1);
        if (dayoff < min_time / SECSPERDAY)
        {
                error(_("time too small"));
@@ -1243,32 +1446,31 @@ inleap(char **fields, int nfields)
                error(_("time too large"));
                return;
        }
-       t = (zic_t) dayoff *SECSPERDAY;
-
-       tod = gethms(fields[LP_TIME], _("invalid time of day"), FALSE);
+       t = dayoff * SECSPERDAY;
+       tod = gethms(fields[LP_TIME], _("invalid time of day"), false);
        cp = fields[LP_CORR];
        {
-               int                     positive;
+               bool            positive;
                int                     count;
 
                if (strcmp(cp, "") == 0)
                {                                               /* infile() turns "-" into "" */
-                       positive = FALSE;
+                       positive = false;
                        count = 1;
                }
                else if (strcmp(cp, "--") == 0)
                {
-                       positive = FALSE;
+                       positive = false;
                        count = 2;
                }
                else if (strcmp(cp, "+") == 0)
                {
-                       positive = TRUE;
+                       positive = true;
                        count = 1;
                }
                else if (strcmp(cp, "++") == 0)
                {
-                       positive = TRUE;
+                       positive = true;
                        count = 2;
                }
                else
@@ -1281,7 +1483,13 @@ inleap(char **fields, int nfields)
                        error(_("illegal Rolling/Stationary field on Leap line"));
                        return;
                }
-               leapadd(tadd(t, tod), positive, lp->l_value, count);
+               t = tadd(t, tod);
+               if (t < big_bang_time)
+               {
+                       error(_("leap second precedes Big Bang"));
+                       return;
+               }
+               leapadd(t, positive, lp->l_value, count);
        }
 }
 
@@ -1300,17 +1508,13 @@ inlink(char **fields, int nfields)
                error(_("blank FROM field on Link line"));
                return;
        }
-       if (*fields[LF_TO] == '\0')
-       {
-               error(_("blank TO field on Link line"));
+       if (!namecheck(fields[LF_TO]))
                return;
-       }
        l.l_filename = filename;
        l.l_linenum = linenum;
        l.l_from = ecpyalloc(fields[LF_FROM]);
        l.l_to = ecpyalloc(fields[LF_TO]);
-       links = (struct link *) (void *) erealloc((char *) links,
-                                                                          (int) ((nlinks + 1) * sizeof *links));
+       links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
        links[nlinks++] = l;
 }
 
@@ -1323,6 +1527,7 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp,
        const char *cp;
        char       *dp;
        char       *ep;
+       char            xs;
 
        if ((lp = byword(monthp, mon_names)) == NULL)
        {
@@ -1330,8 +1535,8 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp,
                return;
        }
        rp->r_month = lp->l_value;
-       rp->r_todisstd = FALSE;
-       rp->r_todisgmt = FALSE;
+       rp->r_todisstd = false;
+       rp->r_todisgmt = false;
        dp = ecpyalloc(timep);
        if (*dp != '\0')
        {
@@ -1339,26 +1544,26 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp,
                switch (lowerit(*ep))
                {
                        case 's':                       /* Standard */
-                               rp->r_todisstd = TRUE;
-                               rp->r_todisgmt = FALSE;
+                               rp->r_todisstd = true;
+                               rp->r_todisgmt = false;
                                *ep = '\0';
                                break;
                        case 'w':                       /* Wall */
-                               rp->r_todisstd = FALSE;
-                               rp->r_todisgmt = FALSE;
+                               rp->r_todisstd = false;
+                               rp->r_todisgmt = false;
                                *ep = '\0';
                                break;
                        case 'g':                       /* Greenwich */
                        case 'u':                       /* Universal */
                        case 'z':                       /* Zulu */
-                               rp->r_todisstd = TRUE;
-                               rp->r_todisgmt = TRUE;
+                               rp->r_todisstd = true;
+                               rp->r_todisgmt = true;
                                *ep = '\0';
                                break;
                }
        }
-       rp->r_tod = gethms(dp, _("invalid time of day"), FALSE);
-       ifree(dp);
+       rp->r_tod = gethms(dp, _("invalid time of day"), false);
+       free(dp);
 
        /*
         * Year work.
@@ -1370,18 +1575,18 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp,
                switch ((int) lp->l_value)
                {
                        case YR_MINIMUM:
-                               rp->r_loyear = INT_MIN;
+                               rp->r_loyear = ZIC_MIN;
                                break;
                        case YR_MAXIMUM:
-                               rp->r_loyear = INT_MAX;
+                               rp->r_loyear = ZIC_MAX;
                                break;
                        default:                        /* "cannot happen" */
-                               (void) fprintf(stderr,
-                                                          _("%s: panic: Invalid l_value %d\n"),
-                                                          progname, lp->l_value);
+                               fprintf(stderr,
+                                               _("%s: panic: Invalid l_value %d\n"),
+                                               progname, lp->l_value);
                                exit(EXIT_FAILURE);
                }
-       else if (sscanf(cp, scheck(cp, "%d"), &rp->r_loyear) != 1)
+       else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_loyear, &xs) != 1)
        {
                error(_("invalid starting year"));
                return;
@@ -1393,21 +1598,21 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp,
                switch ((int) lp->l_value)
                {
                        case YR_MINIMUM:
-                               rp->r_hiyear = INT_MIN;
+                               rp->r_hiyear = ZIC_MIN;
                                break;
                        case YR_MAXIMUM:
-                               rp->r_hiyear = INT_MAX;
+                               rp->r_hiyear = ZIC_MAX;
                                break;
                        case YR_ONLY:
                                rp->r_hiyear = rp->r_loyear;
                                break;
                        default:                        /* "cannot happen" */
-                               (void) fprintf(stderr,
-                                                          _("%s: panic: Invalid l_value %d\n"),
-                                                          progname, lp->l_value);
+                               fprintf(stderr,
+                                               _("%s: panic: Invalid l_value %d\n"),
+                                               progname, lp->l_value);
                                exit(EXIT_FAILURE);
                }
-       else if (sscanf(cp, scheck(cp, "%d"), &rp->r_hiyear) != 1)
+       else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_hiyear, &xs) != 1)
        {
                error(_("invalid ending year"));
                return;
@@ -1456,65 +1661,67 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp,
                        if (*ep++ != '=')
                        {
                                error(_("invalid day of month"));
-                               ifree(dp);
+                               free(dp);
                                return;
                        }
                        if ((lp = byword(dp, wday_names)) == NULL)
                        {
                                error(_("invalid weekday name"));
-                               ifree(dp);
+                               free(dp);
                                return;
                        }
                        rp->r_wday = lp->l_value;
                }
-               if (sscanf(ep, scheck(ep, "%d"), &rp->r_dayofmonth) != 1 ||
+               if (sscanf(ep, "%d%c", &rp->r_dayofmonth, &xs) != 1 ||
                        rp->r_dayofmonth <= 0 ||
                        (rp->r_dayofmonth > len_months[1][rp->r_month]))
                {
                        error(_("invalid day of month"));
-                       ifree(dp);
+                       free(dp);
                        return;
                }
        }
-       ifree(dp);
+       free(dp);
 }
 
 static void
-convert(long val, char *buf)
+convert(const int32 val, char *const buf)
 {
        int                     i;
        int                     shift;
+       unsigned char *const b = (unsigned char *) buf;
 
        for (i = 0, shift = 24; i < 4; ++i, shift -= 8)
-               buf[i] = val >> shift;
+               b[i] = val >> shift;
 }
 
 static void
-convert64(zic_t val, char *buf)
+convert64(const zic_t val, char *const buf)
 {
        int                     i;
        int                     shift;
+       unsigned char *const b = (unsigned char *) buf;
 
        for (i = 0, shift = 56; i < 8; ++i, shift -= 8)
-               buf[i] = val >> shift;
+               b[i] = val >> shift;
 }
 
 static void
-puttzcode(long val, FILE *fp)
+puttzcode(const int32 val, FILE *const fp)
 {
        char            buf[4];
 
        convert(val, buf);
-       (void) fwrite((void *) buf, (size_t) sizeof buf, (size_t) 1, fp);
+       fwrite(buf, sizeof buf, 1, fp);
 }
 
 static void
-puttzcode64(zic_t val, FILE *fp)
+puttzcode64(const zic_t val, FILE *const fp)
 {
        char            buf[8];
 
        convert64(val, buf);
-       (void) fwrite((void *) buf, (size_t) sizeof buf, (size_t) 1, fp);
+       fwrite(buf, sizeof buf, 1, fp);
 }
 
 static int
@@ -1526,14 +1733,14 @@ atcomp(const void *avp, const void *bvp)
        return (a < b) ? -1 : (a > b);
 }
 
-static int
-is32(zic_t x)
+static bool
+is32(const zic_t x)
 {
        return x == ((zic_t) ((int32) x));
 }
 
 static void
-writezone(const char *name, const char *string)
+writezone(const char *const name, const char *const string, char version)
 {
        FILE       *fp;
        int                     i,
@@ -1543,18 +1750,18 @@ writezone(const char *name, const char *string)
        int                     timecnt32,
                                timei32;
        int                     pass;
-       static char *fullname;
+       char       *fullname;
        static const struct tzhead tzh0;
        static struct tzhead tzh;
-       zic_t           ats[TZ_MAX_TIMES];
-       unsigned char types[TZ_MAX_TIMES];
+       zic_t      *ats = emalloc(size_product(timecnt, sizeof *ats + 1));
+       void       *typesptr = ats + timecnt;
+       unsigned char *types = typesptr;
 
        /*
         * Sort.
         */
        if (timecnt > 1)
-               (void) qsort((void *) attypes, (size_t) timecnt,
-                                        (size_t) sizeof *attypes, atcomp);
+               qsort(attypes, timecnt, sizeof *attypes, atcomp);
 
        /*
         * Optimize.
@@ -1565,21 +1772,17 @@ writezone(const char *name, const char *string)
 
                toi = 0;
                fromi = 0;
-               while (fromi < timecnt && attypes[fromi].at < min_time)
+               while (fromi < timecnt && attypes[fromi].at < big_bang_time)
                        ++fromi;
-               if (isdsts[0] == 0)
-                       while (fromi < timecnt && attypes[fromi].type == 0)
-                               ++fromi;                /* handled by default rule */
                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 > 1 && ((attypes[fromi].at +
+                                                        gmtoffs[attypes[toi - 1].type]) <=
+                                                       (attypes[toi - 1].at +
+                                                        gmtoffs[attypes[toi - 2].type])))
                        {
-                               attypes[toi - 1].type = attypes[fromi].type;
+                               attypes[toi - 1].type =
+                                       attypes[fromi].type;
                                continue;
                        }
                        if (toi == 0 ||
@@ -1588,6 +1791,9 @@ writezone(const char *name, const char *string)
                }
                timecnt = toi;
        }
+       if (noise && timecnt > 1200)
+               warning(_("pre-2014 clients may mishandle"
+                                 " more than 1200 transition times"));
 
        /*
         * Transfer.
@@ -1626,6 +1832,15 @@ writezone(const char *name, const char *string)
                --timecnt32;
                ++timei32;
        }
+
+       /*
+        * Output an INT32_MIN "transition" if appropriate; see below.
+        */
+       if (timei32 > 0 && ats[timei32] > PG_INT32_MIN)
+       {
+               --timei32;
+               ++timecnt32;
+       }
        while (leapcnt32 > 0 && !is32(trans[leapcnt32 - 1]))
                --leapcnt32;
        while (leapcnt32 > 0 && !is32(trans[leapi32]))
@@ -1633,45 +1848,43 @@ writezone(const char *name, const char *string)
                --leapcnt32;
                ++leapi32;
        }
-       fullname = erealloc(fullname,
-                                               (int) (strlen(directory) + 1 + strlen(name) + 1));
-       (void) sprintf(fullname, "%s/%s", directory, name);
+       fullname = relname(directory, name);
 
        /*
         * Remove old file, if any, to snap links.
         */
-       if (!itsdir(fullname) && remove(fullname) != 0 && errno != ENOENT)
+       if (itsdir(fullname) == 0 && remove(fullname) != 0 && errno != ENOENT)
        {
                const char *e = strerror(errno);
 
-               (void) fprintf(stderr, _("%s: Cannot remove %s: %s\n"),
-                                          progname, fullname, e);
+               fprintf(stderr, _("%s: Cannot remove %s: %s\n"),
+                               progname, fullname, e);
                exit(EXIT_FAILURE);
        }
        if ((fp = fopen(fullname, "wb")) == NULL)
        {
-               if (mkdirs(fullname) != 0)
-                       (void) exit(EXIT_FAILURE);
+               if (!mkdirs(fullname))
+                       exit(EXIT_FAILURE);
                if ((fp = fopen(fullname, "wb")) == NULL)
                {
                        const char *e = strerror(errno);
 
-                       (void) fprintf(stderr, _("%s: Cannot create %s: %s\n"),
-                                                  progname, fullname, e);
+                       fprintf(stderr, _("%s: Cannot create %s: %s\n"),
+                                       progname, fullname, e);
                        exit(EXIT_FAILURE);
                }
        }
        for (pass = 1; pass <= 2; ++pass)
        {
-               register int thistimei,
+               int                     thistimei,
                                        thistimecnt;
-               register int thisleapi,
+               int                     thisleapi,
                                        thisleapcnt;
-               register int thistimelim,
+               int                     thistimelim,
                                        thisleaplim;
-               int                     writetype[TZ_MAX_TIMES];
+               int                     writetype[TZ_MAX_TYPES];
                int                     typemap[TZ_MAX_TYPES];
-               register int thistypecnt;
+               int                     thistypecnt;
                char            thischars[TZ_MAX_CHARS];
                char            thischarcnt;
                int                     indmap[TZ_MAX_CHARS];
@@ -1697,24 +1910,79 @@ writezone(const char *name, const char *string)
                if (thistimecnt == 0)
                {
                        /*
-                        * * No transition times fall in the current * (32- or 64-bit)
-                        * window.
+                        * No transition times fall in the current (32- or 64-bit) window.
                         */
                        if (typecnt != 0)
-                               writetype[typecnt - 1] = TRUE;
+                               writetype[typecnt - 1] = true;
                }
                else
                {
                        for (i = thistimei - 1; i < thistimelim; ++i)
                                if (i >= 0)
-                                       writetype[types[i]] = TRUE;
+                                       writetype[types[i]] = true;
 
                        /*
-                        * For America/Godthab and Antarctica/Palmer
+                        * For America/Godthab and Antarctica/Palmer
                         */
                        if (thistimei == 0)
-                               writetype[0] = TRUE;
+                               writetype[0] = true;
+               }
+#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
+
+               /*
+                * For some pre-2011 systems: if the last-to-be-written standard (or
+                * daylight) type has an offset different from the most recently used
+                * offset, append an (unused) copy of the most recently used type (to
+                * help get global "altzone" and "timezone" variables set correctly).
+                */
+               {
+                       int                     mrudst,
+                                               mrustd,
+                                               hidst,
+                                               histd,
+                                               type;
+
+                       hidst = histd = mrudst = mrustd = -1;
+                       for (i = thistimei; i < thistimelim; ++i)
+                               if (isdsts[types[i]])
+                                       mrudst = types[i];
+                               else
+                                       mrustd = types[i];
+                       for (i = 0; i < typecnt; ++i)
+                               if (writetype[i])
+                               {
+                                       if (isdsts[i])
+                                               hidst = i;
+                                       else
+                                               histd = i;
+                               }
+                       if (hidst >= 0 && mrudst >= 0 && hidst != mrudst &&
+                               gmtoffs[hidst] != gmtoffs[mrudst])
+                       {
+                               isdsts[mrudst] = -1;
+                               type = addtype(gmtoffs[mrudst],
+                                                          &chars[abbrinds[mrudst]],
+                                                          true,
+                                                          ttisstds[mrudst],
+                                                          ttisgmts[mrudst]);
+                               isdsts[mrudst] = 1;
+                               writetype[type] = true;
+                       }
+                       if (histd >= 0 && mrustd >= 0 && histd != mrustd &&
+                               gmtoffs[histd] != gmtoffs[mrustd])
+                       {
+                               isdsts[mrustd] = -1;
+                               type = addtype(gmtoffs[mrustd],
+                                                          &chars[abbrinds[mrustd]],
+                                                          false,
+                                                          ttisstds[mrustd],
+                                                          ttisgmts[mrustd]);
+                               isdsts[mrustd] = 0;
+                               writetype[type] = true;
+                       }
                }
+#endif   /* !defined
+                                                                * LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */
                thistypecnt = 0;
                for (i = 0; i < typecnt; ++i)
                        typemap[i] = writetype[i] ? thistypecnt++ : -1;
@@ -1723,7 +1991,7 @@ writezone(const char *name, const char *string)
                thischarcnt = 0;
                for (i = 0; i < typecnt; ++i)
                {
-                       register char *thisabbr;
+                       char       *thisabbr;
 
                        if (!writetype[i])
                                continue;
@@ -1735,23 +2003,22 @@ writezone(const char *name, const char *string)
                                        break;
                        if (j == thischarcnt)
                        {
-                               (void) strcpy(&thischars[(int) thischarcnt],
-                                                         thisabbr);
+                               strcpy(&thischars[(int) thischarcnt],
+                                          thisabbr);
                                thischarcnt += strlen(thisabbr) + 1;
                        }
                        indmap[abbrinds[i]] = j;
                }
-#define DO(field)      (void) fwrite((void *) tzh.field, \
-                               (size_t) sizeof tzh.field, (size_t) 1, fp)
+#define DO(field)      fwrite(tzh.field, sizeof tzh.field, 1, fp)
                tzh = tzh0;
-               (void) strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
-               tzh.tzh_version[0] = ZIC_VERSION;
-               convert(eitol(thistypecnt), tzh.tzh_ttisgmtcnt);
-               convert(eitol(thistypecnt), tzh.tzh_ttisstdcnt);
-               convert(eitol(thisleapcnt), tzh.tzh_leapcnt);
-               convert(eitol(thistimecnt), tzh.tzh_timecnt);
-               convert(eitol(thistypecnt), tzh.tzh_typecnt);
-               convert(eitol(thischarcnt), tzh.tzh_charcnt);
+               strncpy(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(thisleapcnt, tzh.tzh_leapcnt);
+               convert(thistimecnt, tzh.tzh_timecnt);
+               convert(thistypecnt, tzh.tzh_typecnt);
+               convert(thischarcnt, tzh.tzh_charcnt);
                DO(tzh_magic);
                DO(tzh_version);
                DO(tzh_reserved);
@@ -1764,7 +2031,12 @@ writezone(const char *name, const char *string)
 #undef DO
                for (i = thistimei; i < thistimelim; ++i)
                        if (pass == 1)
-                               puttzcode((long) ats[i], fp);
+
+                               /*
+                                * Output an INT32_MIN "transition" if appropriate; see above.
+                                */
+                               puttzcode(((ats[i] < PG_INT32_MIN) ?
+                                                  PG_INT32_MIN : ats[i]), fp);
                        else
                        {
                                puttzcode64(ats[i], fp);
@@ -1779,7 +2051,7 @@ writezone(const char *name, const char *string)
                                        /* filter out assorted junk entries */
                                        if (strcmp(thisabbrev, GRANDPARENTED) != 0 &&
                                                strcmp(thisabbrev, "zzz") != 0)
-                                               fprintf(stdout, "%s\t%ld%s\n",
+                                               fprintf(stdout, "%s\t" INT64_FORMAT "%s\n",
                                                                thisabbrev,
                                                                gmtoffs[tm],
                                                                isdsts[tm] ? "\tD" : "");
@@ -1790,25 +2062,21 @@ writezone(const char *name, const char *string)
                        unsigned char uc;
 
                        uc = typemap[types[i]];
-                       (void) fwrite((void *) &uc,
-                                                 (size_t) sizeof uc,
-                                                 (size_t) 1,
-                                                 fp);
+                       fwrite(&uc, sizeof uc, 1, fp);
                }
                for (i = 0; i < typecnt; ++i)
                        if (writetype[i])
                        {
                                puttzcode(gmtoffs[i], fp);
-                               (void) putc(isdsts[i], fp);
-                               (void) putc((unsigned char) indmap[abbrinds[i]], fp);
+                               putc(isdsts[i], fp);
+                               putc((unsigned char) indmap[abbrinds[i]], fp);
                        }
                if (thischarcnt != 0)
-                       (void) fwrite((void *) thischars,
-                                                 (size_t) sizeof thischars[0],
-                                                 (size_t) thischarcnt, fp);
+                       fwrite(thischars, sizeof thischars[0],
+                                  thischarcnt, fp);
                for (i = thisleapi; i < thisleaplim; ++i)
                {
-                       register zic_t todo;
+                       zic_t           todo;
 
                        if (roll[i])
                        {
@@ -1835,70 +2103,113 @@ writezone(const char *name, const char *string)
                        else
                                todo = trans[i];
                        if (pass == 1)
-                               puttzcode((long) todo, fp);
+                               puttzcode(todo, fp);
                        else
                                puttzcode64(todo, fp);
                        puttzcode(corr[i], fp);
                }
                for (i = 0; i < typecnt; ++i)
                        if (writetype[i])
-                               (void) putc(ttisstds[i], fp);
+                               putc(ttisstds[i], fp);
                for (i = 0; i < typecnt; ++i)
                        if (writetype[i])
-                               (void) putc(ttisgmts[i], fp);
+                               putc(ttisgmts[i], fp);
        }
-       (void) fprintf(fp, "\n%s\n", string);
-       if (ferror(fp) || fclose(fp))
+       fprintf(fp, "\n%s\n", string);
+       close_file(fp, fullname);
+       free(ats);
+       free(fullname);
+}
+
+static char const *
+abbroffset(char *buf, zic_t offset)
+{
+       char            sign = '+';
+       int                     seconds,
+                               minutes;
+
+       if (offset < 0)
        {
-               (void) fprintf(stderr, _("%s: Error writing %s\n"),
-                                          progname, fullname);
-               exit(EXIT_FAILURE);
+               offset = -offset;
+               sign = '-';
+       }
+
+       seconds = offset % SECSPERMIN;
+       offset /= SECSPERMIN;
+       minutes = offset % MINSPERHOUR;
+       offset /= MINSPERHOUR;
+       if (100 <= offset)
+       {
+               error(_("%%z UTC offset magnitude exceeds 99:59:59"));
+               return "%z";
+       }
+       else
+       {
+               char       *p = buf;
+
+               *p++ = sign;
+               *p++ = '0' + offset / 10;
+               *p++ = '0' + offset % 10;
+               if (minutes | seconds)
+               {
+                       *p++ = '0' + minutes / 10;
+                       *p++ = '0' + minutes % 10;
+                       if (seconds)
+                       {
+                               *p++ = '0' + seconds / 10;
+                               *p++ = '0' + seconds % 10;
+                       }
+               }
+               *p = '\0';
+               return buf;
        }
 }
 
-static void
-doabbr(char *abbr, const char *format, const char *letters, int isdst,
-          int doquotes)
+static size_t
+doabbr(char *abbr, struct zone const * zp, char const * letters,
+          zic_t stdoff, bool doquotes)
 {
        char       *cp;
        char       *slashp;
-       int                     len;
+       size_t          len;
+       char const *format = zp->z_format;
 
        slashp = strchr(format, '/');
        if (slashp == NULL)
        {
-               if (letters == NULL)
-                       (void) strcpy(abbr, format);
-               else
-                       (void) sprintf(abbr, format, letters);
+               char            letterbuf[PERCENT_Z_LEN_BOUND + 1];
+
+               if (zp->z_format_specifier == 'z')
+                       letters = abbroffset(letterbuf, zp->z_gmtoff + stdoff);
+               else if (!letters)
+                       letters = "%s";
+               sprintf(abbr, format, letters);
+       }
+       else if (stdoff != 0)
+       {
+               strcpy(abbr, slashp + 1);
        }
-       else if (isdst)
-               (void) strcpy(abbr, slashp + 1);
        else
        {
-               if (slashp > format)
-                       (void) strncpy(abbr, format,
-                                                  (unsigned) (slashp - format));
+               memcpy(abbr, format, slashp - format);
                abbr[slashp - format] = '\0';
        }
-       if (!doquotes)
-               return;
-       for (cp = abbr; *cp != '\0'; ++cp)
-               if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", *cp) == NULL &&
-                       strchr("abcdefghijklmnopqrstuvwxyz", *cp) == NULL)
-                       break;
        len = strlen(abbr);
+       if (!doquotes)
+               return len;
+       for (cp = abbr; is_alpha(*cp); cp++)
+               continue;
        if (len > 0 && *cp == '\0')
-               return;
+               return len;
        abbr[len + 2] = '\0';
        abbr[len + 1] = '>';
-       for (; len > 0; --len)
-               abbr[len] = abbr[len - 1];
+       memmove(abbr + 1, abbr, len);
        abbr[0] = '<';
+       return len + 2;
 }
 
 static void
-updateminmax(int x)
+updateminmax(const zic_t x)
 {
        if (min_year > x)
                min_year = x;
@@ -1907,44 +2218,46 @@ updateminmax(int x)
 }
 
 static int
-stringoffset(char *result, long offset)
+stringoffset(char *result, zic_t offset)
 {
        int                     hours;
        int                     minutes;
        int                     seconds;
+       bool            negative = offset < 0;
+       int                     len = negative;
 
-       result[0] = '\0';
-       if (offset < 0)
+       if (negative)
        {
-               (void) strcpy(result, "-");
                offset = -offset;
+               result[0] = '-';
        }
        seconds = offset % SECSPERMIN;
        offset /= SECSPERMIN;
        minutes = offset % MINSPERHOUR;
        offset /= MINSPERHOUR;
        hours = offset;
-       if (hours >= HOURSPERDAY)
+       if (hours >= HOURSPERDAY * DAYSPERWEEK)
        {
                result[0] = '\0';
-               return -1;
+               return 0;
        }
-       (void) sprintf(end(result), "%d", hours);
+       len += sprintf(result + len, "%d", hours);
        if (minutes != 0 || seconds != 0)
        {
-               (void) sprintf(end(result), ":%02d", minutes);
+               len += sprintf(result + len, ":%02d", minutes);
                if (seconds != 0)
-                       (void) sprintf(end(result), ":%02d", seconds);
+                       len += sprintf(result + len, ":%02d", seconds);
        }
-       return 0;
+       return len;
 }
 
 static int
-stringrule(char *result, const struct rule * rp, long dstoff, long gmtoff)
+stringrule(char *result, const struct rule * const rp, const zic_t dstoff,
+                  const zic_t gmtoff)
 {
-       long            tod;
+       zic_t           tod = rp->r_tod;
+       int                     compat = 0;
 
-       result = end(result);
        if (rp->r_dycode == DC_DOM)
        {
                int                     month,
@@ -1955,17 +2268,26 @@ stringrule(char *result, const struct rule * rp, long dstoff, long gmtoff)
                total = 0;
                for (month = 0; month < rp->r_month; ++month)
                        total += len_months[0][month];
-               (void) sprintf(result, "J%d", total + rp->r_dayofmonth);
+               /* Omit the "J" in Jan and Feb, as that's shorter.  */
+               if (rp->r_month <= 1)
+                       result += sprintf(result, "%d", total + rp->r_dayofmonth - 1);
+               else
+                       result += sprintf(result, "J%d", total + rp->r_dayofmonth);
        }
        else
        {
                int                     week;
+               int                     wday = rp->r_wday;
+               int                     wdayoff;
 
                if (rp->r_dycode == DC_DOWGEQ)
                {
-                       week = 1 + rp->r_dayofmonth / DAYSPERWEEK;
-                       if ((week - 1) * DAYSPERWEEK + 1 != rp->r_dayofmonth)
-                               return -1;
+                       wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK;
+                       if (wdayoff)
+                               compat = 2013;
+                       wday -= wdayoff;
+                       tod += wdayoff * SECSPERDAY;
+                       week = 1 + (rp->r_dayofmonth - 1) / DAYSPERWEEK;
                }
                else if (rp->r_dycode == DC_DOWLEQ)
                {
@@ -1973,37 +2295,64 @@ stringrule(char *result, const struct rule * rp, long dstoff, long gmtoff)
                                week = 5;
                        else
                        {
-                               week = 1 + rp->r_dayofmonth / DAYSPERWEEK;
-                               if (week * DAYSPERWEEK - 1 != rp->r_dayofmonth)
-                                       return -1;
+                               wdayoff = rp->r_dayofmonth % DAYSPERWEEK;
+                               if (wdayoff)
+                                       compat = 2013;
+                               wday -= wdayoff;
+                               tod += wdayoff * SECSPERDAY;
+                               week = rp->r_dayofmonth / DAYSPERWEEK;
                        }
                }
                else
                        return -1;                      /* "cannot happen" */
-               (void) sprintf(result, "M%d.%d.%d",
-                                          rp->r_month + 1, week, rp->r_wday);
+               if (wday < 0)
+                       wday += DAYSPERWEEK;
+               result += sprintf(result, "M%d.%d.%d",
+                                                 rp->r_month + 1, week, wday);
        }
-       tod = rp->r_tod;
        if (rp->r_todisgmt)
                tod += gmtoff;
        if (rp->r_todisstd && rp->r_stdoff == 0)
                tod += dstoff;
-       if (tod < 0)
-       {
-               result[0] = '\0';
-               return -1;
-       }
        if (tod != 2 * SECSPERMIN * MINSPERHOUR)
        {
-               (void) strcat(result, "/");
-               if (stringoffset(end(result), tod) != 0)
+               *result++ = '/';
+               if (!stringoffset(result, tod))
                        return -1;
+               if (tod < 0)
+               {
+                       if (compat < 2013)
+                               compat = 2013;
+               }
+               else if (SECSPERDAY <= tod)
+               {
+                       if (compat < 1994)
+                               compat = 1994;
+               }
        }
-       return 0;
+       return compat;
 }
 
-static void
-stringzone(char *result, const struct zone * zpfirst, int zonecount)
+static int
+rule_cmp(struct rule const * a, struct rule const * b)
+{
+       if (!a)
+               return -!!b;
+       if (!b)
+               return 1;
+       if (a->r_hiyear != b->r_hiyear)
+               return a->r_hiyear < b->r_hiyear ? -1 : 1;
+       if (a->r_month - b->r_month != 0)
+               return a->r_month - b->r_month;
+       return a->r_dayofmonth - b->r_dayofmonth;
+}
+
+enum
+{
+YEAR_BY_YEAR_ZONE = 1};
+
+static int
+stringzone(char *result, const struct zone * const zpfirst, const int zonecount)
 {
        const struct zone *zp;
        struct rule *rp;
@@ -2011,6 +2360,12 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount)
        struct rule *dstrp;
        int                     i;
        const char *abbrvar;
+       int                     compat = 0;
+       int                     c;
+       size_t          len;
+       int                     offsetlen;
+       struct rule stdr,
+                               dstr;
 
        result[0] = '\0';
        zp = zpfirst + zonecount - 1;
@@ -2018,7 +2373,7 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount)
        for (i = 0; i < zp->z_nrules; ++i)
        {
                rp = &zp->z_rules[i];
-               if (rp->r_hiwasnum || rp->r_hiyear != INT_MAX)
+               if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX)
                        continue;
                if (rp->r_yrtype != NULL)
                        continue;
@@ -2027,32 +2382,32 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount)
                        if (stdrp == NULL)
                                stdrp = rp;
                        else
-                               return;
+                               return -1;
                }
                else
                {
                        if (dstrp == NULL)
                                dstrp = rp;
                        else
-                               return;
+                               return -1;
                }
        }
        if (stdrp == NULL && dstrp == NULL)
        {
                /*
-                * There are no rules running through "max". Let's find the latest
-                * rule.
+                * There are no rules running through "max". Find the latest std rule
+                * in stdabbrrp and latest rule of any type in stdrp.
                 */
+               struct rule *stdabbrrp = NULL;
+
                for (i = 0; i < zp->z_nrules; ++i)
                {
                        rp = &zp->z_rules[i];
-                       if (stdrp == NULL || rp->r_hiyear > stdrp->r_hiyear ||
-                               (rp->r_hiyear == stdrp->r_hiyear &&
-                                rp->r_month > stdrp->r_month))
+                       if (rp->r_stdoff == 0 && rule_cmp(stdabbrrp, rp) < 0)
+                               stdabbrrp = rp;
+                       if (rule_cmp(stdrp, rp) < 0)
                                stdrp = rp;
                }
-               if (stdrp != NULL && stdrp->r_stdoff != 0)
-                       return;                         /* We end up in DST (a POSIX no-no). */
 
                /*
                 * Horrid special case: if year is 2037, presume this is a zone
@@ -2060,39 +2415,75 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount)
                 * zone.
                 */
                if (stdrp != NULL && stdrp->r_hiyear == 2037)
-                       return;
+                       return YEAR_BY_YEAR_ZONE;
+
+               if (stdrp != NULL && stdrp->r_stdoff != 0)
+               {
+                       /* 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_stdoff = stdrp->r_stdoff;
+                       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_stdoff = 0;
+                       stdr.r_abbrvar
+                               = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
+                       dstrp = &dstr;
+                       stdrp = &stdr;
+               }
        }
        if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0))
-               return;
+               return -1;
        abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
-       doabbr(result, zp->z_format, abbrvar, FALSE, TRUE);
-       if (stringoffset(end(result), -zp->z_gmtoff) != 0)
+       len = doabbr(result, zp, abbrvar, 0, true);
+       offsetlen = stringoffset(result + len, -zp->z_gmtoff);
+       if (!offsetlen)
        {
                result[0] = '\0';
-               return;
+               return -1;
        }
+       len += offsetlen;
        if (dstrp == NULL)
-               return;
-       doabbr(end(result), zp->z_format, dstrp->r_abbrvar, TRUE, TRUE);
+               return compat;
+       len += doabbr(result + len, zp, dstrp->r_abbrvar, dstrp->r_stdoff, true);
        if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR)
-               if (stringoffset(end(result),
-                                                -(zp->z_gmtoff + dstrp->r_stdoff)) != 0)
+       {
+               offsetlen = stringoffset(result + len,
+                                                                -(zp->z_gmtoff + dstrp->r_stdoff));
+               if (!offsetlen)
                {
                        result[0] = '\0';
-                       return;
+                       return -1;
                }
-       (void) strcat(result, ",");
-       if (stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff) != 0)
+               len += offsetlen;
+       }
+       result[len++] = ',';
+       c = stringrule(result + len, dstrp, dstrp->r_stdoff, zp->z_gmtoff);
+       if (c < 0)
        {
                result[0] = '\0';
-               return;
+               return -1;
        }
-       (void) strcat(result, ",");
-       if (stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff) != 0)
+       if (compat < c)
+               compat = c;
+       len += strlen(result + len);
+       result[len++] = ',';
+       c = stringrule(result + len, stdrp, dstrp->r_stdoff, zp->z_gmtoff);
+       if (c < 0)
        {
                result[0] = '\0';
-               return;
+               return -1;
        }
+       if (compat < c)
+               compat = c;
+       return compat;
 }
 
 static void
@@ -2102,28 +2493,34 @@ outzone(const struct zone * zpfirst, int zonecount)
        struct rule *rp;
        int                     i,
                                j;
-       int                     usestart,
+       bool            usestart,
                                useuntil;
-       zic_t           starttime = 0;
-       zic_t           untiltime = 0;
-       long            gmtoff;
-       long            stdoff;
-       int                     year;
-       long            startoff;
-       int                     startttisstd;
-       int                     startttisgmt;
+       zic_t           starttime,
+                               untiltime;
+       zic_t           gmtoff;
+       zic_t           stdoff;
+       zic_t           year;
+       zic_t           startoff;
+       bool            startttisstd;
+       bool            startttisgmt;
        int                     type;
        char       *startbuf;
        char       *ab;
        char       *envvar;
        int                     max_abbr_len;
        int                     max_envvar_len;
+       bool            prodstic;               /* all rules are min to max */
+       int                     compat;
+       bool            do_extend;
+       char            version;
 
        max_abbr_len = 2 + max_format_len + max_abbrvar_len;
        max_envvar_len = 2 * max_abbr_len + 5 * 9;
        startbuf = emalloc(max_abbr_len + 1);
        ab = emalloc(max_abbr_len + 1);
        envvar = emalloc(max_envvar_len + 1);
+       INITIALIZE(untiltime);
+       INITIALIZE(starttime);
 
        /*
         * Now. . .finally. . .generate some useful data!
@@ -2131,18 +2528,19 @@ outzone(const struct zone * zpfirst, int zonecount)
        timecnt = 0;
        typecnt = 0;
        charcnt = 0;
+       prodstic = zonecount == 1;
 
        /*
         * Thanks to Earl Chew for noting the need to unconditionally initialize
         * startttisstd.
         */
-       startttisstd = FALSE;
-       startttisgmt = FALSE;
+       startttisstd = false;
+       startttisgmt = false;
        min_year = max_year = EPOCH_YEAR;
        if (leapseen)
        {
                updateminmax(leapminyear);
-               updateminmax(leapmaxyear + (leapmaxyear < INT_MAX));
+               updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX));
        }
        for (i = 0; i < zonecount; ++i)
        {
@@ -2156,33 +2554,71 @@ outzone(const struct zone * zpfirst, int zonecount)
                                updateminmax(rp->r_loyear);
                        if (rp->r_hiwasnum)
                                updateminmax(rp->r_hiyear);
+                       if (rp->r_lowasnum || rp->r_hiwasnum)
+                               prodstic = false;
                }
        }
 
        /*
         * Generate lots of data if a rule can't cover all future times.
         */
-       stringzone(envvar, zpfirst, zonecount);
-       if (noise && envvar[0] == '\0')
-       {
-               char       *wp;
-
-               wp = ecpyalloc(_("no POSIX environment variable for zone"));
-               wp = ecatalloc(wp, " ");
-               wp = ecatalloc(wp, zpfirst->z_name);
-               warning(wp);
-               ifree(wp);
+       compat = stringzone(envvar, zpfirst, zonecount);
+       version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION;
+       do_extend = compat < 0 || compat == YEAR_BY_YEAR_ZONE;
+       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)
+               {
+                       /*
+                        * Circa-COMPAT clients, and earlier clients, might not work for
+                        * this zone when given dates before 1970 or after 2038.
+                        */
+                       warning(_("%s: pre-%d clients may mishandle"
+                                         " distant timestamps"),
+                                       zpfirst->z_name, compat);
+               }
        }
-       if (envvar[0] == '\0')
+       if (do_extend)
        {
-               if (min_year >= INT_MIN + YEARSPERREPEAT)
-                       min_year -= YEARSPERREPEAT;
+               /*
+                * Search through a couple of extra years past the obvious 400, to
+                * avoid edge cases.  For example, suppose a non-POSIX rule applies
+                * from 2012 onwards and has transitions in March and September, plus
+                * some one-off transitions in November 2013.  If zic looked only at
+                * the last 400 years, it would set max_year=2413, with the intent
+                * that the 400 years 2014 through 2413 will be repeated.  The last
+                * transition listed in the tzfile would be in 2413-09, less than 400
+                * years after the last one-off transition in 2013-11.  Two years
+                * might be overkill, but with the kind of edge cases available we're
+                * not sure that one year would suffice.
+                */
+               enum
+               {
+               years_of_observations = YEARSPERREPEAT + 2};
+
+               if (min_year >= ZIC_MIN + years_of_observations)
+                       min_year -= years_of_observations;
                else
-                       min_year = INT_MIN;
-               if (max_year <= INT_MAX - YEARSPERREPEAT)
-                       max_year += YEARSPERREPEAT;
+                       min_year = ZIC_MIN;
+               if (max_year <= ZIC_MAX - years_of_observations)
+                       max_year += years_of_observations;
                else
-                       max_year = INT_MAX;
+                       max_year = ZIC_MAX;
+
+               /*
+                * Regardless of any of the above, for a "proDSTic" zone which
+                * specifies that its rules always have and always will be in effect,
+                * we only need one cycle to define the zone.
+                */
+               if (prodstic)
+               {
+                       min_year = 1900;
+                       max_year = min_year + years_of_observations;
+               }
        }
 
        /*
@@ -2199,9 +2635,9 @@ outzone(const struct zone * zpfirst, int zonecount)
                 */
                stdoff = 0;
                zp = &zpfirst[i];
-               usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
+               usestart = i > 0 && (zp - 1)->z_untiltime > big_bang_time;
                useuntil = i < (zonecount - 1);
-               if (useuntil && zp->z_untiltime <= min_time)
+               if (useuntil && zp->z_untiltime <= big_bang_time)
                        continue;
                gmtoff = zp->z_gmtoff;
                eat(zp->z_filename, zp->z_linenum);
@@ -2210,18 +2646,17 @@ outzone(const struct zone * zpfirst, int zonecount)
                if (zp->z_nrules == 0)
                {
                        stdoff = zp->z_stdoff;
-                       doabbr(startbuf, zp->z_format,
-                                  (char *) NULL, stdoff != 0, FALSE);
+                       doabbr(startbuf, zp, NULL, stdoff, false);
                        type = addtype(oadd(zp->z_gmtoff, stdoff),
                                                   startbuf, stdoff != 0, startttisstd,
                                                   startttisgmt);
                        if (usestart)
                        {
                                addtt(starttime, type);
-                               usestart = FALSE;
+                               usestart = false;
                        }
-                       else if (stdoff != 0)
-                               addtt(min_time, type);
+                       else
+                               addtt(big_bang_time, type);
                }
                else
                        for (year = min_year; year <= max_year; ++year)
@@ -2249,12 +2684,12 @@ outzone(const struct zone * zpfirst, int zonecount)
                                        int                     k;
                                        zic_t           jtime,
                                                                ktime = 0;
-                                       long            offset;
+                                       zic_t           offset;
 
                                        if (useuntil)
                                        {
                                                /*
-                                                * Turn untiltime into UTC assuming the current gmtoff
+                                                * Turn untiltime into UT assuming the current gmtoff
                                                 * and stdoff values.
                                                 */
                                                untiltime = zp->z_untiltime;
@@ -2291,43 +2726,55 @@ outzone(const struct zone * zpfirst, int zonecount)
                                                        k = j;
                                                        ktime = jtime;
                                                }
+                                               else if (jtime == ktime)
+                                               {
+                                                       char const *dup_rules_msg =
+                                                       _("two rules for same instant");
+
+                                                       eats(zp->z_filename, zp->z_linenum,
+                                                                rp->r_filename, rp->r_linenum);
+                                                       warning("%s", dup_rules_msg);
+                                                       rp = &zp->z_rules[k];
+                                                       eats(zp->z_filename, zp->z_linenum,
+                                                                rp->r_filename, rp->r_linenum);
+                                                       error("%s", dup_rules_msg);
+                                               }
                                        }
                                        if (k < 0)
                                                break;  /* go on to next year */
                                        rp = &zp->z_rules[k];
-                                       rp->r_todo = FALSE;
+                                       rp->r_todo = false;
                                        if (useuntil && ktime >= untiltime)
                                                break;
                                        stdoff = rp->r_stdoff;
                                        if (usestart && ktime == starttime)
-                                               usestart = FALSE;
+                                               usestart = false;
                                        if (usestart)
                                        {
                                                if (ktime < starttime)
                                                {
                                                        startoff = oadd(zp->z_gmtoff,
                                                                                        stdoff);
-                                                       doabbr(startbuf, zp->z_format,
+                                                       doabbr(startbuf, zp,
                                                                   rp->r_abbrvar,
-                                                                  rp->r_stdoff != 0,
-                                                                  FALSE);
+                                                                  rp->r_stdoff,
+                                                                  false);
                                                        continue;
                                                }
                                                if (*startbuf == '\0' &&
                                                        startoff == oadd(zp->z_gmtoff, stdoff))
                                                {
                                                        doabbr(startbuf,
-                                                                  zp->z_format,
+                                                                  zp,
                                                                   rp->r_abbrvar,
-                                                                  rp->r_stdoff !=
-                                                                  0,
-                                                                  FALSE);
+                                                                  rp->r_stdoff,
+                                                                  false);
                                                }
                                        }
                                        eats(zp->z_filename, zp->z_linenum,
                                                 rp->r_filename, rp->r_linenum);
-                                       doabbr(ab, zp->z_format, rp->r_abbrvar,
-                                                  rp->r_stdoff != 0, FALSE);
+                                       doabbr(ab, zp, rp->r_abbrvar,
+                                                  rp->r_stdoff, false);
                                        offset = oadd(zp->z_gmtoff, rp->r_stdoff);
                                        type = addtype(offset, ab, rp->r_stdoff != 0,
                                                                   rp->r_todisstd, rp->r_todisgmt);
@@ -2340,7 +2787,7 @@ outzone(const struct zone * zpfirst, int zonecount)
                                zp->z_format != NULL &&
                                strchr(zp->z_format, '%') == NULL &&
                                strchr(zp->z_format, '/') == NULL)
-                               (void) strcpy(startbuf, zp->z_format);
+                               strcpy(startbuf, zp->z_format);
                        eat(zp->z_filename, zp->z_linenum);
                        if (*startbuf == '\0')
                                error(_("cannot determine time zone abbreviation to use just after until time"));
@@ -2366,62 +2813,82 @@ outzone(const struct zone * zpfirst, int zonecount)
                                starttime = tadd(starttime, -gmtoff);
                }
        }
-       writezone(zpfirst->z_name, envvar);
-       ifree(startbuf);
-       ifree(ab);
-       ifree(envvar);
+       if (do_extend)
+       {
+               /*
+                * If we're extending the explicitly listed observations for 400 years
+                * because we can't fill the POSIX-TZ field, check whether we actually
+                * ended up explicitly listing observations through that period.  If
+                * there aren't any near the end of the 400-year period, add a
+                * redundant one at the end of the final year, to make it clear that
+                * we are claiming to have definite knowledge of the lack of
+                * transitions up to that point.
+                */
+               struct rule xr;
+               struct attype *lastat;
+
+               xr.r_month = TM_JANUARY;
+               xr.r_dycode = DC_DOM;
+               xr.r_dayofmonth = 1;
+               xr.r_tod = 0;
+               for (lastat = &attypes[0], i = 1; i < timecnt; i++)
+                       if (attypes[i].at > lastat->at)
+                               lastat = &attypes[i];
+               if (lastat->at < rpytime(&xr, max_year - 1))
+               {
+                       /*
+                        * Create new type code for the redundant entry, to prevent it
+                        * being optimized away.
+                        */
+                       if (typecnt >= TZ_MAX_TYPES)
+                       {
+                               error(_("too many local time types"));
+                               exit(EXIT_FAILURE);
+                       }
+                       gmtoffs[typecnt] = gmtoffs[lastat->type];
+                       isdsts[typecnt] = isdsts[lastat->type];
+                       ttisstds[typecnt] = ttisstds[lastat->type];
+                       ttisgmts[typecnt] = ttisgmts[lastat->type];
+                       abbrinds[typecnt] = abbrinds[lastat->type];
+                       ++typecnt;
+                       addtt(rpytime(&xr, max_year + 1), typecnt - 1);
+               }
+       }
+       writezone(zpfirst->z_name, envvar, version);
+       free(startbuf);
+       free(ab);
+       free(envvar);
 }
 
 static void
-addtt(const zic_t starttime, int type)
+addtt(zic_t starttime, int type)
 {
-       if (starttime <= min_time ||
-               (timecnt == 1 && attypes[0].at < min_time))
+       if (starttime <= big_bang_time ||
+               (timecnt == 1 && attypes[0].at < big_bang_time))
        {
                gmtoffs[0] = gmtoffs[type];
                isdsts[0] = isdsts[type];
                ttisstds[0] = ttisstds[type];
                ttisgmts[0] = ttisgmts[type];
                if (abbrinds[type] != 0)
-                       (void) strcpy(chars, &chars[abbrinds[type]]);
+                       strcpy(chars, &chars[abbrinds[type]]);
                abbrinds[0] = 0;
                charcnt = strlen(chars) + 1;
                typecnt = 1;
                timecnt = 0;
                type = 0;
        }
-       if (timecnt >= TZ_MAX_TIMES)
-       {
-               error(_("too many transitions?!"));
-               exit(EXIT_FAILURE);
-       }
+       attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
        attypes[timecnt].at = starttime;
        attypes[timecnt].type = type;
        ++timecnt;
 }
 
 static int
-addtype(long gmtoff, const char *abbr, int isdst,
-               int ttisstd, int ttisgmt)
+addtype(zic_t gmtoff, char const * abbr, bool isdst, bool ttisstd, bool ttisgmt)
 {
-       int                     i;
-       int                     j;
-
-       if (isdst != TRUE && isdst != FALSE)
-       {
-               error(_("internal error - addtype called with bad isdst"));
-               exit(EXIT_FAILURE);
-       }
-       if (ttisstd != TRUE && ttisstd != FALSE)
-       {
-               error(_("internal error - addtype called with bad ttisstd"));
-               exit(EXIT_FAILURE);
-       }
-       if (ttisgmt != TRUE && ttisgmt != FALSE)
-       {
-               error(_("internal error - addtype called with bad ttisgmt"));
-               exit(EXIT_FAILURE);
-       }
+       int                     i,
+                               j;
 
        /*
         * See if there's already an entry for this zone type. If so, just return
@@ -2446,7 +2913,7 @@ addtype(long gmtoff, const char *abbr, int isdst,
        }
        if (!(-1L - 2147483647L <= gmtoff && gmtoff <= 2147483647L))
        {
-               error(_("UTC offset out of range"));
+               error(_("UT offset out of range"));
                exit(EXIT_FAILURE);
        }
        gmtoffs[i] = gmtoff;
@@ -2465,10 +2932,10 @@ addtype(long gmtoff, const char *abbr, int isdst,
 }
 
 static void
-leapadd(const zic_t t, int positive, int rolling, int count)
+leapadd(zic_t t, bool positive, int rolling, int count)
 {
-       int                     i;
-       int                     j;
+       int                     i,
+                               j;
 
        if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS)
        {
@@ -2494,7 +2961,7 @@ leapadd(const zic_t t, int positive, int rolling, int count)
                        roll[j] = roll[j - 1];
                }
                trans[i] = t;
-               corr[i] = positive ? 1L : eitol(-count);
+               corr[i] = positive ? 1 : -count;
                roll[i] = rolling;
                ++leapcnt;
        } while (positive && --count != 0);
@@ -2504,7 +2971,7 @@ static void
 adjleap(void)
 {
        int                     i;
-       long            last = 0;
+       zic_t           last = 0;
 
        /*
         * propagate leap seconds forward
@@ -2516,61 +2983,201 @@ adjleap(void)
        }
 }
 
-static int
+static bool
 yearistype(int year, const char *type)
 {
        static char *buf;
        int                     result;
 
        if (type == NULL || *type == '\0')
-               return TRUE;
-       buf = erealloc(buf, (int) (132 + strlen(yitcommand) + strlen(type)));
-       (void) sprintf(buf, "%s %d %s", yitcommand, year, type);
+               return true;
+       buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type));
+       sprintf(buf, "%s %d %s", yitcommand, year, type);
        result = system(buf);
        if (WIFEXITED(result))
                switch (WEXITSTATUS(result))
                {
                        case 0:
-                               return TRUE;
+                               return true;
                        case 1:
-                               return FALSE;
+                               return false;
                }
        error(_("Wild result from command execution"));
-       (void) fprintf(stderr, _("%s: command was '%s', result was %d\n"),
-                                  progname, buf, result);
+       fprintf(stderr, _("%s: command was '%s', result was %d\n"),
+                       progname, buf, result);
        for (;;)
                exit(EXIT_FAILURE);
 }
 
-static int
-lowerit(int a)
+/* Is A a space character in the C locale?     */
+static bool
+is_space(char a)
 {
-       a = (unsigned char) a;
-       return (isascii(a) && isupper(a)) ? tolower(a) : a;
+       switch (a)
+       {
+               default:
+                       return false;
+               case ' ':
+               case '\f':
+               case '\n':
+               case '\r':
+               case '\t':
+               case '\v':
+                       return true;
+       }
 }
 
-static int
+/* Is A an alphabetic character in the C locale?  */
+static bool
+is_alpha(char a)
+{
+       switch (a)
+       {
+               default:
+                       return false;
+               case 'A':
+               case 'B':
+               case 'C':
+               case 'D':
+               case 'E':
+               case 'F':
+               case 'G':
+               case 'H':
+               case 'I':
+               case 'J':
+               case 'K':
+               case 'L':
+               case 'M':
+               case 'N':
+               case 'O':
+               case 'P':
+               case 'Q':
+               case 'R':
+               case 'S':
+               case 'T':
+               case 'U':
+               case 'V':
+               case 'W':
+               case 'X':
+               case 'Y':
+               case 'Z':
+               case 'a':
+               case 'b':
+               case 'c':
+               case 'd':
+               case 'e':
+               case 'f':
+               case 'g':
+               case 'h':
+               case 'i':
+               case 'j':
+               case 'k':
+               case 'l':
+               case 'm':
+               case 'n':
+               case 'o':
+               case 'p':
+               case 'q':
+               case 'r':
+               case 's':
+               case 't':
+               case 'u':
+               case 'v':
+               case 'w':
+               case 'x':
+               case 'y':
+               case 'z':
+                       return true;
+       }
+}
+
+/* If A is an uppercase character in the C locale, return its lowercase
+ * counterpart.  Otherwise, return A.  */
+static char
+lowerit(char a)
+{
+       switch (a)
+       {
+               default:
+                       return a;
+               case 'A':
+                       return 'a';
+               case 'B':
+                       return 'b';
+               case 'C':
+                       return 'c';
+               case 'D':
+                       return 'd';
+               case 'E':
+                       return 'e';
+               case 'F':
+                       return 'f';
+               case 'G':
+                       return 'g';
+               case 'H':
+                       return 'h';
+               case 'I':
+                       return 'i';
+               case 'J':
+                       return 'j';
+               case 'K':
+                       return 'k';
+               case 'L':
+                       return 'l';
+               case 'M':
+                       return 'm';
+               case 'N':
+                       return 'n';
+               case 'O':
+                       return 'o';
+               case 'P':
+                       return 'p';
+               case 'Q':
+                       return 'q';
+               case 'R':
+                       return 'r';
+               case 'S':
+                       return 's';
+               case 'T':
+                       return 't';
+               case 'U':
+                       return 'u';
+               case 'V':
+                       return 'v';
+               case 'W':
+                       return 'w';
+               case 'X':
+                       return 'x';
+               case 'Y':
+                       return 'y';
+               case 'Z':
+                       return 'z';
+       }
+}
+
+/* case-insensitive equality */
+static bool
 ciequal(const char *ap, const char *bp)
 {
        while (lowerit(*ap) == lowerit(*bp++))
                if (*ap++ == '\0')
-                       return TRUE;
-       return FALSE;
+                       return true;
+       return false;
 }
 
-static int
+static bool
 itsabbr(const char *abbr, const char *word)
 {
        if (lowerit(*abbr) != lowerit(*word))
-               return FALSE;
+               return false;
        ++word;
        while (*++abbr != '\0')
                do
                {
                        if (*word == '\0')
-                               return FALSE;
+                               return false;
                } while (lowerit(*word++) != lowerit(*abbr));
-       return TRUE;
+       return true;
 }
 
 static const struct lookup *
@@ -2613,13 +3220,11 @@ getfields(char *cp)
 
        if (cp == NULL)
                return NULL;
-       array = (char **) (void *)
-               emalloc((int) ((strlen(cp) + 1) * sizeof *array));
+       array = emalloc(size_product(strlen(cp) + 1, sizeof *array));
        nsubs = 0;
        for (;;)
        {
-               while (isascii((unsigned char) *cp) &&
-                          isspace((unsigned char) *cp))
+               while (is_space(*cp))
                        ++cp;
                if (*cp == '\0' || *cp == '#')
                        break;
@@ -2637,9 +3242,8 @@ getfields(char *cp)
                                                error(_("Odd number of quotation marks"));
                                                exit(1);
                                        }
-               } while (*cp != '\0' && *cp != '#' &&
-                                (!isascii(*cp) || !isspace((unsigned char) *cp)));
-               if (isascii(*cp) && isspace((unsigned char) *cp))
+               } while (*cp && *cp != '#' && !is_space(*cp));
+               if (is_space(*cp))
                        ++cp;
                *dp = '\0';
        }
@@ -2647,55 +3251,62 @@ getfields(char *cp)
        return array;
 }
 
-static long
-oadd(long t1, long t2)
+static void
+time_overflow(void)
 {
-       long            t;
-
-       t = t1 + t2;
-       if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1))
-       {
-               error(_("time overflow"));
-               exit(EXIT_FAILURE);
-       }
-       return t;
+       error(_("time overflow"));
+       exit(EXIT_FAILURE);
 }
 
 static zic_t
-tadd(const zic_t t1, long t2)
+oadd(zic_t t1, zic_t t2)
 {
-       zic_t           t;
+       if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2)
+               time_overflow();
+       return t1 + t2;
+}
 
-       if (t1 == max_time && t2 > 0)
-               return max_time;
-       if (t1 == min_time && t2 < 0)
-               return min_time;
-       t = t1 + t2;
-       if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1))
+static zic_t
+tadd(zic_t t1, zic_t t2)
+{
+       if (t1 < 0)
        {
-               error(_("time overflow"));
-               exit(EXIT_FAILURE);
+               if (t2 < min_time - t1)
+               {
+                       if (t1 != min_time)
+                               time_overflow();
+                       return min_time;
+               }
+       }
+       else
+       {
+               if (max_time - t1 < t2)
+               {
+                       if (t1 != max_time)
+                               time_overflow();
+                       return max_time;
+               }
        }
-       return t;
+       return t1 + t2;
 }
 
 /*
- * Given a rule, and a year, compute the date in seconds since January 1,
- * 1970, 00:00 LOCAL time - in that year that the rule refers to.
+ * Given a rule, and a year, compute the date (in seconds since January 1,
+ * 1970, 00:00 LOCAL time) in that year that the rule refers to.
  */
 
 static zic_t
-rpytime(const struct rule * rp, int wantedy)
+rpytime(const struct rule * rp, zic_t wantedy)
 {
-       int                     y,
-                               m,
+       int                     m,
                                i;
-       long            dayoff;                 /* with a nod to Margaret O. */
-       zic_t           t;
+       zic_t           dayoff;                 /* with a nod to Margaret O. */
+       zic_t           t,
+                               y;
 
-       if (wantedy == INT_MIN)
+       if (wantedy == ZIC_MIN)
                return min_time;
-       if (wantedy == INT_MAX)
+       if (wantedy == ZIC_MAX)
                return max_time;
        dayoff = 0;
        m = TM_JANUARY;
@@ -2712,12 +3323,12 @@ rpytime(const struct rule * rp, int wantedy)
                        --y;
                        i = -len_years[isleap(y)];
                }
-               dayoff = oadd(dayoff, eitol(i));
+               dayoff = oadd(dayoff, i);
        }
        while (m != rp->r_month)
        {
                i = len_months[isleap(y)][m];
-               dayoff = oadd(dayoff, eitol(i));
+               dayoff = oadd(dayoff, i);
                ++m;
        }
        i = rp->r_dayofmonth;
@@ -2732,13 +3343,13 @@ rpytime(const struct rule * rp, int wantedy)
                }
        }
        --i;
-       dayoff = oadd(dayoff, eitol(i));
+       dayoff = oadd(dayoff, i);
        if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ)
        {
-               long            wday;
+               zic_t           wday;
 
-#define LDAYSPERWEEK   ((long) DAYSPERWEEK)
-               wday = eitol(EPOCH_WDAY);
+#define LDAYSPERWEEK   ((zic_t) DAYSPERWEEK)
+               wday = EPOCH_WDAY;
 
                /*
                 * Don't trust mod of negative numbers.
@@ -2751,17 +3362,17 @@ rpytime(const struct rule * rp, int wantedy)
                        if (wday < 0)
                                wday += LDAYSPERWEEK;
                }
-               while (wday != eitol(rp->r_wday))
+               while (wday != rp->r_wday)
                        if (rp->r_dycode == DC_DOWGEQ)
                        {
-                               dayoff = oadd(dayoff, (long) 1);
+                               dayoff = oadd(dayoff, 1);
                                if (++wday >= LDAYSPERWEEK)
                                        wday = 0;
                                ++i;
                        }
                        else
                        {
-                               dayoff = oadd(dayoff, (long) -1);
+                               dayoff = oadd(dayoff, -1);
                                if (--wday < 0)
                                        wday = LDAYSPERWEEK - 1;
                                --i;
@@ -2769,7 +3380,7 @@ rpytime(const struct rule * rp, int wantedy)
                if (i < 0 || i >= len_months[isleap(y)][m])
                {
                        if (noise)
-                               warning(_("rule goes past start/end of month--\
+                               warning(_("rule goes past start/end of month\
 will not work with pre-2004 versions of zic"));
                }
        }
@@ -2790,28 +3401,21 @@ newabbr(const char *string)
        if (strcmp(string, GRANDPARENTED) != 0)
        {
                const char *cp;
-               char       *wp;
+               const char *mp;
 
                cp = string;
-               wp = NULL;
-               while (isalpha((unsigned char) *cp) || ('0' <= *cp && *cp <= '9')
+               mp = NULL;
+               while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9')
                           || *cp == '-' || *cp == '+')
                        ++cp;
                if (noise && cp - string < 3)
-                       wp = _("time zone abbreviation has fewer than 3 characters");
+                       mp = _("time zone abbreviation has fewer than 3 characters");
                if (cp - string > ZIC_MAX_ABBR_LEN_WO_WARN)
-                       wp = _("time zone abbreviation has too many characters");
+                       mp = _("time zone abbreviation has too many characters");
                if (*cp != '\0')
-                       wp = _("time zone abbreviation differs from POSIX standard");
-               if (wp != NULL)
-               {
-                       wp = ecpyalloc(wp);
-                       wp = ecatalloc(wp, " (");
-                       wp = ecatalloc(wp, string);
-                       wp = ecatalloc(wp, ")");
-                       warning(wp);
-                       ifree(wp);
-               }
+                       mp = _("time zone abbreviation differs from POSIX standard");
+               if (mp != NULL)
+                       warning("%s (%s)", mp, string);
        }
        i = strlen(string) + 1;
        if (charcnt + i > TZ_MAX_CHARS)
@@ -2819,18 +3423,18 @@ newabbr(const char *string)
                error(_("too many, or too long, time zone abbreviations"));
                exit(EXIT_FAILURE);
        }
-       (void) strcpy(&chars[charcnt], string);
-       charcnt += eitol(i);
+       strcpy(&chars[charcnt], string);
+       charcnt += i;
 }
 
-static int
+static bool
 mkdirs(char *argname)
 {
        char       *name;
        char       *cp;
 
        if (argname == NULL || *argname == '\0')
-               return 0;
+               return true;
        cp = name = ecpyalloc(argname);
        while ((cp = strchr(cp + 1, '/')) != NULL)
        {
@@ -2840,60 +3444,38 @@ mkdirs(char *argname)
                /*
                 * DOS drive specifier?
                 */
-               if (isalpha((unsigned char) name[0]) &&
-                       name[1] == ':' && name[2] == '\0')
+               if (is_alpha(name[0]) && name[1] == ':' && name[2] == '\0')
                {
                        *cp = '/';
                        continue;
                }
 #endif   /* WIN32 */
-               if (!itsdir(name))
+
+               /*
+                * Try to create it.  It's OK if creation fails because the directory
+                * already exists, perhaps because some other process just created it.
+                */
+               if (mkdir(name, MKDIR_UMASK) != 0)
                {
-                       /*
-                        * It doesn't seem to exist, so we try to create it. Creation may
-                        * fail because of the directory being created by some other
-                        * multiprocessor, so we get to do extra checking.
-                        */
-                       if (mkdir(name, MKDIR_UMASK) != 0)
+                       int                     err = errno;
+
+                       if (itsdir(name) <= 0)
                        {
-                               const char *e = strerror(errno);
+                               char const *e = strerror(err);
 
-                               if (errno != EEXIST || !itsdir(name))
-                               {
-                                       (void) fprintf(stderr,
-                                                                  _("%s: Cannot create directory %s: %s\n"),
-                                                                  progname, name, e);
-                                       ifree(name);
-                                       return -1;
-                               }
+                               warning(_("%s: Can't create directory"
+                                                 " %s: %s"),
+                                               progname, name, e);
+                               free(name);
+                               return false;
                        }
                }
                *cp = '/';
        }
-       ifree(name);
-       return 0;
+       free(name);
+       return true;
 }
 
-static long
-eitol(int i)
-{
-       long            l;
-
-       l = i;
-       if ((i < 0 && l >= 0) || (i == 0 && l != 0) || (i > 0 && l <= 0))
-       {
-               (void) fprintf(stderr,
-                                          _("%s: %d did not sign extend correctly\n"),
-                                          progname, i);
-               exit(EXIT_FAILURE);
-       }
-       return l;
-}
-
-/*
- * UNIX was a registered trademark of The Open Group in 2003.
- */
-
 
 #ifdef WIN32
 /*
@@ -2902,18 +3484,8 @@ eitol(int i)
 int
 link(const char *oldpath, const char *newpath)
 {
-       if (!CopyFile(oldpath, newpath, FALSE))
+       if (!CopyFile(oldpath, newpath, false))
                return -1;
        return 0;
 }
 #endif
-
-/*
- *     This allows zic to compile by just returning a dummy value.
- *     localtime.c references it, but no one uses it from zic.
- */
-int
-pg_open_tzfile(const char *name, char *canonname)
-{
-       return -1;
-}