]> granicus.if.org Git - postgresql/commitdiff
Sync our copy of the timezone library with IANA release tzcode2016g.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 19 Oct 2016 22:55:52 +0000 (18:55 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 19 Oct 2016 22:55:52 +0000 (18:55 -0400)
This is mostly to absorb some corner-case fixes in zic for year-2037
timestamps.  The other changes that have been made are unlikely to affect
our usage, but nonetheless we may as well take 'em.

src/timezone/localtime.c
src/timezone/private.h
src/timezone/strftime.c
src/timezone/zic.c

index d004e5ebe2b4e92b605d09e58d8b48a01d45d83c..e6c1beaf96a8a5042e5d2cadd5ef3935149971ce 100644 (file)
@@ -1280,9 +1280,8 @@ gmtsub(pg_time_t const * timep, int32 offset, struct pg_tm * tmp)
        result = timesub(timep, offset, gmtptr, tmp);
 
        /*
-        * 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.
+        * Could get fancy here and deliver something such as "+xx" or "-xx" if
+        * offset is non-zero, but this is no time for a treasure hunt.
         */
        if (offset != 0)
                tmp->tm_zone = wildabbr;
index 8480d389612243adc322dc3e80129d12afaa6a16..b8533d51e83a299ca9052a45068b8dda7623dbc8 100644 (file)
@@ -23,6 +23,7 @@
 
 #include "pgtime.h"
 
+/* This string was in the Factory zone through version 2016f.  */
 #define GRANDPARENTED  "Local time zone must be set--see zic manual page"
 
 /*
index 5630619321312c00fe59a008d0448f783c347e7c..4a0a01db651962c8e0b8c074f7a71cb540609107 100644 (file)
@@ -128,7 +128,7 @@ pg_strftime(char *s, size_t maxsize, const char *format,
        int                     warn;
 
        warn = IN_NONE;
-       p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
+       p = _fmt(format, t, s, s + maxsize, &warn);
        if (p == s + maxsize)
                return 0;
        *p = '\0';
index b546a1737299b803569102d4816d5b0bff844e31..52d8004ff73e437e5ec8146a878ff3ce822bfde2 100644 (file)
@@ -105,7 +105,7 @@ 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 void dolink(const char *fromfield, const char *tofield);
+static void dolink(const char *, const char *, bool);
 static char **getfields(char *buf);
 static zic_t gethms(const char *string, const char *errstring,
           bool);
@@ -119,7 +119,7 @@ static bool inzsub(char **, int, bool);
 static int     itsdir(const char *name);
 static bool is_alpha(char a);
 static char lowerit(char);
-static bool mkdirs(char *);
+static void mkdirs(char const *, bool);
 static void newabbr(const char *abbr);
 static zic_t oadd(zic_t t1, zic_t t2);
 static void outzone(const struct zone * zp, int ntzones);
@@ -136,6 +136,15 @@ enum
 {
 PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1};
 
+/* If true, work around a bug in Qt 5.6.1 and earlier, which mishandles
+   tz binary files whose POSIX-TZ-style strings contain '<'; see
+   QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>.  This
+   workaround will no longer be needed when Qt 5.6.1 and earlier are
+   obsolete, say in the year 2021.  */
+enum
+{
+WORK_AROUND_QTBUG_53071 = true};
+
 static int     charcnt;
 static bool errors;
 static bool warnings;
@@ -346,6 +355,7 @@ static const int len_years[2] = {
 static struct attype
 {
        zic_t           at;
+       bool            dontmerge;
        unsigned char type;
 }      *attypes;
 static zic_t gmtoffs[TZ_MAX_TYPES];
@@ -410,7 +420,8 @@ growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc)
                return ptr;
        else
        {
-               int                     amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX;
+               int                     nitems_max = INT_MAX - WORK_AROUND_QTBUG_53071;
+               int                     amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX;
 
                if ((amax - 1) / 3 * 2 < *nitems_alloc)
                        memory_exhausted(_("int overflow"));
@@ -478,17 +489,17 @@ warning(const char *string,...)
 }
 
 static void
-close_file(FILE *stream, char const * name)
+close_file(FILE *stream, char const * dir, char const * name)
 {
        char const *e = (ferror(stream) ? _("I/O error")
                                         : fclose(stream) != 0 ? strerror(errno) : NULL);
 
        if (e)
        {
-               fprintf(stderr, "%s: ", progname);
-               if (name)
-                       fprintf(stderr, "%s: ", name);
-               fprintf(stderr, "%s\n", e);
+               fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
+                               dir ? dir : "", dir ? "/" : "",
+                               name ? name : "", name ? ": " : "",
+                               e);
                exit(EXIT_FAILURE);
        }
 }
@@ -503,10 +514,34 @@ usage(FILE *stream, int status)
                          "Report bugs to %s.\n"),
                        progname, progname, PACKAGE_BUGREPORT);
        if (status == EXIT_SUCCESS)
-               close_file(stream, NULL);
+               close_file(stream, NULL, NULL);
        exit(status);
 }
 
+/* Change the working directory to DIR, possibly creating DIR and its
+   ancestors.  After this is done, all files are accessed with names
+   relative to DIR.  */
+static void
+change_directory(char const * dir)
+{
+       if (chdir(dir) != 0)
+       {
+               int                     chdir_errno = errno;
+
+               if (chdir_errno == ENOENT)
+               {
+                       mkdirs(dir, false);
+                       chdir_errno = chdir(dir) == 0 ? 0 : errno;
+               }
+               if (chdir_errno != 0)
+               {
+                       fprintf(stderr, _("%s: Can't chdir to %s: %s\n"),
+                                       progname, dir, strerror(chdir_errno));
+                       exit(EXIT_FAILURE);
+               }
+       }
+}
+
 static const char *psxrules;
 static const char *lcltime;
 static const char *directory;
@@ -534,7 +569,7 @@ main(int argc, char *argv[])
                if (strcmp(argv[i], "--version") == 0)
                {
                        printf("zic %s\n", PG_VERSION);
-                       close_file(stdout, NULL);
+                       close_file(stdout, NULL, NULL);
                        return EXIT_SUCCESS;
                }
                else if (strcmp(argv[i], "--help") == 0)
@@ -630,6 +665,7 @@ main(int argc, char *argv[])
        if (errors)
                return EXIT_FAILURE;
        associate();
+       change_directory(directory);
        for (i = 0; i < nzones; i = j)
        {
                /*
@@ -646,7 +682,7 @@ main(int argc, char *argv[])
        for (i = 0; i < nlinks; ++i)
        {
                eat(links[i].l_filename, links[i].l_linenum);
-               dolink(links[i].l_from, links[i].l_to);
+               dolink(links[i].l_from, links[i].l_to, false);
                if (noise)
                        for (j = 0; j < nlinks; ++j)
                                if (strcmp(links[i].l_to,
@@ -656,12 +692,12 @@ main(int argc, char *argv[])
        if (lcltime != NULL)
        {
                eat(_("command line"), 1);
-               dolink(lcltime, TZDEFAULT);
+               dolink(lcltime, TZDEFAULT, true);
        }
        if (psxrules != NULL)
        {
                eat(_("command line"), 1);
-               dolink(psxrules, TZDEFRULES);
+               dolink(psxrules, TZDEFRULES, true);
        }
        if (warnings && (ferror(stderr) || fclose(stderr) != 0))
                return EXIT_FAILURE;
@@ -751,131 +787,117 @@ namecheck(const char *name)
        return componentcheck(name, component, cp);
 }
 
-static char *
-relname(char const * dir, char const * base)
-{
-       if (*base == '/')
-               return ecpyalloc(base);
-       else
-       {
-               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)
+dolink(char const * fromfield, char const * tofield, bool staysymlink)
 {
-       char       *fromname;
-       char       *toname;
        int                     fromisdir;
-
-       fromname = relname(directory, fromfield);
-       toname = relname(directory, tofield);
+       bool            todirs_made = false;
+       int                     link_errno;
 
        /*
         * We get to be careful here since there's a fair chance of root running
         * us.
         */
-       fromisdir = itsdir(fromname);
+       fromisdir = itsdir(fromfield);
        if (fromisdir)
        {
                char const *e = strerror(fromisdir < 0 ? errno : EPERM);
 
-               fprintf(stderr, _("%s: link from %s failed: %s"),
-                               progname, fromname, e);
+               fprintf(stderr, _("%s: link from %s/%s failed: %s\n"),
+                               progname, directory, fromfield, e);
                exit(EXIT_FAILURE);
        }
-       if (link(fromname, toname) != 0)
+       if (staysymlink)
+               staysymlink = itsdir(tofield) == 2;
+       if (remove(tofield) == 0)
+               todirs_made = true;
+       else if (errno != ENOENT)
        {
-               int                     link_errno = errno;
-               bool            retry_if_link_supported = false;
+               char const *e = strerror(errno);
 
-               if (link_errno == ENOENT || link_errno == ENOTSUP)
+               fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
+                               progname, directory, tofield, e);
+               exit(EXIT_FAILURE);
+       }
+       link_errno = (staysymlink ? ENOTSUP
+                                 : link(fromfield, tofield) == 0 ? 0 : errno);
+       if (link_errno == ENOENT && !todirs_made)
+       {
+               mkdirs(tofield, true);
+               todirs_made = true;
+               link_errno = link(fromfield, tofield) == 0 ? 0 : errno;
+       }
+       if (link_errno != 0)
+       {
+#ifdef HAVE_SYMLINK
+               const char *s = fromfield;
+               const char *t;
+               char       *p;
+               size_t          dotdots = 0;
+               char       *symlinkcontents;
+               int                     symlink_errno;
+
+               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_errno = symlink(symlinkcontents, tofield) == 0 ? 0 : errno;
+               if (symlink_errno == ENOENT && !todirs_made)
                {
-                       if (!mkdirs(toname))
-                               exit(EXIT_FAILURE);
-                       retry_if_link_supported = true;
+                       mkdirs(tofield, true);
+                       symlink_errno = symlink(symlinkcontents, tofield) == 0 ? 0 : errno;
                }
-               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)
+               free(symlinkcontents);
+               if (symlink_errno == 0)
                {
-#ifdef HAVE_SYMLINK
-                       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
+                       if (link_errno != ENOTSUP)
+                               warning(_("symbolic link used because hard link failed: %s"),
+                                               strerror(link_errno));
+               }
+               else
 #endif   /* HAVE_SYMLINK */
-                       {
-                               FILE       *fp,
-                                                  *tp;
-                               int                     c;
+               {
+                       FILE       *fp,
+                                          *tp;
+                       int                     c;
 
-                               fp = fopen(fromname, "rb");
-                               if (!fp)
-                               {
-                                       const char *e = strerror(errno);
+                       fp = fopen(fromfield, "rb");
+                       if (!fp)
+                       {
+                               char const *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 read %s/%s: %s\n"),
+                                               progname, directory, fromfield, e);
+                               exit(EXIT_FAILURE);
+                       }
+                       tp = fopen(tofield, "wb");
+                       if (!tp)
+                       {
+                               char const *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));
+                               fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
+                                               progname, directory, tofield, e);
+                               exit(EXIT_FAILURE);
                        }
+                       while ((c = getc(fp)) != EOF)
+                               putc(c, tp);
+                       close_file(fp, directory, fromfield);
+                       close_file(tp, directory, tofield);
+                       if (link_errno != ENOTSUP)
+                               warning(_("copy used because hard link failed: %s"),
+                                               strerror(link_errno));
+                       else if (symlink_errno != ENOTSUP)
+                               warning(_("copy used because symbolic link failed: %s"),
+                                               strerror(symlink_errno));
                }
        }
-       free(fromname);
-       free(toname);
 }
 
 #define TIME_T_BITS_IN_FILE 64
@@ -888,10 +910,6 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
  * 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.
@@ -913,26 +931,45 @@ static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
 #define BIG_BANG (- (((zic_t) 1) << 59))
 #endif
 
-static const zic_t big_bang_time = BIG_BANG;
+/* If true, work around GNOME bug 730332
+   <https://bugzilla.gnome.org/show_bug.cgi?id=730332>
+   by refusing to output time stamps before BIG_BANG.
+   Such time stamps are physically suspect anyway.
 
-/* Return 1 if NAME is a directory, 0 if it's something else, -1 if trouble.  */
+   The GNOME bug is scheduled to be fixed in GNOME 3.22, and if so
+   this workaround will no longer be needed when GNOME 3.21 and
+   earlier are obsolete, say in the year 2021.  */
+enum
+{
+WORK_AROUND_GNOME_BUG_730332 = true};
+
+static const zic_t early_time = (WORK_AROUND_GNOME_BUG_730332
+                                                                ? BIG_BANG
+                                                                : MINVAL(zic_t, TIME_T_BITS_IN_FILE));
+
+/* Return 1 if NAME is a directory, 2 if a symbolic link, 0 if
+   something else, -1 (setting errno) if trouble.  */
 static int
 itsdir(char const * name)
 {
        struct stat st;
-       int                     res = stat(name, &st);
+       int                     res = lstat(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;
+#ifdef S_ISDIR
+               return S_ISDIR(st.st_mode) ? 1 : S_ISLNK(st.st_mode) ? 2 : 0;
+#else
+               size_t          n = strlen(name);
+               char       *nameslashdot = emalloc(n + 3);
+               bool            dir;
 
+               memcpy(nameslashdot, name, n);
+               strcpy(&nameslashdot[n], &"/."[!(n && name[n - 1] != '/')]);
+               dir = lstat(nameslashdot, &st) == 0;
                free(nameslashdot);
                return dir;
+#endif
        }
        return -1;
 }
@@ -1129,7 +1166,7 @@ infile(const char *name)
                }
                free(fields);
        }
-       close_file(fp, filename);
+       close_file(fp, NULL, filename);
        if (wantcont)
                error(_("expected continuation line not found"));
 }
@@ -1313,7 +1350,7 @@ inzsub(char **fields, int nfields, bool iscont)
        z.z_filename = filename;
        z.z_linenum = linenum;
        z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"), true);
-       if ((cp = strchr(fields[i_format], '%')) != 0)
+       if ((cp = strchr(fields[i_format], '%')) != NULL)
        {
                if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
                        || strchr(fields[i_format], '/'))
@@ -1491,7 +1528,7 @@ inleap(char **fields, int nfields)
                        return;
                }
                t = tadd(t, tod);
-               if (t < big_bang_time)
+               if (t < early_time)
                {
                        error(_("leap second precedes Big Bang"));
                        return;
@@ -1764,11 +1801,14 @@ writezone(const char *const name, const char *const string, char version)
        int                     timecnt32,
                                timei32;
        int                     pass;
-       char       *fullname;
        static const struct tzhead tzh0;
        static struct tzhead tzh;
-       zic_t      *ats = emalloc(size_product(timecnt, sizeof *ats + 1));
-       void       *typesptr = ats + timecnt;
+       bool            dir_checked = false;
+       zic_t           one = 1;
+       zic_t           y2038_boundary = one << 31;
+       int                     nats = timecnt + WORK_AROUND_QTBUG_53071;
+       zic_t      *ats = emalloc(size_product(nats, sizeof *ats + 1));
+       void       *typesptr = ats + nats;
        unsigned char *types = typesptr;
 
        /*
@@ -1786,7 +1826,7 @@ writezone(const char *const name, const char *const string, char version)
 
                toi = 0;
                fromi = 0;
-               while (fromi < timecnt && attypes[fromi].at < big_bang_time)
+               while (fromi < timecnt && attypes[fromi].at < early_time)
                        ++fromi;
                for (; fromi < timecnt; ++fromi)
                {
@@ -1799,8 +1839,9 @@ writezone(const char *const name, const char *const string, char version)
                                        attypes[fromi].type;
                                continue;
                        }
-                       if (toi == 0 ||
-                               attypes[toi - 1].type != attypes[fromi].type)
+                       if (toi == 0
+                               || attypes[fromi].dontmerge
+                               || attypes[toi - 1].type != attypes[fromi].type)
                                attypes[toi++] = attypes[fromi];
                }
                timecnt = toi;
@@ -1818,6 +1859,20 @@ writezone(const char *const name, const char *const string, char version)
                types[i] = attypes[i].type;
        }
 
+       /*
+        * Work around QTBUG-53071 for time stamps less than y2038_boundary - 1,
+        * by inserting a no-op transition at time y2038_boundary - 1. This works
+        * only for timestamps before the boundary, which should be good enough in
+        * practice as QTBUG-53071 should be long-dead by 2038.
+        */
+       if (WORK_AROUND_QTBUG_53071 && timecnt != 0
+               && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<'))
+       {
+               ats[timecnt] = y2038_boundary - 1;
+               types[timecnt] = types[timecnt - 1];
+               timecnt++;
+       }
+
        /*
         * Correct for leap seconds.
         */
@@ -1862,29 +1917,35 @@ writezone(const char *const name, const char *const string, char version)
                --leapcnt32;
                ++leapi32;
        }
-       fullname = relname(directory, name);
 
        /*
         * Remove old file, if any, to snap links.
         */
-       if (itsdir(fullname) == 0 && remove(fullname) != 0 && errno != ENOENT)
+       if (remove(name) == 0)
+               dir_checked = true;
+       else if (errno != ENOENT)
        {
                const char *e = strerror(errno);
 
-               fprintf(stderr, _("%s: Cannot remove %s: %s\n"),
-                               progname, fullname, e);
+               fprintf(stderr, _("%s: Cannot remove %s/%s: %s\n"),
+                               progname, directory, name, e);
                exit(EXIT_FAILURE);
        }
-       if ((fp = fopen(fullname, "wb")) == NULL)
+       fp = fopen(name, "wb");
+       if (!fp)
        {
-               if (!mkdirs(fullname))
-                       exit(EXIT_FAILURE);
-               if ((fp = fopen(fullname, "wb")) == NULL)
-               {
-                       const char *e = strerror(errno);
+               int                     fopen_errno = errno;
 
-                       fprintf(stderr, _("%s: Cannot create %s: %s\n"),
-                                       progname, fullname, e);
+               if (fopen_errno == ENOENT && !dir_checked)
+               {
+                       mkdirs(name, true);
+                       fp = fopen(name, "wb");
+                       fopen_errno = errno;
+               }
+               if (!fp)
+               {
+                       fprintf(stderr, _("%s: Cannot create %s/%s: %s\n"),
+                                       progname, directory, name, strerror(fopen_errno));
                        exit(EXIT_FAILURE);
                }
        }
@@ -2130,9 +2191,8 @@ writezone(const char *const name, const char *const string, char version)
                                putc(ttisgmts[i], fp);
        }
        fprintf(fp, "\n%s\n", string);
-       close_file(fp, fullname);
+       close_file(fp, directory, name);
        free(ats);
-       free(fullname);
 }
 
 static char const *
@@ -2527,6 +2587,7 @@ outzone(const struct zone * zpfirst, int zonecount)
        int                     compat;
        bool            do_extend;
        char            version;
+       int                     lastatmax = -1;
 
        max_abbr_len = 2 + max_format_len + max_abbrvar_len;
        max_envvar_len = 2 * max_abbr_len + 5 * 9;
@@ -2649,9 +2710,9 @@ outzone(const struct zone * zpfirst, int zonecount)
                 */
                stdoff = 0;
                zp = &zpfirst[i];
-               usestart = i > 0 && (zp - 1)->z_untiltime > big_bang_time;
+               usestart = i > 0 && (zp - 1)->z_untiltime > early_time;
                useuntil = i < (zonecount - 1);
-               if (useuntil && zp->z_untiltime <= big_bang_time)
+               if (useuntil && zp->z_untiltime <= early_time)
                        continue;
                gmtoff = zp->z_gmtoff;
                eat(zp->z_filename, zp->z_linenum);
@@ -2670,7 +2731,7 @@ outzone(const struct zone * zpfirst, int zonecount)
                                usestart = false;
                        }
                        else
-                               addtt(big_bang_time, type);
+                               addtt(early_time, type);
                }
                else
                        for (year = min_year; year <= max_year; ++year)
@@ -2792,6 +2853,10 @@ outzone(const struct zone * zpfirst, int zonecount)
                                        offset = oadd(zp->z_gmtoff, rp->r_stdoff);
                                        type = addtype(offset, ab, rp->r_stdoff != 0,
                                                                   rp->r_todisstd, rp->r_todisgmt);
+                                       if (rp->r_hiyear == ZIC_MAX
+                                               && !(0 <= lastatmax
+                                                        && ktime < attypes[lastatmax].at))
+                                               lastatmax = timecnt;
                                        addtt(ktime, type);
                                }
                        }
@@ -2827,6 +2892,8 @@ outzone(const struct zone * zpfirst, int zonecount)
                                starttime = tadd(starttime, -gmtoff);
                }
        }
+       if (0 <= lastatmax)
+               attypes[lastatmax].dontmerge = true;
        if (do_extend)
        {
                /*
@@ -2850,22 +2917,8 @@ outzone(const struct zone * zpfirst, int zonecount)
                                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);
+                       attypes[timecnt - 1].dontmerge = true;
                }
        }
        writezone(zpfirst->z_name, envvar, version);
@@ -2877,8 +2930,8 @@ outzone(const struct zone * zpfirst, int zonecount)
 static void
 addtt(zic_t starttime, int type)
 {
-       if (starttime <= big_bang_time ||
-               (timecnt == 1 && attypes[0].at < big_bang_time))
+       if (starttime <= early_time
+               || (timecnt == 1 && attypes[0].at < early_time))
        {
                gmtoffs[0] = gmtoffs[type];
                isdsts[0] = isdsts[type];
@@ -2894,6 +2947,7 @@ addtt(zic_t starttime, int type)
        }
        attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
        attypes[timecnt].at = starttime;
+       attypes[timecnt].dontmerge = false;
        attypes[timecnt].type = type;
        ++timecnt;
 }
@@ -3441,53 +3495,51 @@ newabbr(const char *string)
        charcnt += i;
 }
 
-static bool
-mkdirs(char *argname)
+/* Ensure that the directories of ARGNAME exist, by making any missing
+   ones.  If ANCESTORS, do this only for ARGNAME's ancestors; otherwise,
+   do it for ARGNAME too.  Exit with failure if there is trouble.  */
+static void
+mkdirs(char const * argname, bool ancestors)
 {
        char       *name;
        char       *cp;
 
-       if (argname == NULL || *argname == '\0')
-               return true;
        cp = name = ecpyalloc(argname);
-       while ((cp = strchr(cp + 1, '/')) != NULL)
-       {
-               *cp = '\0';
+
+       /* Do not mkdir a root directory, as it must exist.  */
 #ifdef WIN32
+       if (is_alpha(name[0]) && name[1] == ':')
+               cp += 2;
+#endif
+       while (*cp == '/')
+               cp++;
 
-               /*
-                * DOS drive specifier?
-                */
-               if (is_alpha(name[0]) && name[1] == ':' && name[2] == '\0')
-               {
-                       *cp = '/';
-                       continue;
-               }
-#endif   /* WIN32 */
+       while (cp && ((cp = strchr(cp, '/')) || !ancestors))
+       {
+               if (cp)
+                       *cp = '\0';
 
                /*
                 * Try to create it.  It's OK if creation fails because the directory
                 * already exists, perhaps because some other process just created it.
+                * For simplicity do not check first whether it already exists, as
+                * that is checked anyway if the mkdir fails.
                 */
                if (mkdir(name, MKDIR_UMASK) != 0)
                {
                        int                     err = errno;
 
-                       if (itsdir(name) <= 0)
+                       if (err != EEXIST && itsdir(name) < 0)
                        {
-                               char const *e = strerror(err);
-
-                               warning(_("%s: Can't create directory"
-                                                 " %s: %s"),
-                                               progname, name, e);
-                               free(name);
-                               return false;
+                               error(_("%s: Cannot create directory %s: %s"),
+                                         progname, name, strerror(err));
+                               exit(EXIT_FAILURE);
                        }
                }
-               *cp = '/';
+               if (cp)
+                       *cp++ = '/';
        }
        free(name);
-       return true;
 }