about summary refs log tree commit diff
path: root/timezone
diff options
context:
space:
mode:
Diffstat (limited to 'timezone')
-rw-r--r--timezone/private.h236
-rw-r--r--timezone/tzfile.h6
-rwxr-xr-xtimezone/tzselect.ksh33
-rw-r--r--timezone/zdump.c131
-rw-r--r--timezone/zic.c530
5 files changed, 599 insertions, 337 deletions
diff --git a/timezone/private.h b/timezone/private.h
index e2f23f5f40..1ead14793b 100644
--- a/timezone/private.h
+++ b/timezone/private.h
@@ -1,3 +1,5 @@
+/* Private header for tzdb code.  */
+
 #ifndef PRIVATE_H
 
 #define PRIVATE_H
@@ -15,6 +17,16 @@
 ** Thank you!
 */
 
+/*
+** zdump has been made independent of the rest of the time
+** conversion package to increase confidence in the verification it provides.
+** You can use zdump to help in verifying other implementations.
+** To do this, compile with -DUSE_LTZ=0 and link without the tz library.
+*/
+#ifndef USE_LTZ
+# define USE_LTZ 1
+#endif
+
 /* This string was in the Factory zone through version 2016f.  */
 #define GRANDPARENTED	"Local time zone must be set--see zic manual page"
 
@@ -27,13 +39,28 @@
 #define HAVE_DECL_ASCTIME_R 1
 #endif
 
+#if !defined HAVE_GENERIC && defined __has_extension
+# if __has_extension(c_generic_selections)
+#  define HAVE_GENERIC 1
+# else
+#  define HAVE_GENERIC 0
+# endif
+#endif
+/* _Generic is buggy in pre-4.9 GCC.  */
+#if !defined HAVE_GENERIC && defined __GNUC__
+# define HAVE_GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__))
+#endif
+#ifndef HAVE_GENERIC
+# define HAVE_GENERIC (201112 <= __STDC_VERSION__)
+#endif
+
 #ifndef HAVE_GETTEXT
 #define HAVE_GETTEXT		0
 #endif /* !defined HAVE_GETTEXT */
 
 #ifndef HAVE_INCOMPATIBLE_CTIME_R
 #define HAVE_INCOMPATIBLE_CTIME_R	0
-#endif /* !defined INCOMPATIBLE_CTIME_R */
+#endif
 
 #ifndef HAVE_LINK
 #define HAVE_LINK		1
@@ -43,10 +70,18 @@
 #define HAVE_POSIX_DECLS 1
 #endif
 
+#ifndef HAVE_STDBOOL_H
+#define HAVE_STDBOOL_H (199901 <= __STDC_VERSION__)
+#endif
+
 #ifndef HAVE_STRDUP
 #define HAVE_STRDUP 1
 #endif
 
+#ifndef HAVE_STRTOLL
+#define HAVE_STRTOLL 1
+#endif
+
 #ifndef HAVE_SYMLINK
 #define HAVE_SYMLINK		1
 #endif /* !defined HAVE_SYMLINK */
@@ -76,13 +111,23 @@
 #define ctime_r _incompatible_ctime_r
 #endif /* HAVE_INCOMPATIBLE_CTIME_R */
 
-/* Enable tm_gmtoff and tm_zone on GNUish systems.  */
+/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems.  */
 #define _GNU_SOURCE 1
 /* Fix asctime_r on Solaris 11.  */
 #define _POSIX_PTHREAD_SEMANTICS 1
 /* Enable strtoimax on pre-C99 Solaris 11.  */
 #define __EXTENSIONS__ 1
 
+/* To avoid having 'stat' fail unnecessarily with errno == EOVERFLOW,
+   enable large files on GNUish systems ...  */
+#ifndef _FILE_OFFSET_BITS
+# define _FILE_OFFSET_BITS 64
+#endif
+/* ... and on AIX ...  */
+#define _LARGE_FILES 1
+/* ... and enable large inode numbers on Mac OS X 10.5 and later.  */
+#define _DARWIN_USE_64_BIT_INODE 1
+
 /*
 ** Nested includes
 */
@@ -105,7 +150,6 @@
 #undef tzfree
 
 #include <sys/types.h>	/* for time_t */
-#include <stdio.h>
 #include <string.h>
 #include <limits.h>	/* for CHAR_BIT et al. */
 #include <stdlib.h>
@@ -126,19 +170,8 @@
 #include <libintl.h>
 #endif /* HAVE_GETTEXT */
 
-#if HAVE_SYS_WAIT_H
-#include <sys/wait.h>	/* for WIFEXITED and WEXITSTATUS */
-#endif /* HAVE_SYS_WAIT_H */
-
-#ifndef WIFEXITED
-#define WIFEXITED(status)	(((status) & 0xff) == 0)
-#endif /* !defined WIFEXITED */
-#ifndef WEXITSTATUS
-#define WEXITSTATUS(status)	(((status) >> 8) & 0xff)
-#endif /* !defined WEXITSTATUS */
-
 #if HAVE_UNISTD_H
-#include <unistd.h>	/* for F_OK, R_OK, and other POSIX goodness */
+#include <unistd.h>	/* for R_OK, and other POSIX goodness */
 #endif /* HAVE_UNISTD_H */
 
 #ifndef HAVE_STRFTIME_L
@@ -149,9 +182,22 @@
 # endif
 #endif
 
-#ifndef F_OK
-#define F_OK	0
-#endif /* !defined F_OK */
+#ifndef USG_COMPAT
+# ifndef _XOPEN_VERSION
+#  define USG_COMPAT 0
+# else
+#  define USG_COMPAT 1
+# endif
+#endif
+
+#ifndef HAVE_TZNAME
+# if _POSIX_VERSION < 198808 && !USG_COMPAT
+#  define HAVE_TZNAME 0
+# else
+#  define HAVE_TZNAME 1
+# endif
+#endif
+
 #ifndef R_OK
 #define R_OK	4
 #endif /* !defined R_OK */
@@ -236,15 +282,19 @@ typedef int int_fast32_t;
 #ifndef INTMAX_MAX
 # ifdef LLONG_MAX
 typedef long long intmax_t;
-#  define strtoimax strtoll
+#  if HAVE_STRTOLL
+#   define strtoimax strtoll
+#  endif
 #  define INTMAX_MAX LLONG_MAX
 #  define INTMAX_MIN LLONG_MIN
 # else
 typedef long intmax_t;
-#  define strtoimax strtol
 #  define INTMAX_MAX LONG_MAX
 #  define INTMAX_MIN LONG_MIN
 # endif
+# ifndef strtoimax
+#  define strtoimax strtol
+# endif
 #endif
 
 #ifndef PRIdMAX
@@ -294,12 +344,14 @@ typedef unsigned long uintmax_t;
 #define SIZE_MAX ((size_t) -1)
 #endif
 
-#if 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+#if 3 <= __GNUC__
 # define ATTRIBUTE_CONST __attribute__ ((const))
+# define ATTRIBUTE_MALLOC __attribute__ ((__malloc__))
 # define ATTRIBUTE_PURE __attribute__ ((__pure__))
 # define ATTRIBUTE_FORMAT(spec) __attribute__ ((__format__ spec))
 #else
 # define ATTRIBUTE_CONST /* empty */
+# define ATTRIBUTE_MALLOC /* empty */
 # define ATTRIBUTE_PURE /* empty */
 # define ATTRIBUTE_FORMAT(spec) /* empty */
 #endif
@@ -326,6 +378,16 @@ typedef unsigned long uintmax_t;
 #ifndef EPOCH_OFFSET
 # define EPOCH_OFFSET 0
 #endif
+#ifndef RESERVE_STD_EXT_IDS
+# define RESERVE_STD_EXT_IDS 0
+#endif
+
+/* If standard C identifiers with external linkage (e.g., localtime)
+   are reserved and are not already being renamed anyway, rename them
+   as if compiling with '-Dtime_tz=time_t'.  */
+#if !defined time_tz && RESERVE_STD_EXT_IDS && USE_LTZ
+# define time_tz time_t
+#endif
 
 /*
 ** Compile with -Dtime_tz=T to build the tz package with a private
@@ -335,6 +397,12 @@ typedef unsigned long uintmax_t;
 ** typical platforms.
 */
 #if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0
+# define TZ_TIME_T 1
+#else
+# define TZ_TIME_T 0
+#endif
+
+#if TZ_TIME_T
 # ifdef LOCALTIME_IMPLEMENTATION
 static time_t sys_time(time_t *x) { return time(x); }
 # endif
@@ -367,6 +435,8 @@ typedef time_tz tz_time_t;
 # define posix2time tz_posix2time
 # undef  posix2time_z
 # define posix2time_z tz_posix2time_z
+# undef  strftime
+# define strftime tz_strftime
 # undef  time
 # define time tz_time
 # undef  time2posix
@@ -389,10 +459,34 @@ typedef time_tz tz_time_t;
 # define tzset tz_tzset
 # undef  tzsetwall
 # define tzsetwall tz_tzsetwall
+# if HAVE_STRFTIME_L
+#  undef  strftime_l
+#  define strftime_l tz_strftime_l
+# endif
+# if HAVE_TZNAME
+#  undef  tzname
+#  define tzname tz_tzname
+# endif
+# if USG_COMPAT
+#  undef  daylight
+#  define daylight tz_daylight
+#  undef  timezone
+#  define timezone tz_timezone
+# endif
+# ifdef ALTZONE
+#  undef  altzone
+#  define altzone tz_altzone
+# endif
 
 char *ctime(time_t const *);
 char *ctime_r(time_t const *, char *);
-double difftime(time_t, time_t);
+double difftime(time_t, time_t) ATTRIBUTE_CONST;
+size_t strftime(char *restrict, size_t, char const *restrict,
+		struct tm const *restrict);
+# if HAVE_STRFTIME_L
+size_t strftime_l(char *restrict, size_t, char const *restrict,
+		  struct tm const *restrict, locale_t);
+# endif
 struct tm *gmtime(time_t const *);
 struct tm *gmtime_r(time_t const *restrict, struct tm *restrict);
 struct tm *localtime(time_t const *);
@@ -406,18 +500,29 @@ void tzset(void);
 extern char *asctime_r(struct tm const *restrict, char *restrict);
 #endif
 
-#if !HAVE_POSIX_DECLS
-# ifdef USG_COMPAT
-#  ifndef timezone
+#ifndef HAVE_DECL_ENVIRON
+# if defined environ || defined __USE_GNU
+#  define HAVE_DECL_ENVIRON 1
+# else
+#  define HAVE_DECL_ENVIRON 0
+# endif
+#endif
+
+#if !HAVE_DECL_ENVIRON
+extern char **environ;
+#endif
+
+#if TZ_TIME_T || !HAVE_POSIX_DECLS
+# if HAVE_TZNAME
+extern char *tzname[];
+# endif
+# if USG_COMPAT
 extern long timezone;
-#  endif
-#  ifndef daylight
 extern int daylight;
-#  endif
 # endif
 #endif
 
-#if defined ALTZONE && !defined altzone
+#ifdef ALTZONE
 extern long altzone;
 #endif
 
@@ -427,25 +532,25 @@ extern long altzone;
 */
 
 #ifdef STD_INSPIRED
-# if !defined tzsetwall || defined time_tz
+# if TZ_TIME_T || !defined tzsetwall
 void tzsetwall(void);
 # endif
-# if !defined offtime || defined time_tz
+# if TZ_TIME_T || !defined offtime
 struct tm *offtime(time_t const *, long);
 # endif
-# if !defined timegm || defined time_tz
+# if TZ_TIME_T || !defined timegm
 time_t timegm(struct tm *);
 # endif
-# if !defined timelocal || defined time_tz
+# if TZ_TIME_T || !defined timelocal
 time_t timelocal(struct tm *);
 # endif
-# if !defined timeoff || defined time_tz
+# if TZ_TIME_T || !defined timeoff
 time_t timeoff(struct tm *, long);
 # endif
-# if !defined time2posix || defined time_tz
+# if TZ_TIME_T || !defined time2posix
 time_t time2posix(time_t);
 # endif
-# if !defined posix2time || defined time_tz
+# if TZ_TIME_T || !defined posix2time
 time_t posix2time(time_t);
 # endif
 #endif
@@ -479,10 +584,10 @@ time_t mktime_z(timezone_t restrict, struct tm *restrict);
 timezone_t tzalloc(char const *);
 void tzfree(timezone_t);
 # ifdef STD_INSPIRED
-#  if !defined posix2time_z || defined time_tz
+#  if TZ_TIME_T || !defined posix2time_z
 time_t posix2time_z(timezone_t, time_t) ATTRIBUTE_PURE;
 #  endif
-#  if !defined time2posix_z || defined time_tz
+#  if TZ_TIME_T || !defined time2posix_z
 time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
 #  endif
 # endif
@@ -492,12 +597,12 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
 ** Finally, some convenience items.
 */
 
-#if __STDC_VERSION__ < 199901
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
 # define true 1
 # define false 0
 # define bool int
-#else
-# include <stdbool.h>
 #endif
 
 #define TYPE_BIT(type)	(sizeof (type) * CHAR_BIT)
@@ -513,27 +618,32 @@ time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
 #define MINVAL(t, b)						\
   ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0))
 
-/* The minimum and maximum finite time values.  This implementation
-   assumes no padding if time_t is signed and either the compiler is
-   pre-C11 or time_t is not one of the standard signed integer types.  */
-#if 201112 <= __STDC_VERSION__
-static time_t const time_t_min
-  = (TYPE_SIGNED(time_t)
-     ? _Generic((time_t) 0,
-		signed char: SCHAR_MIN, short: SHRT_MIN,
-		int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN,
-		default: MINVAL(time_t, TYPE_BIT(time_t)))
-     : 0);
-static time_t const time_t_max
-  = (TYPE_SIGNED(time_t)
-     ? _Generic((time_t) 0,
-		signed char: SCHAR_MAX, short: SHRT_MAX,
-		int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX,
-		default: MAXVAL(time_t, TYPE_BIT(time_t)))
-     : -1);
+/* The extreme time values, assuming no padding.  */
+#define TIME_T_MIN_NO_PADDING MINVAL(time_t, TYPE_BIT(time_t))
+#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t))
+
+/* The extreme time values.  These are macros, not constants, so that
+   any portability problem occur only when compiling .c files that use
+   the macros, which is safer for applications that need only zdump and zic.
+   This implementation assumes no padding if time_t is signed and
+   either the compiler lacks support for _Generic or time_t is not one
+   of the standard signed integer types.  */
+#if HAVE_GENERIC
+# define TIME_T_MIN \
+    _Generic((time_t) 0, \
+	     signed char: SCHAR_MIN, short: SHRT_MIN, \
+	     int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \
+	     default: TIME_T_MIN_NO_PADDING)
+# define TIME_T_MAX \
+    (TYPE_SIGNED(time_t) \
+     ? _Generic((time_t) 0, \
+		signed char: SCHAR_MAX, short: SHRT_MAX, \
+		int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
+		default: TIME_T_MAX_NO_PADDING)			    \
+     : (time_t) -1)
 #else
-static time_t const time_t_min = MINVAL(time_t, TYPE_BIT(time_t));
-static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t));
+# define TIME_T_MIN TIME_T_MIN_NO_PADDING
+# define TIME_T_MAX TIME_T_MAX_NO_PADDING
 #endif
 
 /*
@@ -550,7 +660,7 @@ static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t));
 ** INITIALIZE(x)
 */
 
-#ifdef lint
+#ifdef GCC_LINT
 # define INITIALIZE(x)	((x) = 0)
 #else
 # define INITIALIZE(x)
@@ -633,7 +743,7 @@ char *ctime_r(time_t const *, char *);
 ** 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.
 */
 
diff --git a/timezone/tzfile.h b/timezone/tzfile.h
index 0e51dce1d7..27a38cc74d 100644
--- a/timezone/tzfile.h
+++ b/timezone/tzfile.h
@@ -1,3 +1,5 @@
+/* Layout and location of TZif files.  */
+
 #ifndef TZFILE_H
 
 #define TZFILE_H
@@ -20,11 +22,11 @@
 */
 
 #ifndef TZDIR
-#define TZDIR	"/usr/local/etc/zoneinfo" /* Time zone object file directory */
+#define TZDIR	"/usr/share/zoneinfo" /* Time zone object file directory */
 #endif /* !defined TZDIR */
 
 #ifndef TZDEFAULT
-#define TZDEFAULT	"localtime"
+#define TZDEFAULT	"/etc/localtime"
 #endif /* !defined TZDEFAULT */
 
 #ifndef TZDEFRULES
diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh
index d2c3a6d1dd..18fce27e24 100755
--- a/timezone/tzselect.ksh
+++ b/timezone/tzselect.ksh
@@ -1,12 +1,11 @@
 #!/bin/bash
+# Ask the user about the time zone, and output the resulting TZ value to stdout.
+# Interact with the user via stderr and stdin.
 
 PKGVERSION='(tzcode) '
 TZVERSION=see_Makefile
 REPORT_BUGS_TO=tz@iana.org
 
-# Ask the user about the time zone, and output the resulting TZ value to stdout.
-# Interact with the user via stderr and stdin.
-
 # Contributed by Paul Eggert.  This file is in the public domain.
 
 # Porting notes:
@@ -17,9 +16,9 @@ REPORT_BUGS_TO=tz@iana.org
 # If your host lacks both Bash and the Korn shell, you can get their
 # source from one of these locations:
 #
-#	Bash <http://www.gnu.org/software/bash/bash.html>
+#	Bash <https://www.gnu.org/software/bash/>
 #	Korn Shell <http://www.kornshell.com/>
-#	Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/>
+#	MirBSD Korn Shell <https://www.mirbsd.org/mksh.htm>
 #
 # For portability to Solaris 9 /bin/sh this script avoids some POSIX
 # features and common extensions, such as $(...) (which works sometimes
@@ -29,8 +28,8 @@ REPORT_BUGS_TO=tz@iana.org
 # If your host lacks awk, or has an old awk that does not conform to Posix,
 # you can use either of the following free programs instead:
 #
-#	Gawk (GNU awk) <http://www.gnu.org/software/gawk/>
-#	mawk <http://invisible-island.net/mawk/>
+#	Gawk (GNU awk) <https://www.gnu.org/software/gawk/>
+#	mawk <https://invisible-island.net/mawk/>
 
 
 # Specify default values for environment variables if they are unset.
@@ -55,7 +54,7 @@ location_limit=10
 zonetabtype=zone1970
 
 usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
-Select a time zone interactively.
+Select a timezone interactively.
 
 Options:
 
@@ -327,7 +326,7 @@ while
 	eval '
 	    doselect '"$quoted_continents"' \
 		"coord - I want to use geographical coordinates." \
-		"TZ - I want to specify the time zone using the Posix TZ format."
+		"TZ - I want to specify the timezone using the Posix TZ format."
 	    continent=$select_result
 	    case $continent in
 	    Americas) continent=America;;
@@ -342,8 +341,10 @@ while
 		while
 			echo >&2 'Please enter the desired value' \
 				'of the TZ environment variable.'
-			echo >&2 'For example, GST-10 is a zone named GST' \
-				'that is 10 hours ahead (east) of UTC.'
+			echo >&2 'For example, AEST-10 is abbreviated' \
+				'AEST and is 10 hours'
+			echo >&2 'ahead (east) of Greenwich,' \
+				'with no daylight saving time.'
 			read TZ
 			$AWK -v TZ="$TZ" 'BEGIN {
 				tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})"
@@ -360,7 +361,7 @@ while
 				exit 0
 			}'
 		do
-		    say >&2 "'$TZ' is not a conforming Posix time zone string."
+		    say >&2 "'$TZ' is not a conforming Posix timezone string."
 		done
 		TZ_for_date=$TZ;;
 	*)
@@ -386,8 +387,7 @@ while
 		      BEGIN { FS = "\t" }
 		      { print $NF }
 		    '`
-		    echo >&2 'Please select one of the following' \
-			    'time zone regions,'
+		    echo >&2 'Please select one of the following timezones,' \
 		    echo >&2 'listed roughly in increasing order' \
 			    "of distance from $coord".
 		    doselect $regions
@@ -437,7 +437,7 @@ while
 		esac
 
 
-		# Get list of names of time zone rule regions in the country.
+		# Get list of timezones in the country.
 		regions=`$AWK \
 			-v country="$country" \
 			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
@@ -460,8 +460,7 @@ while
 		# If there's more than one region, ask the user which one.
 		case $regions in
 		*"$newline"*)
-			echo >&2 'Please select one of the following' \
-				'time zone regions.'
+			echo >&2 'Please select one of the following timezones.'
 			doselect $regions
 			region=$select_result;;
 		*)
diff --git a/timezone/zdump.c b/timezone/zdump.c
index bf75800101..608f288691 100644
--- a/timezone/zdump.c
+++ b/timezone/zdump.c
@@ -1,3 +1,5 @@
+/* Dump time zone data in a textual format.  */
+
 /*
 ** This file is in the public domain, so clarified as of
 ** 2009-05-17 by Arthur David Olson.
@@ -5,21 +7,16 @@
 
 #include "version.h"
 
-/*
-** This code has been made independent of the rest of the time
-** conversion package to increase confidence in the verification it provides.
-** You can use this code to help in verifying other implementations.
-** To do this, compile with -DUSE_LTZ=0 and link without the tz library.
-*/
-
 #ifndef NETBSD_INSPIRED
 # define NETBSD_INSPIRED 1
 #endif
-#ifndef USE_LTZ
-# define USE_LTZ 1
-#endif
 
 #include "private.h"
+#include <stdio.h>
+
+#ifndef HAVE_SNPRINTF
+# define HAVE_SNPRINTF (199901 <= __STDC_VERSION__)
+#endif
 
 #ifndef HAVE_LOCALTIME_R
 # define HAVE_LOCALTIME_R 1
@@ -72,14 +69,11 @@ enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
 # define timezone_t char **
 #endif
 
-extern char **	environ;
-
 #if !HAVE_POSIX_DECLS
 extern int	getopt(int argc, char * const argv[],
 			const char * options);
 extern char *	optarg;
 extern int	optind;
-extern char *	tzname[];
 #endif
 
 /* The minimum and maximum finite time values.  */
@@ -144,7 +138,7 @@ sumsize(size_t a, size_t b)
 
 /* Return a pointer to a newly allocated buffer of size SIZE, exiting
    on failure.  SIZE should be nonzero.  */
-static void *
+static void * ATTRIBUTE_MALLOC
 xmalloc(size_t size)
 {
   void *p = malloc(size);
@@ -263,7 +257,7 @@ tzfree(timezone_t env)
 }
 #endif /* ! USE_LOCALTIME_RZ */
 
-/* A UTC time zone, and its initializer.  */
+/* A UT time zone, and its initializer.  */
 static timezone_t gmtz;
 static void
 gmtzinit(void)
@@ -278,7 +272,7 @@ gmtzinit(void)
   }
 }
 
-/* Convert *TP to UTC, storing the broken-down time into *TMP.
+/* Convert *TP to UT, storing the broken-down time into *TMP.
    Return TMP if successful, NULL otherwise.  This is like gmtime_r(TP, TMP),
    except typically faster if USE_LOCALTIME_RZ.  */
 static struct tm *
@@ -390,7 +384,7 @@ static void
 usage(FILE * const stream, const int status)
 {
 	fprintf(stream,
-_("%s: usage: %s OPTIONS ZONENAME ...\n"
+_("%s: usage: %s OPTIONS TIMEZONE ...\n"
   "Options include:\n"
   "  -c [L,]U   Start at year L (default -500), end before year U (default 2500)\n"
   "  -t [L,]U   Start at time L, end before time U (in seconds since 1970)\n"
@@ -550,6 +544,7 @@ main(int argc, char *argv[])
 		}
 		if (t < cutlotime)
 			t = cutlotime;
+		INITIALIZE (ab);
 		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
 		if (tm_ok) {
 		  ab = saveabbr(&abbrev, &abbrevsize, &tm);
@@ -565,11 +560,10 @@ main(int argc, char *argv[])
 				 : cuthitime);
 		  struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
 		  bool newtm_ok = newtmp != NULL;
-		  if (! (tm_ok & newtm_ok
-			 ? (delta(&newtm, &tm) == newt - t
-			    && newtm.tm_isdst == tm.tm_isdst
-			    && strcmp(abbr(&newtm), ab) == 0)
-			 : tm_ok == newtm_ok)) {
+		  if (tm_ok != newtm_ok
+		      || (tm_ok && (delta(&newtm, &tm) != newt - t
+				    || newtm.tm_isdst != tm.tm_isdst
+				    || strcmp(abbr(&newtm), ab) != 0))) {
 		    newt = hunt(tz, argv[i], t, newt);
 		    newtmp = localtime_rz(tz, &newt, &newtm);
 		    newtm_ok = newtmp != NULL;
@@ -685,17 +679,15 @@ hunt(timezone_t tz, char *name, time_t lot, time_t hit)
 }
 
 /*
-** Thanks to Paul Eggert for logic used in delta.
+** Thanks to Paul Eggert for logic used in delta_nonneg.
 */
 
 static intmax_t
-delta(struct tm * newp, struct tm *oldp)
+delta_nonneg(struct tm *newp, struct tm *oldp)
 {
 	register intmax_t	result;
 	register int		tmy;
 
-	if (newp->tm_year < oldp->tm_year)
-		return -delta(oldp, newp);
 	result = 0;
 	for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
 		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
@@ -709,6 +701,14 @@ delta(struct tm * newp, struct tm *oldp)
 	return result;
 }
 
+static intmax_t
+delta(struct tm *newp, struct tm *oldp)
+{
+  return (newp->tm_year < oldp->tm_year
+	  ? -delta_nonneg(oldp, newp)
+	  : delta_nonneg(newp, oldp));
+}
+
 #ifndef TM_GMTOFF
 /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
    Assume A and B differ by at most one year.  */
@@ -722,8 +722,8 @@ adjusted_yday(struct tm const *a, struct tm const *b)
 }
 #endif
 
-/* If A is the broken-down local time and B the broken-down UTC for
-   the same instant, return A's UTC offset in seconds, where positive
+/* If A is the broken-down local time and B the broken-down UT for
+   the same instant, return A's UT offset in seconds, where positive
    offsets are east of Greenwich.  On failure, return LONG_MIN.
 
    If T is nonnull, *T is the timestamp that corresponds to A; call
@@ -787,6 +787,40 @@ show(timezone_t tz, char *zone, time_t t, bool v)
 		abbrok(abbr(tmp), zone);
 }
 
+#if HAVE_SNPRINTF
+# define my_snprintf snprintf
+#else
+# include <stdarg.h>
+
+/* A substitute for snprintf that is good enough for zdump.  */
+static int ATTRIBUTE_FORMAT((printf, 3, 4))
+my_snprintf(char *s, size_t size, char const *format, ...)
+{
+  int n;
+  va_list args;
+  char const *arg;
+  size_t arglen, slen;
+  char buf[1024];
+  va_start(args, format);
+  if (strcmp(format, "%s") == 0) {
+    arg = va_arg(args, char const *);
+    arglen = strlen(arg);
+  } else {
+    n = vsprintf(buf, format, args);
+    if (n < 0)
+      return n;
+    arg = buf;
+    arglen = n;
+  }
+  slen = arglen < size ? arglen : size - 1;
+  memcpy(s, arg, slen);
+  s[slen] = '\0';
+  n = arglen <= INT_MAX ? arglen : -1;
+  va_end(args);
+  return n;
+}
+#endif
+
 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
    Use ISO 8601 format +HH:MM:SS.  Omit :SS if SS is zero, and omit
    :MM too if MM is also zero.
@@ -799,16 +833,16 @@ format_local_time(char *buf, size_t size, struct tm const *tm)
 {
   int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
   return (ss
-	  ? snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
+	  ? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
 	  : mm
-	  ? snprintf(buf, size, "%02d:%02d", hh, mm)
-	  : snprintf(buf, size, "%02d", hh));
+	  ? my_snprintf(buf, size, "%02d:%02d", hh, mm)
+	  : my_snprintf(buf, size, "%02d", hh));
 }
 
-/* Store into BUF, of size SIZE, a formatted UTC offset for the
+/* Store into BUF, of size SIZE, a formatted UT offset for the
    localtime *TM corresponding to time T.  Use ISO 8601 format
    +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
-   format -00 for unknown UTC offsets.  If the hour needs more than
+   format -00 for unknown UT offsets.  If the hour needs more than
    two digits to represent, extend the length of HH as needed.
    Otherwise, omit SS if SS is zero, and omit MM too if MM is also
    zero.
@@ -837,10 +871,10 @@ format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
   mm = off / 60 % 60;
   hh = off / 60 / 60;
   return (ss || 100 <= hh
-	  ? snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
+	  ? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
 	  : mm
-	  ? snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
-	  : snprintf(buf, size, "%c%02ld", sign, hh));
+	  ? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
+	  : my_snprintf(buf, size, "%c%02ld", sign, hh));
 }
 
 /* Store into BUF (of size SIZE) a quoted string representation of P.
@@ -883,7 +917,7 @@ format_quoted_string(char *buf, size_t size, char const *p)
 
    %f zone name
    %L local time as per format_local_time
-   %Q like "U\t%Z\tD" where U is the UTC offset as for format_utc_offset
+   %Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
       and D is the isdst flag; except omit D if it is zero, omit %Z if
       it equals U, quote and escape %Z if it contains nonalphabetics,
       and omit any trailing tabs.  */
@@ -943,15 +977,16 @@ istrftime(char *buf, size_t size, char const *time_fmt,
 	    for (abp = ab; is_alpha(*abp); abp++)
 	      continue;
 	    len = (!*abp && *ab
-		   ? snprintf(b, s, "%s", ab)
+		   ? my_snprintf(b, s, "%s", ab)
 		   : format_quoted_string(b, s, ab));
 	    if (s <= len)
 	      return false;
 	    b += len, s -= len;
 	  }
-	  formatted_len = (tm->tm_isdst
-			   ? snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
-			   : 0);
+	  formatted_len
+	    = (tm->tm_isdst
+	       ? my_snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
+	       : 0);
 	}
 	break;
       }
@@ -993,9 +1028,11 @@ abbr(struct tm const *tmp)
 #ifdef TM_ZONE
 	return tmp->TM_ZONE;
 #else
-	return (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst]
-		? tzname[0 < tmp->tm_isdst]
-		: "");
+# if HAVE_TZNAME
+	if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
+	  return tzname[0 < tmp->tm_isdst];
+# endif
+	return "";
 #endif
 }
 
@@ -1030,10 +1067,10 @@ tformat(void)
 static void
 dumptime(register const struct tm *timeptr)
 {
-	static const char	wday_name[][3] = {
+	static const char	wday_name[][4] = {
 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
 	};
-	static const char	mon_name[][3] = {
+	static const char	mon_name[][4] = {
 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 	};
@@ -1059,7 +1096,7 @@ dumptime(register const struct tm *timeptr)
 		(int) (sizeof mon_name / sizeof mon_name[0]))
 			mn = "???";
 	else		mn = mon_name[timeptr->tm_mon];
-	printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
+	printf("%s %s%3d %.2d:%.2d:%.2d ",
 		wn, mn,
 		timeptr->tm_mday, timeptr->tm_hour,
 		timeptr->tm_min, timeptr->tm_sec);
diff --git a/timezone/zic.c b/timezone/zic.c
index e738386600..cb1bf28bfb 100644
--- a/timezone/zic.c
+++ b/timezone/zic.c
@@ -1,3 +1,5 @@
+/* Compile .zi time zone data into TZif binary files.  */
+
 /*
 ** This file is in the public domain, so clarified as of
 ** 2006-07-17 by Arthur David Olson.
@@ -11,6 +13,7 @@
 #include <locale.h>
 #include <stdarg.h>
 #include <stddef.h>
+#include <stdio.h>
 
 #define	ZIC_VERSION_PRE_2013 '2'
 #define	ZIC_VERSION	'3'
@@ -40,12 +43,32 @@ typedef int_fast64_t	zic_t;
 #else
 #define MKDIR_UMASK 0755
 #endif
+/* Port to native MS-Windows and to ancient UNIX.  */
+#if !defined S_ISDIR && defined S_IFDIR && defined S_IFMT
+# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+#endif
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>	/* for WIFEXITED and WEXITSTATUS */
+#endif /* HAVE_SYS_WAIT_H */
+
+#ifndef WIFEXITED
+#define WIFEXITED(status)	(((status) & 0xff) == 0)
+#endif /* !defined WIFEXITED */
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(status)	(((status) >> 8) & 0xff)
+#endif /* !defined WEXITSTATUS */
 
 /* The maximum ptrdiff_t value, for pre-C99 platforms.  */
 #ifndef PTRDIFF_MAX
 static ptrdiff_t const PTRDIFF_MAX = MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t));
 #endif
 
+/* The minimum alignment of a type, for pre-C11 platforms.  */
+#if __STDC_VERSION__ < 201112
+# define _Alignof(type) offsetof(struct { char a; type b; }, b)
+#endif
+
 /* The type for line numbers.  Use PRIdMAX to format them; formerly
    there was also "#define PRIdLINENO PRIdMAX" and formats used
    PRIdLINENO, but xgettext cannot grok that.  */
@@ -73,7 +96,9 @@ struct rule {
 					/* 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 */
+	bool		r_isdst;	/* is this daylight saving time? */
+	zic_t		r_stdoff;	/* offset from default time (which is
+					   usually standard time) */
 	const char *	r_abbrvar;	/* variable part of abbreviation */
 
 	bool		r_todo;		/* a rule to do (used in outzone) */
@@ -94,10 +119,11 @@ struct zone {
 
 	const char *	z_name;
 	zic_t		z_gmtoff;
-	const char *	z_rule;
+	char *		z_rule;
 	const char *	z_format;
 	char		z_format_specifier;
 
+	bool		z_isdst;
 	zic_t		z_stdoff;
 
 	struct rule *	z_rules;
@@ -135,8 +161,8 @@ static void	adjleap(void);
 static void	associate(void);
 static void	dolink(const char *, const char *, bool);
 static char **	getfields(char * buf);
-static zic_t	gethms(const char * string, const char * errstring,
-		       bool);
+static zic_t	gethms(const char * string, const char * errstring);
+static zic_t	getstdoff(char *, bool *);
 static void	infile(const char * filename);
 static void	inleap(char ** fields, int nfields);
 static void	inlink(char ** fields, int nfields);
@@ -164,11 +190,13 @@ static bool	yearistype(zic_t year, const char * type);
 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
+   TZif 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.  */
+#ifndef WORK_AROUND_QTBUG_53071
 enum { WORK_AROUND_QTBUG_53071 = true };
+#endif
 
 static int		charcnt;
 static bool		errors;
@@ -299,10 +327,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}
 };
@@ -407,6 +438,16 @@ size_product(size_t nitems, size_t itemsize)
 	return nitems * itemsize;
 }
 
+static ATTRIBUTE_PURE size_t
+align_to(size_t size, size_t alignment)
+{
+  size_t aligned_size = size + alignment - 1;
+  aligned_size -= aligned_size % alignment;
+  if (aligned_size < size)
+    memory_exhausted(_("alignment overflow"));
+  return aligned_size;
+}
+
 #if !HAVE_STRDUP
 static char *
 strdup(char const *str)
@@ -416,7 +457,7 @@ strdup(char const *str)
 }
 #endif
 
-static ATTRIBUTE_PURE void *
+static void *
 memcheck(void *ptr)
 {
 	if (ptr == NULL)
@@ -424,7 +465,7 @@ memcheck(void *ptr)
 	return ptr;
 }
 
-static void *
+static void * ATTRIBUTE_MALLOC
 emalloc(size_t size)
 {
   return memcheck(malloc(size));
@@ -436,7 +477,7 @@ erealloc(void *ptr, size_t size)
   return memcheck(realloc(ptr, size));
 }
 
-static char *
+static char * ATTRIBUTE_MALLOC
 ecpyalloc (char const *str)
 {
   return memcheck(strdup(str));
@@ -534,7 +575,7 @@ usage(FILE *stream, int status)
   fprintf(stream,
 	  _("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n"
 	    "\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n"
-	    "\t[ -L leapseconds ] [ filename ... ]\n\n"
+	    "\t[ -t localtime-link ] [ -L leapseconds ] [ filename ... ]\n\n"
 	    "Report bugs to %s.\n"),
 	  progname, progname, REPORT_BUGS_TO);
   if (status == EXIT_SUCCESS)
@@ -566,6 +607,7 @@ static const char *	psxrules;
 static const char *	lcltime;
 static const char *	directory;
 static const char *	leapsec;
+static const char *	tzdefault;
 static const char *	yitcommand;
 
 int
@@ -598,7 +640,7 @@ main(int argc, char **argv)
 		} else if (strcmp(argv[k], "--help") == 0) {
 			usage(stdout, EXIT_SUCCESS);
 		}
-	while ((c = getopt(argc, argv, "d:l:p:L:vsy:")) != EOF && c != -1)
+	while ((c = getopt(argc, argv, "d:l:L:p:st:vy:")) != EOF && c != -1)
 		switch (c) {
 			default:
 				usage(stderr, EXIT_FAILURE);
@@ -632,10 +674,21 @@ _("%s: More than one -p option specified\n"),
 					return EXIT_FAILURE;
 				}
 				break;
+			case 't':
+				if (tzdefault != NULL) {
+				  fprintf(stderr,
+					  _("%s: More than one -t option"
+					    " specified\n"),
+					  progname);
+				  return EXIT_FAILURE;
+				}
+				tzdefault = optarg;
+				break;
 			case 'y':
-				if (yitcommand == NULL)
+				if (yitcommand == NULL) {
+					warning(_("-y is obsolescent"));
 					yitcommand = optarg;
-				else {
+				} else {
 					fprintf(stderr,
 _("%s: More than one -y option specified\n"),
 						progname);
@@ -663,6 +716,8 @@ _("%s: More than one -L option specified\n"),
 		usage(stderr, EXIT_FAILURE);	/* usage message by request */
 	if (directory == NULL)
 		directory = TZDIR;
+	if (tzdefault == NULL)
+		tzdefault = TZDEFAULT;
 	if (yitcommand == NULL)
 		yitcommand = "yearistype";
 
@@ -699,7 +754,7 @@ _("%s: More than one -L option specified\n"),
 	}
 	if (lcltime != NULL) {
 		eat(_("command line"), 1);
-		dolink(lcltime, TZDEFAULT, true);
+		dolink(lcltime, tzdefault, true);
 	}
 	if (psxrules != NULL) {
 		eat(_("command line"), 1);
@@ -864,9 +919,11 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink)
 	  char *linkalloc = absolute ? NULL : relname(fromfield, tofield);
 	  char const *contents = absolute ? fromfield : linkalloc;
 	  int symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno;
-	  if (symlink_errno == ENOENT && !todirs_made) {
+	  if (!todirs_made
+	      && (symlink_errno == ENOENT || symlink_errno == ENOTSUP)) {
 	    mkdirs(tofield, true);
-	    symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno;
+	    if (symlink_errno == ENOENT)
+	      symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno;
 	  }
 	  free(linkalloc);
 	  if (symlink_errno == 0) {
@@ -909,44 +966,6 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink)
 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);
 
-/* 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.
-
-   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 (- (1LL << 59))
-#endif
-
-/* 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.
-
-   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 true if NAME is a directory.  */
 static bool
 itsdir(char const *name)
@@ -1053,8 +1072,7 @@ associate(void)
 			** Maybe we have a local standard time offset.
 			*/
 			eat(zp->z_filename, zp->z_linenum);
-			zp->z_stdoff = gethms(zp->z_rule, _("unruly zone"),
-				true);
+			zp->z_stdoff = getstdoff(zp->z_rule, &zp->z_isdst);
 			/*
 			** Note, though, that if there's no rule,
 			** a '%s' in the format is a bad thing.
@@ -1114,6 +1132,8 @@ infile(const char *name)
 		} else if (wantcont) {
 			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"));
@@ -1130,11 +1150,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" */
@@ -1160,26 +1176,38 @@ _("%s: panic: Invalid l_value %d\n"),
 */
 
 static zic_t
-gethms(char const *string, char const *errstring, bool signable)
+gethms(char const *string, char const *errstring)
 {
 	zic_t	hh;
-	int	mm, ss, sign;
-	char xs;
+	int sign, mm = 0, ss = 0;
+	char hhx, mmx, ssx, xr = '0', xs;
+	int tenths = 0;
+	bool ok = true;
 
 	if (string == NULL || *string == '\0')
 		return 0;
-	if (!signable)
-		sign = 1;
-	else if (*string == '-') {
+	if (*string == '-') {
 		sign = -1;
 		++string;
 	} else	sign = 1;
-	if (sscanf(string, "%"SCNdZIC"%c", &hh, &xs) == 1)
-		mm = ss = 0;
-	else if (sscanf(string, "%"SCNdZIC":%d%c", &hh, &mm, &xs) == 2)
-		ss = 0;
-	else if (sscanf(string, "%"SCNdZIC":%d:%d%c", &hh, &mm, &ss, &xs)
-		 != 3) {
+	switch (sscanf(string,
+		       "%"SCNdZIC"%c%d%c%d%c%1d%*[0]%c%*[0123456789]%c",
+		       &hh, &hhx, &mm, &mmx, &ss, &ssx, &tenths, &xr, &xs)) {
+	  default: ok = false; break;
+	  case 8:
+	    ok = '0' <= xr && xr <= '9';
+	    /* fallthrough */
+	  case 7:
+	    ok &= ssx == '.';
+	    if (ok && noise)
+	      warning(_("fractional seconds rejected by"
+			" pre-2018 versions of zic"));
+	    /* fallthrough */
+	  case 5: ok &= mmx == ':'; /* fallthrough */
+	  case 3: ok &= hhx == ':'; /* fallthrough */
+	  case 1: break;
+	}
+	if (!ok) {
 			error("%s", errstring);
 			return 0;
 	}
@@ -1193,6 +1221,7 @@ gethms(char const *string, char const *errstring, bool signable)
 		error(_("time overflow"));
 		return 0;
 	}
+	ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths; /* Round to even.  */
 	if (noise && (hh > HOURSPERDAY ||
 		(hh == HOURSPERDAY && (mm != 0 || ss != 0))))
 warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
@@ -1200,6 +1229,24 @@ warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
 		    sign * (mm * SECSPERMIN + ss));
 }
 
+static zic_t
+getstdoff(char *field, bool *isdst)
+{
+  int dst = -1;
+  zic_t stdoff;
+  size_t fieldlen = strlen(field);
+  if (fieldlen != 0) {
+    char *ep = field + fieldlen - 1;
+    switch (*ep) {
+      case 'd': dst = 1; *ep = '\0'; break;
+      case 's': dst = 0; *ep = '\0'; break;
+    }
+  }
+  stdoff = gethms(field, _("invalid saved time"));
+  *isdst = dst < 0 ? stdoff != 0 : dst;
+  return stdoff;
+}
+
 static void
 inrule(char **fields, int nfields)
 {
@@ -1209,13 +1256,18 @@ inrule(char **fields, int nfields)
 		error(_("wrong number of fields on Rule line"));
 		return;
 	}
-	if (*fields[RF_NAME] == '\0') {
-		error(_("nameless rule"));
+	switch (*fields[RF_NAME]) {
+	  case '\0':
+	  case ' ': case '\f': case '\n': case '\r': case '\t': case '\v':
+	  case '+': case '-':
+	  case '0': case '1': case '2': case '3': case '4':
+	  case '5': case '6': case '7': case '8': case '9':
+		error(_("Invalid rule name \"%s\""), fields[RF_NAME]);
 		return;
 	}
 	r.r_filename = filename;
 	r.r_linenum = linenum;
-	r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), true);
+	r.r_stdoff = getstdoff(fields[RF_STDOFF], &r.r_isdst);
 	rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
 		fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
 	r.r_name = ecpyalloc(fields[RF_NAME]);
@@ -1235,10 +1287,10 @@ inzone(char **fields, int nfields)
 		error(_("wrong number of fields on Zone line"));
 		return false;
 	}
-	if (strcmp(fields[ZF_NAME], TZDEFAULT) == 0 && lcltime != NULL) {
+	if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0) {
 		error(
 _("\"Zone %s\" line and -l option are mutually exclusive"),
-			TZDEFAULT);
+			tzdefault);
 		return false;
 	}
 	if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) {
@@ -1304,7 +1356,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);
+	z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"));
 	if ((cp = strchr(fields[i_format], '%')) != 0) {
 		if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
 		    || strchr(fields[i_format], '/')) {
@@ -1426,7 +1478,7 @@ inleap(char **fields, int nfields)
 		return;
 	}
 	t = dayoff * SECSPERDAY;
-	tod = gethms(fields[LP_TIME], _("invalid time of day"), false);
+	tod = gethms(fields[LP_TIME], _("invalid time of day"));
 	cp = fields[LP_CORR];
 	{
 		register bool	positive;
@@ -1435,15 +1487,9 @@ inleap(char **fields, int nfields)
 		if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */
 			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"));
 			return;
@@ -1455,8 +1501,8 @@ inleap(char **fields, int nfields)
 			return;
 		}
 		t = tadd(t, tod);
-		if (t < early_time) {
-			error(_("leap second precedes Big Bang"));
+		if (t < 0) {
+			error(_("leap second precedes Epoch"));
 			return;
 		}
 		leapadd(t, positive, lp->l_value, count);
@@ -1527,7 +1573,7 @@ rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 				break;
 		}
 	}
-	rp->r_tod = gethms(dp, _("invalid time of day"), false);
+	rp->r_tod = gethms(dp, _("invalid time of day"));
 	free(dp);
 	/*
 	** Year work.
@@ -1584,13 +1630,16 @@ 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
+	**	lastSunday
+	**	last-Sunday (undocumented; warn about this)
 	**	Sun<=20
 	**	Sun>=7
 	*/
@@ -1682,14 +1731,20 @@ atcomp(const void *avp, const void *bvp)
 	return (a < b) ? -1 : (a > b);
 }
 
-static bool
-is32(const zic_t x)
+static void
+swaptypes(int i, int j)
 {
-	return INT32_MIN <= x && x <= INT32_MAX;
+  { zic_t t = gmtoffs[i]; gmtoffs[i] = gmtoffs[j]; gmtoffs[j] = t; }
+  { char t = isdsts[i]; isdsts[i] = isdsts[j]; isdsts[j] = t; }
+  { unsigned char t = abbrinds[i]; abbrinds[i] = abbrinds[j];
+    abbrinds[j] = t; }
+  { bool t = ttisstds[i]; ttisstds[i] = ttisstds[j]; ttisstds[j] = t; }
+  { bool t = ttisgmts[i]; ttisgmts[i] = ttisgmts[j]; ttisgmts[j] = t; }
 }
 
 static void
-writezone(const char *const name, const char *const string, char version)
+writezone(const char *const name, const char *const string, char version,
+	  int defaulttype)
 {
 	register FILE *			fp;
 	register ptrdiff_t		i, j;
@@ -1702,7 +1757,11 @@ writezone(const char *const name, const char *const string, char version)
 	zic_t one = 1;
 	zic_t y2038_boundary = one << 31;
 	ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071;
-	zic_t *ats = emalloc(size_product(nats, sizeof *ats + 1));
+
+	/* Allocate the ATS and TYPES arrays via a single malloc,
+	   as this is a bit faster.  */
+	zic_t *ats = emalloc(align_to(size_product(nats, sizeof *ats + 1),
+				      _Alignof(zic_t)));
 	void *typesptr = ats + nats;
 	unsigned char *types = typesptr;
 
@@ -1719,13 +1778,11 @@ writezone(const char *const name, const char *const string, char version)
 
 		toi = 0;
 		fromi = 0;
-		while (fromi < timecnt && attypes[fromi].at < early_time)
-			++fromi;
 		for ( ; fromi < timecnt; ++fromi) {
-			if (toi > 1 && ((attypes[fromi].at +
+			if (toi != 0 && ((attypes[fromi].at +
 				gmtoffs[attypes[toi - 1].type]) <=
-				(attypes[toi - 1].at +
-				gmtoffs[attypes[toi - 2].type]))) {
+				(attypes[toi - 1].at + gmtoffs[toi == 1 ? 0
+				: attypes[toi - 2].type]))) {
 					attypes[toi - 1].type =
 						attypes[fromi].type;
 					continue;
@@ -1755,18 +1812,6 @@ 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.
 	*/
@@ -1778,6 +1823,22 @@ writezone(const char *const name, const char *const string, char version)
 				break;
 			}
 	}
+
+	/* Work around QTBUG-53071 for timestamps 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.  Do this after correcting for leap
+	   seconds, as the idea is to insert a transition just before
+	   32-bit time_t rolls around, and this occurs at a slightly
+	   different moment if transitions are leap-second corrected.  */
+	if (WORK_AROUND_QTBUG_53071 && timecnt != 0
+	    && ats[timecnt - 1] < y2038_boundary - 1 && strchr(string, '<')) {
+	  ats[timecnt] = y2038_boundary - 1;
+	  types[timecnt] = types[timecnt - 1];
+	  timecnt++;
+	}
+
 	/*
 	** Figure out 32-bit-limited starts and counts.
 	*/
@@ -1785,22 +1846,22 @@ writezone(const char *const name, const char *const string, char version)
 	timei32 = 0;
 	leapcnt32 = leapcnt;
 	leapi32 = 0;
-	while (timecnt32 > 0 && !is32(ats[timecnt32 - 1]))
+	while (0 < timecnt32 && INT32_MAX < ats[timecnt32 - 1])
 		--timecnt32;
-	while (timecnt32 > 0 && !is32(ats[timei32])) {
+	while (1 < timecnt32 && ats[timei32] < INT32_MIN
+	       && ats[timei32 + 1] <= INT32_MIN) {
+		/* Discard too-low transitions, except keep any last too-low
+		   transition if no transition is exactly at INT32_MIN.
+		   The kept transition will be output as an INT32_MIN
+		   "transition" appropriate for buggy 32-bit clients that do
+		   not use time type 0 for timestamps before the first
+		   transition; see below.  */
 		--timecnt32;
 		++timei32;
 	}
-	/*
-	** Output an INT32_MIN "transition" if appropriate; see below.
-	*/
-	if (timei32 > 0 && ats[timei32] > INT32_MIN) {
-		--timei32;
-		++timecnt32;
-	}
-	while (leapcnt32 > 0 && !is32(trans[leapcnt32 - 1]))
+	while (0 < leapcnt32 && INT32_MAX < trans[leapcnt32 - 1])
 		--leapcnt32;
-	while (leapcnt32 > 0 && !is32(trans[leapi32])) {
+	while (0 < leapcnt32 && trans[leapi32] < INT32_MIN) {
 		--leapcnt32;
 		++leapi32;
 	}
@@ -1833,7 +1894,8 @@ writezone(const char *const name, const char *const string, char version)
 	for (pass = 1; pass <= 2; ++pass) {
 		register ptrdiff_t thistimei, thistimecnt, thistimelim;
 		register int	thisleapi, thisleapcnt, thisleaplim;
-		int		writetype[TZ_MAX_TYPES];
+		int old0;
+		char		omittype[TZ_MAX_TYPES];
 		int		typemap[TZ_MAX_TYPES];
 		register int	thistypecnt;
 		char		thischars[TZ_MAX_CHARS];
@@ -1858,25 +1920,18 @@ writezone(const char *const name, const char *const string, char version)
 		  error(_("too many transition times"));
 		thistimelim = thistimei + thistimecnt;
 		thisleaplim = thisleapi + thisleapcnt;
-		for (i = 0; i < typecnt; ++i)
-			writetype[i] = thistimecnt == timecnt;
-		if (thistimecnt == 0) {
-			/*
-			** No transition times fall in the current
-			** (32- or 64-bit) window.
-			*/
-			if (typecnt != 0)
-				writetype[typecnt - 1] = true;
-		} else {
-			for (i = thistimei - 1; i < thistimelim; ++i)
-				if (i >= 0)
-					writetype[types[i]] = true;
-			/*
-			** For America/Godthab and Antarctica/Palmer
-			*/
-			if (thistimei == 0)
-				writetype[0] = true;
-		}
+		memset(omittype, true, typecnt);
+		omittype[defaulttype] = false;
+		for (i = thistimei; i < thistimelim; i++)
+		  omittype[types[i]] = false;
+
+		/* Reorder types to make DEFAULTTYPE type 0.
+		   Use TYPEMAP to swap OLD0 and DEFAULTTYPE so that
+		   DEFAULTTYPE appears as type 0 in the output instead
+		   of OLD0.  TYPEMAP also omits unused types.  */
+		old0 = strlen(omittype);
+		swaptypes(old0, defaulttype);
+
 #ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
 		/*
 		** For some pre-2011 systems: if the last-to-be-written
@@ -1894,8 +1949,8 @@ writezone(const char *const name, const char *const string, char version)
 				if (isdsts[types[i]])
 					mrudst = types[i];
 				else	mrustd = types[i];
-			for (i = 0; i < typecnt; ++i)
-				if (writetype[i]) {
+			for (i = old0; i < typecnt; i++)
+				if (!omittype[i]) {
 					if (isdsts[i])
 						hidst = i;
 					else	histd = i;
@@ -1909,7 +1964,7 @@ writezone(const char *const name, const char *const string, char version)
 						ttisstds[mrudst],
 						ttisgmts[mrudst]);
 					isdsts[mrudst] = 1;
-					writetype[type] = true;
+					omittype[type] = false;
 			}
 			if (histd >= 0 && mrustd >= 0 && histd != mrustd &&
 				gmtoffs[histd] != gmtoffs[mrustd]) {
@@ -1920,20 +1975,24 @@ writezone(const char *const name, const char *const string, char version)
 						ttisstds[mrustd],
 						ttisgmts[mrustd]);
 					isdsts[mrustd] = 0;
-					writetype[type] = true;
+					omittype[type] = false;
 			}
 		}
 #endif /* !defined LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */
 		thistypecnt = 0;
-		for (i = 0; i < typecnt; ++i)
-			typemap[i] = writetype[i] ?  thistypecnt++ : -1;
+		for (i = old0; i < typecnt; i++)
+		  if (!omittype[i])
+		    typemap[i == old0 ? defaulttype
+			    : i == defaulttype ? old0 : i]
+		      = thistypecnt++;
+
 		for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
 			indmap[i] = -1;
 		thischarcnt = 0;
-		for (i = 0; i < typecnt; ++i) {
+		for (i = old0; i < typecnt; i++) {
 			register char *	thisabbr;
 
-			if (!writetype[i])
+			if (omittype[i])
 				continue;
 			if (indmap[abbrinds[i]] >= 0)
 				continue;
@@ -1982,8 +2041,8 @@ writezone(const char *const name, const char *const string, char version)
 			uc = typemap[types[i]];
 			fwrite(&uc, sizeof uc, 1, fp);
 		}
-		for (i = 0; i < typecnt; ++i)
-			if (writetype[i]) {
+		for (i = old0; i < typecnt; i++)
+			if (!omittype[i]) {
 				puttzcode(gmtoffs[i], fp);
 				putc(isdsts[i], fp);
 				putc((unsigned char) indmap[abbrinds[i]], fp);
@@ -2016,12 +2075,13 @@ writezone(const char *const name, const char *const string, char version)
 			else	puttzcode64(todo, fp);
 			puttzcode(corr[i], fp);
 		}
-		for (i = 0; i < typecnt; ++i)
-			if (writetype[i])
+		for (i = old0; i < typecnt; i++)
+			if (!omittype[i])
 				putc(ttisstds[i], fp);
-		for (i = 0; i < typecnt; ++i)
-			if (writetype[i])
+		for (i = old0; i < typecnt; i++)
+			if (!omittype[i])
 				putc(ttisgmts[i], fp);
+		swaptypes(old0, defaulttype);
 	}
 	fprintf(fp, "\n%s\n", string);
 	close_file(fp, directory, name);
@@ -2044,7 +2104,7 @@ abbroffset(char *buf, zic_t offset)
   minutes = offset % MINSPERHOUR;
   offset /= MINSPERHOUR;
   if (100 <= offset) {
-    error(_("%%z UTC offset magnitude exceeds 99:59:59"));
+    error(_("%%z UT offset magnitude exceeds 99:59:59"));
     return "%z";
   } else {
     char *p = buf;
@@ -2066,7 +2126,7 @@ abbroffset(char *buf, zic_t offset)
 
 static size_t
 doabbr(char *abbr, struct zone const *zp, char const *letters,
-       zic_t stdoff, bool doquotes)
+       bool isdst, zic_t stdoff, bool doquotes)
 {
 	register char *	cp;
 	register char *	slashp;
@@ -2081,7 +2141,7 @@ doabbr(char *abbr, struct zone const *zp, char const *letters,
 	  else if (!letters)
 	    letters = "%s";
 	  sprintf(abbr, format, letters);
-	} else if (stdoff != 0) {
+	} else if (isdst) {
 		strcpy(abbr, slashp + 1);
 	} else {
 		memcpy(abbr, format, slashp - format);
@@ -2192,7 +2252,7 @@ stringrule(char *result, const struct rule *const rp, const zic_t dstoff,
 	}
 	if (rp->r_todisgmt)
 		tod += gmtoff;
-	if (rp->r_todisstd && rp->r_stdoff == 0)
+	if (rp->r_todisstd && !rp->r_isdst)
 		tod += dstoff;
 	if (tod != 2 * SECSPERMIN * MINSPERHOUR) {
 		*result++ = '/';
@@ -2249,7 +2309,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 			continue;
 		if (rp->r_yrtype != NULL)
 			continue;
-		if (rp->r_stdoff == 0) {
+		if (!rp->r_isdst) {
 			if (stdrp == NULL)
 				stdrp = rp;
 			else	return -1;
@@ -2268,7 +2328,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		register struct rule *stdabbrrp = NULL;
 		for (i = 0; i < zp->z_nrules; ++i) {
 			rp = &zp->z_rules[i];
-			if (rp->r_stdoff == 0 && rule_cmp(stdabbrrp, rp) < 0)
+			if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0)
 				stdabbrrp = rp;
 			if (rule_cmp(stdrp, rp) < 0)
 				stdrp = rp;
@@ -2281,13 +2341,14 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 		if (stdrp != NULL && stdrp->r_hiyear == 2037)
 			return YEAR_BY_YEAR_ZONE;
 
-		if (stdrp != NULL && stdrp->r_stdoff != 0) {
+		if (stdrp != NULL && stdrp->r_isdst) {
 			/* Perpetual DST.  */
 			dstr.r_month = TM_JANUARY;
 			dstr.r_dycode = DC_DOM;
 			dstr.r_dayofmonth = 1;
 			dstr.r_tod = 0;
 			dstr.r_todisstd = dstr.r_todisgmt = false;
+			dstr.r_isdst = stdrp->r_isdst;
 			dstr.r_stdoff = stdrp->r_stdoff;
 			dstr.r_abbrvar = stdrp->r_abbrvar;
 			stdr.r_month = TM_DECEMBER;
@@ -2295,6 +2356,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 			stdr.r_dayofmonth = 31;
 			stdr.r_tod = SECSPERDAY + stdrp->r_stdoff;
 			stdr.r_todisstd = stdr.r_todisgmt = false;
+			stdr.r_isdst = false;
 			stdr.r_stdoff = 0;
 			stdr.r_abbrvar
 			  = (stdabbrrp ? stdabbrrp->r_abbrvar : "");
@@ -2302,10 +2364,10 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 			stdrp = &stdr;
 		}
 	}
-	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0))
+	if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst))
 		return -1;
 	abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar;
-	len = doabbr(result, zp, abbrvar, 0, true);
+	len = doabbr(result, zp, abbrvar, false, 0, true);
 	offsetlen = stringoffset(result + len, -zp->z_gmtoff);
 	if (! offsetlen) {
 		result[0] = '\0';
@@ -2314,7 +2376,8 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 	len += offsetlen;
 	if (dstrp == NULL)
 		return compat;
-	len += doabbr(result + len, zp, dstrp->r_abbrvar, dstrp->r_stdoff, true);
+	len += doabbr(result + len, zp, dstrp->r_abbrvar,
+		      dstrp->r_isdst, dstrp->r_stdoff, true);
 	if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR) {
 	  offsetlen = stringoffset(result + len,
 				   -(zp->z_gmtoff + dstrp->r_stdoff));
@@ -2372,6 +2435,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 	zic_t one = 1;
 	zic_t y2038_boundary = one << 31;
 	zic_t max_year0;
+	int defaulttype = -1;
 
 	max_abbr_len = 2 + max_format_len + max_abbrvar_len;
 	max_envvar_len = 2 * max_abbr_len + 5 * 9;
@@ -2480,9 +2544,9 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 		*/
 		stdoff = 0;
 		zp = &zpfirst[i];
-		usestart = i > 0 && (zp - 1)->z_untiltime > early_time;
+		usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
 		useuntil = i < (zonecount - 1);
-		if (useuntil && zp->z_untiltime <= early_time)
+		if (useuntil && zp->z_untiltime <= min_time)
 			continue;
 		gmtoff = zp->z_gmtoff;
 		eat(zp->z_filename, zp->z_linenum);
@@ -2490,14 +2554,15 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 		startoff = zp->z_gmtoff;
 		if (zp->z_nrules == 0) {
 			stdoff = zp->z_stdoff;
-			doabbr(startbuf, zp, NULL, stdoff, false);
+			doabbr(startbuf, zp, NULL, zp->z_isdst, stdoff, false);
 			type = addtype(oadd(zp->z_gmtoff, stdoff),
-				startbuf, stdoff != 0, startttisstd,
+				startbuf, zp->z_isdst, startttisstd,
 				startttisgmt);
 			if (usestart) {
 				addtt(starttime, type);
 				usestart = false;
-			} else	addtt(early_time, type);
+			} else
+				defaulttype = type;
 		} else for (year = min_year; year <= max_year; ++year) {
 			if (useuntil && year > zp->z_untilrule.r_hiyear)
 				break;
@@ -2588,6 +2653,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 							stdoff);
 						doabbr(startbuf, zp,
 							rp->r_abbrvar,
+							rp->r_isdst,
 							rp->r_stdoff,
 							false);
 						continue;
@@ -2598,6 +2664,7 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 							doabbr(startbuf,
 								zp,
 								rp->r_abbrvar,
+								rp->r_isdst,
 								rp->r_stdoff,
 								false);
 					}
@@ -2605,10 +2672,12 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 				eats(zp->z_filename, zp->z_linenum,
 					rp->r_filename, rp->r_linenum);
 				doabbr(ab, zp, rp->r_abbrvar,
-				       rp->r_stdoff, false);
+				       rp->r_isdst, rp->r_stdoff, false);
 				offset = oadd(zp->z_gmtoff, rp->r_stdoff);
-				type = addtype(offset, ab, rp->r_stdoff != 0,
+				type = addtype(offset, ab, rp->r_isdst,
 					rp->r_todisstd, rp->r_todisgmt);
+				if (defaulttype < 0 && !rp->r_isdst)
+				  defaulttype = type;
 				if (rp->r_hiyear == ZIC_MAX
 				    && ! (0 <= lastatmax
 					  && ktime < attypes[lastatmax].at))
@@ -2625,11 +2694,14 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 			eat(zp->z_filename, zp->z_linenum);
 			if (*startbuf == '\0')
 error(_("can't determine time zone abbreviation to use just after until time"));
-			else	addtt(starttime,
-					addtype(startoff, startbuf,
-						startoff != zp->z_gmtoff,
-						startttisstd,
-						startttisgmt));
+			else {
+			  bool isdst = startoff != zp->z_gmtoff;
+			  type = addtype(startoff, startbuf, isdst,
+					 startttisstd, startttisgmt);
+			  if (defaulttype < 0 && !isdst)
+			    defaulttype = type;
+			  addtt(starttime, type);
+			}
 		}
 		/*
 		** Now we may get to set starttime for the next zone line.
@@ -2644,6 +2716,8 @@ error(_("can't determine time zone abbreviation to use just after until time"));
 				starttime = tadd(starttime, -gmtoff);
 		}
 	}
+	if (defaulttype < 0)
+	  defaulttype = 0;
 	if (0 <= lastatmax)
 	  attypes[lastatmax].dontmerge = true;
 	if (do_extend) {
@@ -2671,7 +2745,7 @@ error(_("can't determine time zone abbreviation to use just after until time"));
 			attypes[timecnt - 1].dontmerge = true;
 		}
 	}
-	writezone(zpfirst->z_name, envvar, version);
+	writezone(zpfirst->z_name, envvar, version, defaulttype);
 	free(startbuf);
 	free(ab);
 	free(envvar);
@@ -2680,20 +2754,6 @@ error(_("can't determine time zone abbreviation to use just after until time"));
 static void
 addtt(zic_t starttime, int type)
 {
-	if (starttime <= early_time
-	    || (timecnt == 1 && attypes[0].at < early_time)) {
-		gmtoffs[0] = gmtoffs[type];
-		isdsts[0] = isdsts[type];
-		ttisstds[0] = ttisstds[type];
-		ttisgmts[0] = ttisgmts[type];
-		if (abbrinds[type] != 0)
-			strcpy(chars, &chars[abbrinds[type]]);
-		abbrinds[0] = 0;
-		charcnt = strlen(chars) + 1;
-		typecnt = 1;
-		timecnt = 0;
-		type = 0;
-	}
 	attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
 	attypes[timecnt].at = starttime;
 	attypes[timecnt].dontmerge = false;
@@ -2754,13 +2814,8 @@ leapadd(zic_t t, bool positive, int rolling, int count)
 		exit(EXIT_FAILURE);
 	}
 	for (i = 0; i < leapcnt; ++i)
-		if (t <= trans[i]) {
-			if (t == trans[i]) {
-				error(_("repeated leap second moment"));
-				exit(EXIT_FAILURE);
-			}
+		if (t <= trans[i])
 			break;
-		}
 	do {
 		for (j = leapcnt; j > i; --j) {
 			trans[j] = trans[j - 1];
@@ -2779,11 +2834,17 @@ adjleap(void)
 {
 	register int	i;
 	register zic_t	last = 0;
+	register 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;
 	}
@@ -2884,7 +2945,7 @@ lowerit(char a)
 }
 
 /* case-insensitive equality */
-static ATTRIBUTE_PURE bool
+static bool
 ciequal(register const char *ap, register const char *bp)
 {
 	while (lowerit(*ap) == lowerit(*bp++))
@@ -2893,7 +2954,7 @@ ciequal(register const char *ap, register const char *bp)
 	return false;
 }
 
-static ATTRIBUTE_PURE bool
+static bool
 itsabbr(register const char *abbr, register const char *word)
 {
 	if (lowerit(*abbr) != lowerit(*word))
@@ -2907,7 +2968,20 @@ itsabbr(register const char *abbr, register const char *word)
 	return true;
 }
 
-static ATTRIBUTE_PURE const struct lookup *
+/* 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)
 {
 	register const struct lookup *	foundlp;
@@ -2915,6 +2989,20 @@ 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.
 	*/
@@ -2926,11 +3014,25 @@ 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;
 }
 
@@ -3023,6 +3125,15 @@ rpytime(const struct rule *rp, zic_t wantedy)
 	dayoff = 0;
 	m = TM_JANUARY;
 	y = EPOCH_YEAR;
+	if (y < wantedy) {
+	  wantedy -= y;
+	  dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
+	  wantedy %= YEARSPERREPEAT;
+	  wantedy += y;
+	} else if (wantedy < 0) {
+	  dayoff = (wantedy / YEARSPERREPEAT) * (SECSPERREPEAT / SECSPERDAY);
+	  wantedy %= YEARSPERREPEAT;
+	}
 	while (wantedy != y) {
 		if (wantedy > y) {
 			i = len_years[isleap(y)];
@@ -3134,11 +3245,14 @@ 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 HAVE_DOS_FILE_NAMES
-	if (is_alpha(name[0]) && name[1] == ':')
-	  cp += 2;
-#endif
 	while (*cp == '/')
 	  cp++;