]> granicus.if.org Git - postgresql/commitdiff
Sync our copy of the timezone library with IANA tzcode master.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 22 Sep 2017 04:04:21 +0000 (00:04 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 22 Sep 2017 04:04:29 +0000 (00:04 -0400)
This patch absorbs a few unreleased fixes in the IANA code.
It corresponds to commit 2d8b944c1cec0808ac4f7a9ee1a463c28f9cd00a
in https://github.com/eggert/tz.  Non-cosmetic changes include:

TZDEFRULESTRING is updated to match current US DST practice,
rather than what it was over ten years ago.  This only matters
for interpretation of POSIX-style zone names (e.g., "EST5EDT"),
and only if the timezone database doesn't include either an exact
match for the zone name or a "posixrules" entry.  The latter
should not be true in any current Postgres installation, but
this could possibly matter when using --with-system-tzdata.

Get rid of a nonportable use of "++var" on a bool var.
This is part of a larger fix that eliminates some vestigial
support for consecutive leap seconds, and adds checks to
the "zic" compiler that the data files do not specify that.

Remove a couple of ancient compatibility hacks.  The IANA
crew think these are obsolete, and I tend to agree.  But
perhaps our buildfarm will think different.

Back-patch to all supported branches, in line with our policy
that all branches should be using current IANA code.  Before v10,
this includes application of current pgindent rules, to avoid
whitespace problems in future back-patches.

Discussion: https://postgr.es/m/E1dsWhf-0000pT-F9@gemulon.postgresql.org

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

index 08642d12363abe6a96c1dbd2c061b16694708c0c..d946e882aa61625515fd2ace8c24b1d9199d7862 100644 (file)
 #ifndef WILDABBR
 /*
  * Someone might make incorrect use of a time zone abbreviation:
- *     1.  They might reference tzname[0] before calling tzset (explicitly
+ *     1.      They might reference tzname[0] before calling tzset (explicitly
  *             or implicitly).
- *     2.  They might reference tzname[1] before calling tzset (explicitly
+ *     2.      They might reference tzname[1] before calling tzset (explicitly
  *             or implicitly).
- *     3.  They might reference tzname[1] after setting to a time zone
+ *     3.      They might reference tzname[1] after setting to a time zone
  *             in which Daylight Saving Time is never observed.
- *     4.  They might reference tzname[0] after setting to a time zone
+ *     4.      They might reference tzname[0] after setting to a time zone
  *             in which Standard Time is never observed.
- *     5.  They might reference tm.TM_ZONE after calling offtime.
+ *     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
@@ -50,12 +50,8 @@ 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));
-
 /*
- * We cache the result of trying to load the TZDEFRULES zone here.
+ * PG: We cache the result of trying to load the TZDEFRULES zone here.
  * tzdefrules_loaded is 0 if not tried yet, +1 if good, -1 if failed.
  */
 static struct state tzdefrules_s;
@@ -63,12 +59,12 @@ static int  tzdefrules_loaded = 0;
 
 /*
  * 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.
+ * Default to US rules as of 2017-05-07.
  * POSIX 1003.1 section 8.1.1 says that the default DST rules are
  * implementation dependent; for historical reasons, US rules are a
  * common default.
  */
-#define TZDEFRULESTRING ",M4.1.0,M10.5.0"
+#define TZDEFRULESTRING ",M3.2.0,M11.1.0"
 
 /* structs ttinfo, lsinfo, state have been moved to pgtz.h */
 
@@ -195,10 +191,8 @@ union input_buffer
 /* 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
+       struct file_analysis
        {
                /* The input buffer.  */
                union input_buffer u;
@@ -206,6 +200,8 @@ union local_storage
                /* A temporary state used for parsing a TZ string in the file.  */
                struct state st;
        }                       u;
+
+       /* We don't need the "fullname" member */
 };
 
 /* Load tz data from the file named NAME into *SP.  Read extended
@@ -255,6 +251,8 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
        {
                int32           ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
                int32           ttisgmtcnt = detzcode(up->tzhead.tzh_ttisgmtcnt);
+               int64           prevtr = 0;
+               int32           prevcorr = 0;
                int32           leapcnt = detzcode(up->tzhead.tzh_leapcnt);
                int32           timecnt = detzcode(up->tzhead.tzh_timecnt);
                int32           typecnt = detzcode(up->tzhead.tzh_typecnt);
@@ -285,8 +283,8 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
 
                /*
                 * Read transitions, discarding those out of pg_time_t range. But
-                * pretend the last transition before time_t_min occurred at
-                * time_t_min.
+                * pretend the last transition before TIME_T_MIN occurred at
+                * TIME_T_MIN.
                 */
                timecnt = 0;
                for (i = 0; i < sp->timecnt; ++i)
@@ -294,12 +292,12 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
                        int64           at
                        = stored == 4 ? detzcode(p) : detzcode64(p);
 
-                       sp->types[i] = at <= time_t_max;
+                       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);
+                               = ((TYPE_SIGNED(pg_time_t) ? at < TIME_T_MIN : at < 0)
+                                  ? TIME_T_MIN : at);
 
                                if (timecnt && attime <= sp->ats[timecnt - 1])
                                {
@@ -354,20 +352,22 @@ tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
                        int32           corr = detzcode(p + stored);
 
                        p += stored + 4;
-                       if (tr <= time_t_max)
+                       /* Leap seconds cannot occur before the Epoch.  */
+                       if (tr < 0)
+                               return EINVAL;
+                       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;
+                               /*
+                                * Leap seconds cannot occur more than once per UTC month, and
+                                * UTC months are at least 28 days long (minus 1 second for a
+                                * negative leap second).  Each leap second's correction must
+                                * differ from the previous one's by 1 second.
+                                */
+                               if (tr - prevtr < 28 * SECSPERDAY - 1
+                                       || (corr != prevcorr - 1 && corr != prevcorr + 1))
+                                       return EINVAL;
+                               sp->lsis[leapcnt].ls_trans = prevtr = tr;
+                               sp->lsis[leapcnt].ls_corr = prevcorr = corr;
                                leapcnt++;
                        }
                }
@@ -1361,11 +1361,18 @@ pg_gmtime(const pg_time_t *timep)
  * Return the number of leap years through the end of the given year
  * where, to make the math easy, the answer for year zero is defined as zero.
  */
+static int
+leaps_thru_end_of_nonneg(int y)
+{
+       return y / 4 - y / 100 + y / 400;
+}
+
 static int
 leaps_thru_end_of(const int y)
 {
-       return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
-               -(leaps_thru_end_of(-(y + 1)) + 1);
+       return (y < 0
+                       ? -1 - leaps_thru_end_of_nonneg(-1 - y)
+                       : leaps_thru_end_of_nonneg(y));
 }
 
 static struct pg_tm *
@@ -1390,22 +1397,9 @@ timesub(const pg_time_t *timep, int32 offset,
                lp = &sp->lsis[i];
                if (*timep >= lp->ls_trans)
                {
-                       if (*timep == lp->ls_trans)
-                       {
-                               hit = ((i == 0 && lp->ls_corr > 0) ||
-                                          lp->ls_corr > sp->lsis[i - 1].ls_corr);
-                               if (hit)
-                                       while (i > 0 &&
-                                                  sp->lsis[i].ls_trans ==
-                                                  sp->lsis[i - 1].ls_trans + 1 &&
-                                                  sp->lsis[i].ls_corr ==
-                                                  sp->lsis[i - 1].ls_corr + 1)
-                                       {
-                                               ++hit;
-                                               --i;
-                                       }
-                       }
                        corr = lp->ls_corr;
+                       hit = (*timep == lp->ls_trans
+                                  && (i == 0 ? 0 : lp[-1].ls_corr) < corr);
                        break;
                }
        }
@@ -1529,13 +1523,13 @@ increment_overflow_time(pg_time_t *tp, int32 j)
 {
        /*----------
         * This is like
-        * 'if (! (time_t_min <= *tp + j && *tp + j <= time_t_max)) ...',
+        * '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))
+                 ? (TYPE_SIGNED(pg_time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp)
+                 : *tp <= TIME_T_MAX - j))
                return true;
        *tp += j;
        return false;
index e65cd1bb4ecd29a91afddd9239319da3e40917d2..701112ec5b80b4ddfbd4f736bf42affcfeca4bb8 100644 (file)
 #define EOVERFLOW EINVAL
 #endif
 
-#ifndef WIFEXITED
-#define WIFEXITED(status)      (((status) & 0xff) == 0)
-#endif                                                 /* !defined WIFEXITED */
-#ifndef WEXITSTATUS
-#define WEXITSTATUS(status) (((status) >> 8) & 0xff)
-#endif                                                 /* !defined WEXITSTATUS */
-
 /* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
 #define is_digit(c) ((unsigned)(c) - '0' <= 9)
 
-/*
- * SunOS 4.1.1 libraries lack remove.
- */
-
-#ifndef remove
-extern int     unlink(const char *filename);
-
-#define remove unlink
-#endif                                                 /* !defined remove */
-
 
 /*
  * Finally, some convenience items.
@@ -78,6 +61,10 @@ extern int   unlink(const char *filename);
 #define MINVAL(t, b)                                           \
   ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0))
 
+/* The extreme time values, assuming no padding.  */
+#define TIME_T_MIN MINVAL(pg_time_t, TYPE_BIT(pg_time_t))
+#define TIME_T_MAX MAXVAL(pg_time_t, TYPE_BIT(pg_time_t))
+
 /*
  * 302 / 1000 is log10(2.0) rounded up.
  * Subtract one for the sign bit if the type is signed;
@@ -91,7 +78,7 @@ extern int    unlink(const char *filename);
 /*
  * INITIALIZE(x)
  */
-#define INITIALIZE(x)  ((x) = 0)
+#define INITIALIZE(x)  ((x) = 0)
 
 #undef _
 #define _(msgid) (msgid)
@@ -146,7 +133,7 @@ extern int  unlink(const char *filename);
  * or
  *     isleap(a + b) == isleap(a % 400 + b % 400)
  * This is true even if % means modulo rather than Fortran remainder
- * (which is allowed by C89 but not C99).
+ * (which is allowed by C89 but not by C99 or later).
  * We use this to avoid addition overflow problems.
  */
 
index 7cbafc9d834465ffe02f7506f3c29a813435b6fd..e1c64834430df57e25f4f7026889a2f4e9aa9d9b 100644 (file)
@@ -82,17 +82,17 @@ static const struct lc_time_T C_time_locale = {
        /*
         * x_fmt
         *
-        * C99 requires this format. Using just numbers (as here) makes Quakers
-        * happier; it's also compatible with SVR4.
+        * C99 and later require this format. Using just numbers (as here) makes
+        * Quakers happier; it's also compatible with SVR4.
         */
        "%m/%d/%y",
 
        /*
         * c_fmt
         *
-        * C99 requires this format. Previously this code used "%D %X", but we now
-        * conform to C99. Note that "%a %b %d %H:%M:%S %Y" is used by Solaris
-        * 2.3.
+        * C99 and later require this format. Previously this code used "%D %X",
+        * but we now conform to C99. Note that "%a %b %d %H:%M:%S %Y" is used by
+        * Solaris 2.3.
         */
        "%a %b %e %T %Y",
 
@@ -106,26 +106,25 @@ static const struct lc_time_T C_time_locale = {
        "%a %b %e %H:%M:%S %Z %Y"
 };
 
+enum warn
+{
+       IN_NONE, IN_SOME, IN_THIS, IN_ALL
+};
+
 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 *_fmt(const char *, const struct pg_tm *, char *, const char *,
+        enum warn *);
 static char *_yconv(int, int, bool, bool, char *, const char *);
 
-#define IN_NONE 0
-#define IN_SOME 1
-#define IN_THIS 2
-#define IN_ALL 3
-
 
 size_t
 pg_strftime(char *s, size_t maxsize, const char *format,
                        const struct pg_tm *t)
 {
        char       *p;
-       int                     warn;
+       enum warn       warn = IN_NONE;
 
-       warn = IN_NONE;
        p = _fmt(format, t, s, s + maxsize, &warn);
        if (p == s + maxsize)
                return 0;
@@ -134,8 +133,8 @@ pg_strftime(char *s, size_t maxsize, const char *format,
 }
 
 static char *
-_fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim,
-        int *warnp)
+_fmt(const char *format, const struct pg_tm *t, char *pt,
+        const char *ptlim, enum warn *warnp)
 {
        for (; *format; ++format)
        {
@@ -184,7 +183,7 @@ _fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim,
                                        continue;
                                case 'c':
                                        {
-                                               int                     warn2 = IN_SOME;
+                                               enum warn       warn2 = IN_SOME;
 
                                                pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
                                                if (warn2 == IN_ALL)
@@ -203,9 +202,9 @@ _fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim,
                                case 'O':
 
                                        /*
-                                        * C99 locale modifiers. The sequences  %Ec %EC %Ex %EX
-                                        * %Ey %EY      %Od %oe %OH %OI %Om %OM  %OS %Ou %OU %OV %Ow
-                                        * %OW %Oy are supposed to provide alternate
+                                        * Locale modifiers of C99 and later. The sequences %Ec
+                                        * %EC %Ex %EX %Ey %EY %Od %oe %OH %OI %Om %OM %OS %Ou %OU
+                                        * %OV %Ow %OW %Oy are supposed to provide alternate
                                         * representations.
                                         */
                                        goto label;
@@ -417,7 +416,7 @@ _fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim,
                                        continue;
                                case 'x':
                                        {
-                                               int                     warn2 = IN_SOME;
+                                               enum warn       warn2 = IN_SOME;
 
                                                pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
                                                if (warn2 == IN_ALL)
@@ -442,8 +441,8 @@ _fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim,
                                                pt = _add(t->tm_zone, pt, ptlim);
 
                                        /*
-                                        * C99 says that %Z must be replaced by the empty string
-                                        * if the time zone is not determinable.
+                                        * C99 and later say that %Z must be replaced by the empty
+                                        * string if the time zone is not determinable.
                                         */
                                        continue;
                                case 'z':
index 27c841be9e788b32edd46b8a6d9d5b5f61e61b6e..db119265c355783d2f7c9127cbe3cbb12319267b 100644 (file)
@@ -11,7 +11,6 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <time.h>
-#include <unistd.h>
 
 #include "pg_getopt.h"
 
@@ -46,9 +45,12 @@ typedef int64 zic_t;
 static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
 #endif
 
-/* The type and printf format for line numbers.  */
+/*
+ * The type for line numbers.  In Postgres, use %d to format them; upstream
+ * uses PRIdMAX but we prefer not to rely on that, not least because it
+ * results in platform-dependent strings to be translated.
+ */
 typedef int lineno_t;
-#define PRIdLINENO "d"
 
 struct rule
 {
@@ -293,10 +295,13 @@ struct lookup
 static struct lookup const *byword(const char *string,
           const struct lookup *lp);
 
-static struct lookup const line_codes[] = {
+static struct lookup const zi_line_codes[] = {
        {"Rule", LC_RULE},
        {"Zone", LC_ZONE},
        {"Link", LC_LINK},
+       {NULL, 0}
+};
+static struct lookup const leap_line_codes[] = {
        {"Leap", LC_LEAP},
        {NULL, 0}
 };
@@ -435,7 +440,8 @@ growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc)
                return ptr;
        else
        {
-               ptrdiff_t       amax = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071;
+               ptrdiff_t       nitems_max = PTRDIFF_MAX - WORK_AROUND_QTBUG_53071;
+               ptrdiff_t       amax = nitems_max < SIZE_MAX ? nitems_max : SIZE_MAX;
 
                if ((amax - 1) / 3 * 2 < *nitems_alloc)
                        memory_exhausted(_("integer overflow"));
@@ -471,10 +477,10 @@ verror(const char *string, va_list args)
         * "*" -v on BSD systems.
         */
        if (filename)
-               fprintf(stderr, _("\"%s\", line %" PRIdLINENO ": "), filename, linenum);
+               fprintf(stderr, _("\"%s\", line %d: "), filename, linenum);
        vfprintf(stderr, string, args);
        if (rfilename != NULL)
-               fprintf(stderr, _(" (rule from \"%s\", line %" PRIdLINENO ")"),
+               fprintf(stderr, _(" (rule from \"%s\", line %d)"),
                                rfilename, rlinenum);
        fprintf(stderr, "\n");
 }
@@ -563,7 +569,7 @@ static const char *leapsec;
 static const char *yitcommand;
 
 int
-main(int argc, char *argv[])
+main(int argc, char **argv)
 {
        int                     c,
                                k;
@@ -572,7 +578,7 @@ main(int argc, char *argv[])
 
 #ifndef WIN32
        umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
-#endif                                                 /* !WIN32 */
+#endif
        progname = argv[0];
        if (TYPE_BIT(zic_t) <64)
        {
@@ -631,7 +637,10 @@ main(int argc, char *argv[])
                                break;
                        case 'y':
                                if (yitcommand == NULL)
+                               {
+                                       warning(_("-y is obsolescent"));
                                        yitcommand = strdup(optarg);
+                               }
                                else
                                {
                                        fprintf(stderr,
@@ -1202,6 +1211,9 @@ infile(const char *name)
                        wantcont = inzcont(fields, nfields);
                else
                {
+                       struct lookup const *line_codes
+                       = name == leapsec ? leap_line_codes : zi_line_codes;
+
                        lp = byword(fields[0], line_codes);
                        if (lp == NULL)
                                error(_("input line of unknown type"));
@@ -1220,12 +1232,7 @@ infile(const char *name)
                                                wantcont = false;
                                                break;
                                        case LC_LEAP:
-                                               if (name != leapsec)
-                                                       warning(_("%s: Leap line in non leap"
-                                                                         " seconds file %s"),
-                                                                       progname, name);
-                                               else
-                                                       inleap(fields, nfields);
+                                               inleap(fields, nfields);
                                                wantcont = false;
                                                break;
                                        default:        /* "cannot happen" */
@@ -1359,7 +1366,7 @@ inzone(char **fields, int nfields)
                        strcmp(zones[i].z_name, fields[ZF_NAME]) == 0)
                {
                        error(_("duplicate zone name %s"
-                                       " (file \"%s\", line %" PRIdLINENO ")"),
+                                       " (file \"%s\", line %d)"),
                                  fields[ZF_NAME],
                                  zones[i].z_filename,
                                  zones[i].z_linenum);
@@ -1573,21 +1580,11 @@ inleap(char **fields, int nfields)
                        positive = false;
                        count = 1;
                }
-               else if (strcmp(cp, "--") == 0)
-               {
-                       positive = false;
-                       count = 2;
-               }
                else if (strcmp(cp, "+") == 0)
                {
                        positive = true;
                        count = 1;
                }
-               else if (strcmp(cp, "++") == 0)
-               {
-                       positive = true;
-                       count = 2;
-               }
                else
                {
                        error(_("illegal CORRECTION field on Leap line"));
@@ -1599,9 +1596,9 @@ inleap(char **fields, int nfields)
                        return;
                }
                t = tadd(t, tod);
-               if (t < early_time)
+               if (t < 0)
                {
-                       error(_("leap second precedes Big Bang"));
+                       error(_("leap second precedes Epoch"));
                        return;
                }
                leapadd(t, positive, lp->l_value, count);
@@ -1753,11 +1750,14 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
                        error(_("typed single year"));
                        return;
                }
+               warning(_("year type \"%s\" is obsolete; use \"-\" instead"),
+                               typep);
                rp->r_yrtype = ecpyalloc(typep);
        }
 
        /*
-        * Day work. Accept things such as:  1  last-Sunday  Sun<=20  Sun>=7
+        * Day work. Accept things such as: 1 lastSunday last-Sunday
+        * (undocumented; warn about this) Sun<=20 Sun>=7
         */
        dp = ecpyalloc(dayp);
        if ((lp = byword(dp, lasts)) != NULL)
@@ -2850,9 +2850,10 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
                                {
                                        ptrdiff_t       k;
                                        zic_t           jtime,
-                                                               ktime = 0;
+                                                               ktime;
                                        zic_t           offset;
 
+                                       INITIALIZE(ktime);
                                        if (useuntil)
                                        {
                                                /*
@@ -2929,7 +2930,8 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
                                                        continue;
                                                }
                                                if (*startbuf == '\0' &&
-                                                       startoff == oadd(zp->z_gmtoff, stdoff))
+                                                       startoff == oadd(zp->z_gmtoff,
+                                                                                        stdoff))
                                                {
                                                        doabbr(startbuf,
                                                                   zp,
@@ -3104,14 +3106,7 @@ leapadd(zic_t t, bool positive, int rolling, int count)
        }
        for (i = 0; i < leapcnt; ++i)
                if (t <= trans[i])
-               {
-                       if (t == trans[i])
-                       {
-                               error(_("repeated leap second moment"));
-                               exit(EXIT_FAILURE);
-                       }
                        break;
-               }
        do
        {
                for (j = leapcnt; j > i; --j)
@@ -3132,12 +3127,19 @@ adjleap(void)
 {
        int                     i;
        zic_t           last = 0;
+       zic_t           prevtrans = 0;
 
        /*
         * propagate leap seconds forward
         */
        for (i = 0; i < leapcnt; ++i)
        {
+               if (trans[i] - prevtrans < 28 * SECSPERDAY)
+               {
+                       error(_("Leap seconds too close together"));
+                       exit(EXIT_FAILURE);
+               }
+               prevtrans = trans[i];
                trans[i] = tadd(trans[i], last);
                last = corr[i] += last;
        }
@@ -3191,7 +3193,7 @@ yearistype(zic_t year, const char *type)
        exit(EXIT_FAILURE);
 }
 
-/* Is A a space character in the C locale?     */
+/* Is A a space character in the C locale?  */
 static bool
 is_space(char a)
 {
@@ -3362,6 +3364,19 @@ itsabbr(const char *abbr, const char *word)
        return true;
 }
 
+/* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case.  */
+
+static bool
+ciprefix(char const *abbr, char const *word)
+{
+       do
+               if (!*abbr)
+                       return true;
+       while (lowerit(*abbr++) == lowerit(*word++));
+
+       return false;
+}
+
 static const struct lookup *
 byword(const char *word, const struct lookup *table)
 {
@@ -3371,6 +3386,23 @@ byword(const char *word, const struct lookup *table)
        if (word == NULL || table == NULL)
                return NULL;
 
+       /*
+        * If TABLE is LASTS and the word starts with "last" followed by a
+        * non-'-', skip the "last" and look in WDAY_NAMES instead. Warn about any
+        * usage of the undocumented prefix "last-".
+        */
+       if (table == lasts && ciprefix("last", word) && word[4])
+       {
+               if (word[4] == '-')
+                       warning(_("\"%s\" is undocumented; use \"last%s\" instead"),
+                                       word, word + 5);
+               else
+               {
+                       word += 4;
+                       table = wday_names;
+               }
+       }
+
        /*
         * Look for exact match.
         */
@@ -3383,13 +3415,31 @@ byword(const char *word, const struct lookup *table)
         */
        foundlp = NULL;
        for (lp = table; lp->l_word != NULL; ++lp)
-               if (itsabbr(word, lp->l_word))
+               if (ciprefix(word, lp->l_word))
                {
                        if (foundlp == NULL)
                                foundlp = lp;
                        else
                                return NULL;    /* multiple inexact matches */
                }
+
+       /* Warn about any backward-compatibility issue with pre-2017c zic.  */
+       if (foundlp)
+       {
+               bool            pre_2017c_match = false;
+
+               for (lp = table; lp->l_word; lp++)
+                       if (itsabbr(word, lp->l_word))
+                       {
+                               if (pre_2017c_match)
+                               {
+                                       warning(_("\"%s\" is ambiguous in pre-2017c zic"), word);
+                                       break;
+                               }
+                               pre_2017c_match = true;
+                       }
+       }
+
        return foundlp;
 }
 
@@ -3621,11 +3671,15 @@ mkdirs(char const *argname, bool ancestors)
 
        cp = name = ecpyalloc(argname);
 
+       /*
+        * On MS-Windows systems, do not worry about drive letters or backslashes,
+        * as this should suffice in practice.  Time zone names do not use drive
+        * letters and backslashes.  If the -d option of zic does not name an
+        * already-existing directory, it can use slashes to separate the
+        * already-existing ancestor prefix from the to-be-created subdirectories.
+        */
+
        /* 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++;