Patchwork Update timezone code from tzcode 2017b

login
register
mail settings
Submitter Joseph Myers
Date June 15, 2017, 10:32 p.m.
Message ID <alpine.DEB.2.20.1706152232270.8900@digraph.polyomino.org.uk>
Download mbox | patch
Permalink /patch/21041/
State New
Headers show

Comments

Joseph Myers - June 15, 2017, 10:32 p.m.
This patch updates files coming from tzcode to the versions in tzcode
2017b.  A couple of changes to other glibc code are needed.
time/tzset.c was using the SECSPERDAY macro from tzfile.h, which no
longer defines that macro, so a local definition is added to tzset.c.
Because timezone/private.h now defines the _ macro whenever
HAVE_GETTEXT is true, even if it was previously defined, it is also
necessary to avoid a conflict with the definition in
include/libintl.h.  Defining _ISOMAC is the obvious way to avoid such
internal definitions being visible, together with defining TZ_DOMAIN
so that zic and zdump continue to get the messages from the libc
domain as desired.  However, zic and zdump rely on PKGVERSION and
REPORT_BUGS_TO from config.h, which is not included by default with
_ISOMAC, so -include config.h needs adding to the options for these
programs as well.  Together those changes allow unmodified tzcode
2017b sources to work in glibc.

Tested for x86_64.

2017-06-15  Joseph Myers  <joseph@codesourcery.com>

	* timezone/private.h: Update from tzcode 2017b.
	* timezone/tzfile.h: Likewise.
	* timezone/tzselect.ksh: Likewise.
	* timezone/zdump.c: Likewise.
	* timezone/zic.c: Likewise.
	* timezone/Makefile (tz-cflags): Add -D_ISOMAC
	-DTZ_DOMAIN='"libc"' -include $(common-objpfx)config.h.
	* time/tzset.c (SECSPERDAY): New macro.
Paul Eggert - June 15, 2017, 11:59 p.m.
Thanks for doing all that. Those changes look good to me.

The intent is that glibc should be insulated from tzcode changes, in 
that glibc should be able to copy the latest source code from tzcode 
unaltered, without having to change any other source file in glibc. 
Obviously this doesn't work in areas where glibc assumes particular 
contents of timezone/private.h, an assumption that is unfortunately 
necessary in a few places. For what it's worth, looking at the planned 
(i.e., post-2017b) changes to tzcode's private.h, I don't see anything 
likely to cause problems in the next merge. If you can see any changes 
to the tzcode source files that could help avoid similar merge problems 
in the future, they'd be welcome, as I want the merge process to be as 
painless as practical.
Stan Shebs - June 16, 2017, 4:38 p.m.
On Thu, Jun 15, 2017 at 5:32 PM, Joseph Myers <joseph@codesourcery.com> wrote:
>
> This patch updates files coming from tzcode to the versions in tzcode
> 2017b.

So this blows up for me compiling with GCC 4.8.4 (yes yes I know, it's
what Google makes
me use), which doesn't like the _Generic in timezone/private.h.

Does it make sense to try to borrow the __HAVE_GENERIC_SELECTION added
for float128,
or is this more of a sign to start requiring 4.9 as the minimum?

Stan
Joseph Myers - June 16, 2017, 4:45 p.m.
On Fri, 16 Jun 2017, Stan Shebs wrote:

> On Thu, Jun 15, 2017 at 5:32 PM, Joseph Myers <joseph@codesourcery.com> wrote:
> >
> > This patch updates files coming from tzcode to the versions in tzcode
> > 2017b.
> 
> So this blows up for me compiling with GCC 4.8.4 (yes yes I know, it's 
> what Google makes me use), which doesn't like the _Generic in 
> timezone/private.h.
> 
> Does it make sense to try to borrow the __HAVE_GENERIC_SELECTION added 
> for float128, or is this more of a sign to start requiring 4.9 as the 
> minimum?

We don't want to have local changes to the files from tzcode.  Presumably 
this is from the -std=gnu11, which sets __STDC_VERSION__ but was 
incomplete in GCC 4.8.  I think requiring GCC >= 4.9 and binutils >= 2.25 
(the latter given internal linker errors on x86 with 2.24) is reasonable 
for building glibc.
Paul Eggert - June 16, 2017, 7:31 p.m.
Thanks for the bug report and diagnosis. To help avoid this problem in 
the future, I changed upstream's private.h to work around the GCC 4.8.4 
problem, and presumably this will make its way into glibc into due 
course. The patch is here:

http://mm.icann.org/pipermail/tz/2017-June/025163.html
Stefan Liebler - June 19, 2017, 11:59 a.m.
On 06/16/2017 06:45 PM, Joseph Myers wrote:
> On Fri, 16 Jun 2017, Stan Shebs wrote:
> 
>> On Thu, Jun 15, 2017 at 5:32 PM, Joseph Myers <joseph@codesourcery.com> wrote:
>>>
>>> This patch updates files coming from tzcode to the versions in tzcode
>>> 2017b.
>>
>> So this blows up for me compiling with GCC 4.8.4 (yes yes I know, it's
>> what Google makes me use), which doesn't like the _Generic in
>> timezone/private.h.
>>
>> Does it make sense to try to borrow the __HAVE_GENERIC_SELECTION added
>> for float128, or is this more of a sign to start requiring 4.9 as the
>> minimum?
> 
> We don't want to have local changes to the files from tzcode.  Presumably
> this is from the -std=gnu11, which sets __STDC_VERSION__ but was
> incomplete in GCC 4.8.  I think requiring GCC >= 4.9 and binutils >= 2.25
> (the latter given internal linker errors on x86 with 2.24) is reasonable
> for building glibc.
> 

As information:
I've also run into the same issue with _Generic on s390 with GCC 4.8.5.
Do you propose a patch which adds such a configure check?

Bye
Stefan

Patch

diff --git a/time/tzset.c b/time/tzset.c
index 8868e9a..cf5fe96 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -27,6 +27,8 @@ 
 
 #include <timezone/tzfile.h>
 
+#define SECSPERDAY 86400
+
 char *__tzname[2] = { (char *) "GMT", (char *) "GMT" };
 int __daylight = 0;
 long int __timezone = 0L;
diff --git a/timezone/Makefile b/timezone/Makefile
index 35e6a95..d6cc7ba 100644
--- a/timezone/Makefile
+++ b/timezone/Makefile
@@ -59,7 +59,8 @@  tz-cflags = -DTZDIR='"$(zonedir)"' \
 	    -DTZDEFAULT='"$(localtime-file)"' \
 	    -DTZDEFRULES='"$(posixrules-file)"' \
 	    -DTM_GMTOFF=tm_gmtoff -DTM_ZONE=tm_zone \
-	    -DHAVE_GETTEXT -DUSE_LTZ=0 -Wno-maybe-uninitialized
+	    -DHAVE_GETTEXT -DUSE_LTZ=0 -D_ISOMAC -DTZ_DOMAIN='"libc"' \
+	    -include $(common-objpfx)config.h -Wno-maybe-uninitialized
 
 # The -Wno-unused-variable flag is used to prevent GCC 6
 # from warning about time_t_min and time_t_max which are
diff --git a/timezone/private.h b/timezone/private.h
index 1c176e6..e2f23f5 100644
--- a/timezone/private.h
+++ b/timezone/private.h
@@ -15,6 +15,7 @@ 
 ** Thank you!
 */
 
+/* This string was in the Factory zone through version 2016f.  */
 #define GRANDPARENTED	"Local time zone must be set--see zic manual page"
 
 /*
@@ -22,6 +23,10 @@ 
 ** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'.
 */
 
+#ifndef HAVE_DECL_ASCTIME_R
+#define HAVE_DECL_ASCTIME_R 1
+#endif
+
 #ifndef HAVE_GETTEXT
 #define HAVE_GETTEXT		0
 #endif /* !defined HAVE_GETTEXT */
@@ -34,6 +39,10 @@ 
 #define HAVE_LINK		1
 #endif /* !defined HAVE_LINK */
 
+#ifndef HAVE_POSIX_DECLS
+#define HAVE_POSIX_DECLS 1
+#endif
+
 #ifndef HAVE_STRDUP
 #define HAVE_STRDUP 1
 #endif
@@ -69,9 +78,9 @@ 
 
 /* Enable tm_gmtoff and tm_zone on GNUish systems.  */
 #define _GNU_SOURCE 1
-/* Fix asctime_r on Solaris 10.  */
+/* Fix asctime_r on Solaris 11.  */
 #define _POSIX_PTHREAD_SEMANTICS 1
-/* Enable strtoimax on Solaris 10.  */
+/* Enable strtoimax on pre-C99 Solaris 11.  */
 #define __EXTENSIONS__ 1
 
 /*
@@ -95,23 +104,26 @@ 
 #undef tzalloc
 #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"
+#include <sys/types.h>	/* for time_t */
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>	/* for CHAR_BIT et al. */
+#include <stdlib.h>
 
-#include "errno.h"
+#include <errno.h>
 
 #ifndef ENAMETOOLONG
 # define ENAMETOOLONG EINVAL
 #endif
+#ifndef ENOTSUP
+# define ENOTSUP EINVAL
+#endif
 #ifndef EOVERFLOW
 # define EOVERFLOW EINVAL
 #endif
 
 #if HAVE_GETTEXT
-#include "libintl.h"
+#include <libintl.h>
 #endif /* HAVE_GETTEXT */
 
 #if HAVE_SYS_WAIT_H
@@ -126,7 +138,7 @@ 
 #endif /* !defined WEXITSTATUS */
 
 #if HAVE_UNISTD_H
-#include "unistd.h"	/* for F_OK, R_OK, and other POSIX goodness */
+#include <unistd.h>	/* for F_OK, R_OK, and other POSIX goodness */
 #endif /* HAVE_UNISTD_H */
 
 #ifndef HAVE_STRFTIME_L
@@ -149,19 +161,19 @@ 
 
 /*
 ** Define HAVE_STDINT_H's default value here, rather than at the
-** start, since __GLIBC__'s value depends on previously-included
-** files.
-** (glibc 2.1 and later have stdint.h, even with pre-C99 compilers.)
+** start, since __GLIBC__ and INTMAX_MAX's values depend on
+** previously-included files.  glibc 2.1 and Solaris 10 and later have
+** stdint.h, even with pre-C99 compilers.
 */
 #ifndef HAVE_STDINT_H
 #define HAVE_STDINT_H \
    (199901 <= __STDC_VERSION__ \
     || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__)	\
-    || __CYGWIN__)
+    || __CYGWIN__ || INTMAX_MAX)
 #endif /* !defined HAVE_STDINT_H */
 
 #if HAVE_STDINT_H
-#include "stdint.h"
+#include <stdint.h>
 #endif /* !HAVE_STDINT_H */
 
 #ifndef HAVE_INTTYPES_H
@@ -197,14 +209,18 @@  typedef long		int_fast64_t;
 # endif
 #endif
 
-#ifndef SCNdFAST64
+#ifndef PRIdFAST64
 # if INT_FAST64_MAX == LLONG_MAX
-#  define SCNdFAST64 "lld"
+#  define PRIdFAST64 "lld"
 # else
-#  define SCNdFAST64 "ld"
+#  define PRIdFAST64 "ld"
 # endif
 #endif
 
+#ifndef SCNdFAST64
+# define SCNdFAST64 PRIdFAST64
+#endif
+
 #ifndef INT_FAST32_MAX
 # if INT_MAX >> 31 == 0
 typedef long int_fast32_t;
@@ -304,6 +320,13 @@  typedef unsigned long uintmax_t;
 ** Workarounds for compilers/systems.
 */
 
+#ifndef EPOCH_LOCAL
+# define EPOCH_LOCAL 0
+#endif
+#ifndef EPOCH_OFFSET
+# define EPOCH_OFFSET 0
+#endif
+
 /*
 ** Compile with -Dtime_tz=T to build the tz package with a private
 ** time_t type equivalent to T rather than the system-supplied time_t.
@@ -311,7 +334,7 @@  typedef unsigned long uintmax_t;
 ** (e.g., time_t wider than 'long', or unsigned time_t) even on
 ** typical platforms.
 */
-#ifdef time_tz
+#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0
 # ifdef LOCALTIME_IMPLEMENTATION
 static time_t sys_time(time_t *x) { return time(x); }
 # endif
@@ -379,25 +402,21 @@  time_t time(time_t *);
 void tzset(void);
 #endif
 
-/*
-** Some time.h implementations don't declare asctime_r.
-** Others might define it as a macro.
-** Fix the former without affecting the latter.
-** Similarly for timezone, daylight, and altzone.
-*/
-
-#ifndef asctime_r
-extern char *	asctime_r(struct tm const *restrict, char *restrict);
+#if !HAVE_DECL_ASCTIME_R && !defined asctime_r
+extern char *asctime_r(struct tm const *restrict, char *restrict);
 #endif
 
-#ifdef USG_COMPAT
-# ifndef timezone
+#if !HAVE_POSIX_DECLS
+# ifdef USG_COMPAT
+#  ifndef timezone
 extern long timezone;
-# endif
-# ifndef daylight
+#  endif
+#  ifndef daylight
 extern int daylight;
+#  endif
 # endif
 #endif
+
 #if defined ALTZONE && !defined altzone
 extern long altzone;
 #endif
@@ -481,14 +500,8 @@  time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
 # include <stdbool.h>
 #endif
 
-#ifndef TYPE_BIT
 #define TYPE_BIT(type)	(sizeof (type) * CHAR_BIT)
-#endif /* !defined TYPE_BIT */
-
-#ifndef TYPE_SIGNED
 #define TYPE_SIGNED(type) (((type) -1) < 0)
-#endif /* !defined TYPE_SIGNED */
-
 #define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0)
 
 /* Max and min values of the integer type T, of which only the bottom
@@ -500,11 +513,29 @@  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 assumes no padding.  */
+/* 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);
+#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));
+#endif
 
-#ifndef INT_STRLEN_MAXIMUM
 /*
 ** 302 / 1000 is log10(2.0) rounded up.
 ** Subtract one for the sign bit if the type is signed;
@@ -514,7 +545,6 @@  static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t));
 #define INT_STRLEN_MAXIMUM(type) \
 	((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
 	1 + TYPE_SIGNED(type))
-#endif /* !defined INT_STRLEN_MAXIMUM */
 
 /*
 ** INITIALIZE(x)
@@ -536,13 +566,11 @@  static time_t const time_t_max = MAXVAL(time_t, TYPE_BIT(time_t));
 ** The default is to use gettext if available, and use MSGID otherwise.
 */
 
-#ifndef _
 #if HAVE_GETTEXT
 #define _(msgid) gettext(msgid)
 #else /* !HAVE_GETTEXT */
 #define _(msgid) msgid
 #endif /* !HAVE_GETTEXT */
-#endif /* !defined _ */
 
 #if !defined TZ_DOMAIN && defined HAVE_GETTEXT
 # define TZ_DOMAIN "tz"
@@ -555,24 +583,70 @@  char *asctime_r(struct tm const *, char *);
 char *ctime_r(time_t const *, char *);
 #endif /* HAVE_INCOMPATIBLE_CTIME_R */
 
-#ifndef YEARSPERREPEAT
+/* Handy macros that are independent of tzfile implementation.  */
+
 #define YEARSPERREPEAT		400	/* years before a Gregorian repeat */
-#endif /* !defined YEARSPERREPEAT */
+
+#define SECSPERMIN	60
+#define MINSPERHOUR	60
+#define HOURSPERDAY	24
+#define DAYSPERWEEK	7
+#define DAYSPERNYEAR	365
+#define DAYSPERLYEAR	366
+#define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
+#define SECSPERDAY	((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
+#define MONSPERYEAR	12
+
+#define TM_SUNDAY	0
+#define TM_MONDAY	1
+#define TM_TUESDAY	2
+#define TM_WEDNESDAY	3
+#define TM_THURSDAY	4
+#define TM_FRIDAY	5
+#define TM_SATURDAY	6
+
+#define TM_JANUARY	0
+#define TM_FEBRUARY	1
+#define TM_MARCH	2
+#define TM_APRIL	3
+#define TM_MAY		4
+#define TM_JUNE		5
+#define TM_JULY		6
+#define TM_AUGUST	7
+#define TM_SEPTEMBER	8
+#define TM_OCTOBER	9
+#define TM_NOVEMBER	10
+#define TM_DECEMBER	11
+
+#define TM_YEAR_BASE	1900
+
+#define EPOCH_YEAR	1970
+#define EPOCH_WDAY	TM_THURSDAY
+
+#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
 
 /*
-** The Gregorian year averages 365.2425 days, which is 31556952 seconds.
+** Since everything in isleap is modulo 400 (or a factor of 400), we know that
+**	isleap(y) == isleap(y % 400)
+** and so
+**	isleap(a + b) == isleap((a + b) % 400)
+** 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).
+** We use this to avoid addition overflow problems.
 */
 
-#ifndef AVGSECSPERYEAR
-#define AVGSECSPERYEAR		31556952L
-#endif /* !defined AVGSECSPERYEAR */
+#define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
+
 
-#ifndef SECSPERREPEAT
-#define SECSPERREPEAT		((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR)
-#endif /* !defined SECSPERREPEAT */
+/*
+** The Gregorian year averages 365.2425 days, which is 31556952 seconds.
+*/
 
-#ifndef SECSPERREPEAT_BITS
+#define AVGSECSPERYEAR		31556952L
+#define SECSPERREPEAT \
+  ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR)
 #define SECSPERREPEAT_BITS	34	/* ceil(log2(SECSPERREPEAT)) */
-#endif /* !defined SECSPERREPEAT_BITS */
 
 #endif /* !defined PRIVATE_H */
diff --git a/timezone/tzfile.h b/timezone/tzfile.h
index ebecd68..0e51dce 100644
--- a/timezone/tzfile.h
+++ b/timezone/tzfile.h
@@ -114,56 +114,4 @@  struct tzhead {
 #define TZ_MAX_LEAPS	50	/* Maximum number of leap second corrections */
 #endif /* !defined TZ_MAX_LEAPS */
 
-#define SECSPERMIN	60
-#define MINSPERHOUR	60
-#define HOURSPERDAY	24
-#define DAYSPERWEEK	7
-#define DAYSPERNYEAR	365
-#define DAYSPERLYEAR	366
-#define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
-#define SECSPERDAY	((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
-#define MONSPERYEAR	12
-
-#define TM_SUNDAY	0
-#define TM_MONDAY	1
-#define TM_TUESDAY	2
-#define TM_WEDNESDAY	3
-#define TM_THURSDAY	4
-#define TM_FRIDAY	5
-#define TM_SATURDAY	6
-
-#define TM_JANUARY	0
-#define TM_FEBRUARY	1
-#define TM_MARCH	2
-#define TM_APRIL	3
-#define TM_MAY		4
-#define TM_JUNE		5
-#define TM_JULY		6
-#define TM_AUGUST	7
-#define TM_SEPTEMBER	8
-#define TM_OCTOBER	9
-#define TM_NOVEMBER	10
-#define TM_DECEMBER	11
-
-#define TM_YEAR_BASE	1900
-
-#define EPOCH_YEAR	1970
-#define EPOCH_WDAY	TM_THURSDAY
-
-#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
-
-/*
-** Since everything in isleap is modulo 400 (or a factor of 400), we know that
-**	isleap(y) == isleap(y % 400)
-** and so
-**	isleap(a + b) == isleap((a + b) % 400)
-** 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).
-** We use this to avoid addition overflow problems.
-*/
-
-#define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
-
 #endif /* !defined TZFILE_H */
diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh
index 2c3b2f4..d2c3a6d 100755
--- a/timezone/tzselect.ksh
+++ b/timezone/tzselect.ksh
@@ -7,7 +7,7 @@  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.
+# Contributed by Paul Eggert.  This file is in the public domain.
 
 # Porting notes:
 #
@@ -346,11 +346,14 @@  while
 				'that is 10 hours ahead (east) of UTC.'
 			read TZ
 			$AWK -v TZ="$TZ" 'BEGIN {
-				tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
-				time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
+				tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})"
+				time = "(2[0-4]|[0-1]?[0-9])" \
+				  "(:[0-5][0-9](:[0-5][0-9])?)?"
 				offset = "[-+]?" time
-				date = "(J?[0-9]+|M[0-9]+\\.[0-9]+\\.[0-9]+)"
-				datetime = "," date "(/" time ")?"
+				mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
+				jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \
+				  "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])"
+				datetime = ",(" mdate "|" jdate ")(/" time ")?"
 				tzpattern = "^(:.*|" tzname offset "(" tzname \
 				  "(" offset ")?(" datetime datetime ")?)?)$"
 				if (TZ ~ tzpattern) exit 1
@@ -509,7 +512,7 @@  while
 		case $TZsec in
 		$UTsec)
 			extra_info="
-Local time is now:	$TZdate.
+Selected time is now:	$TZdate.
 Universal Time is now:	$UTdate."
 			break
 		esac
@@ -545,7 +548,7 @@  case $SHELL in
 *) file=.profile line="TZ='$TZ'; export TZ"
 esac
 
-say >&2 "
+test -t 1 && say >&2 "
 You can make this change permanent for yourself by appending the line
 	$line
 to the file '$file' in your home directory; then log out and log in again.
diff --git a/timezone/zdump.c b/timezone/zdump.c
index 063a263..bf75800 100644
--- a/timezone/zdump.c
+++ b/timezone/zdump.c
@@ -19,89 +19,7 @@ 
 # define USE_LTZ 1
 #endif
 
-#if USE_LTZ
-# include "private.h"
-#endif
-
-/* Enable tm_gmtoff and tm_zone on GNUish systems.  */
-#define _GNU_SOURCE 1
-/* Enable strtoimax on Solaris 10.  */
-#define __EXTENSIONS__ 1
-
-#include "stdio.h"	/* for stdout, stderr, perror */
-#include "string.h"	/* for strcpy */
-#include "sys/types.h"	/* for time_t */
-#include "time.h"	/* for struct tm */
-#include "stdlib.h"	/* for exit, malloc, atoi */
-#include "limits.h"	/* for CHAR_BIT, LLONG_MAX */
-#include <errno.h>
-
-/*
-** Substitutes for pre-C99 compilers.
-** Much of this section of code is stolen from private.h.
-*/
-
-#ifndef HAVE_STDINT_H
-# define HAVE_STDINT_H \
-    (199901 <= __STDC_VERSION__ \
-     || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__)	\
-     || __CYGWIN__)
-#endif
-#if HAVE_STDINT_H
-# include "stdint.h"
-#endif
-#ifndef HAVE_INTTYPES_H
-# define HAVE_INTTYPES_H HAVE_STDINT_H
-#endif
-#if HAVE_INTTYPES_H
-# include <inttypes.h>
-#endif
-
-#ifndef INT_FAST32_MAX
-# if INT_MAX >> 31 == 0
-typedef long int_fast32_t;
-# else
-typedef int int_fast32_t;
-# endif
-#endif
-
-/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX.  */
-#if !defined LLONG_MAX && defined __LONG_LONG_MAX__
-# define LLONG_MAX __LONG_LONG_MAX__
-#endif
-
-#ifndef INTMAX_MAX
-# ifdef LLONG_MAX
-typedef long long intmax_t;
-#  define strtoimax strtoll
-#  define INTMAX_MAX LLONG_MAX
-# else
-typedef long intmax_t;
-#  define strtoimax strtol
-#  define INTMAX_MAX LONG_MAX
-# endif
-#endif
-
-#ifndef PRIdMAX
-# if INTMAX_MAX == LLONG_MAX
-#  define PRIdMAX "lld"
-# else
-#  define PRIdMAX "ld"
-# endif
-#endif
-
-/* Infer TM_ZONE on systems where this information is known, but suppress
-   guessing if NO_TM_ZONE is defined.  Similarly for TM_GMTOFF.  */
-#if (defined __GLIBC__ \
-     || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
-     || (defined __APPLE__ && defined __MACH__))
-# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF
-#  define TM_GMTOFF tm_gmtoff
-# endif
-# if !defined TM_ZONE && !defined NO_TM_ZONE
-#  define TM_ZONE tm_zone
-# endif
-#endif
+#include "private.h"
 
 #ifndef HAVE_LOCALTIME_R
 # define HAVE_LOCALTIME_R 1
@@ -131,62 +49,6 @@  typedef long intmax_t;
 #define MAX_STRING_LENGTH	1024
 #endif /* !defined MAX_STRING_LENGTH */
 
-#if __STDC_VERSION__ < 199901
-# define true 1
-# define false 0
-# define bool int
-#else
-# include <stdbool.h>
-#endif
-
-#ifndef EXIT_SUCCESS
-#define EXIT_SUCCESS	0
-#endif /* !defined EXIT_SUCCESS */
-
-#ifndef EXIT_FAILURE
-#define EXIT_FAILURE	1
-#endif /* !defined EXIT_FAILURE */
-
-#ifndef SECSPERMIN
-#define SECSPERMIN	60
-#endif /* !defined SECSPERMIN */
-
-#ifndef MINSPERHOUR
-#define MINSPERHOUR	60
-#endif /* !defined MINSPERHOUR */
-
-#ifndef SECSPERHOUR
-#define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
-#endif /* !defined SECSPERHOUR */
-
-#ifndef HOURSPERDAY
-#define HOURSPERDAY	24
-#endif /* !defined HOURSPERDAY */
-
-#ifndef EPOCH_YEAR
-#define EPOCH_YEAR	1970
-#endif /* !defined EPOCH_YEAR */
-
-#ifndef TM_YEAR_BASE
-#define TM_YEAR_BASE	1900
-#endif /* !defined TM_YEAR_BASE */
-
-#ifndef DAYSPERNYEAR
-#define DAYSPERNYEAR	365
-#endif /* !defined DAYSPERNYEAR */
-
-#ifndef isleap
-#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
-#endif /* !defined isleap */
-
-#ifndef isleap_sum
-/*
-** See tzfile.h for details on isleap_sum.
-*/
-#define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
-#endif /* !defined isleap_sum */
-
-#define SECSPERDAY	((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
 #define SECSPERNYEAR	(SECSPERDAY * DAYSPERNYEAR)
 #define SECSPERLYEAR	(SECSPERNYEAR + SECSPERDAY)
 #define SECSPER400YEARS	(SECSPERNYEAR * (intmax_t) (300 + 3)	\
@@ -201,49 +63,24 @@  typedef long intmax_t;
 */
 enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
 
-#ifndef HAVE_GETTEXT
-#define HAVE_GETTEXT 0
-#endif
 #if HAVE_GETTEXT
-#include "locale.h"	/* for setlocale */
-#include "libintl.h"
+#include <locale.h>	/* for setlocale */
 #endif /* HAVE_GETTEXT */
 
-#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__)
-# define ATTRIBUTE_PURE __attribute__ ((__pure__))
-#else
-# define ATTRIBUTE_PURE /* empty */
-#endif
-
-/*
-** For the benefit of GNU folk...
-** '_(MSGID)' uses the current locale's message library string for MSGID.
-** The default is to use gettext if available, and use MSGID otherwise.
-*/
-
-#ifndef _
-#if HAVE_GETTEXT
-#define _(msgid) gettext(msgid)
-#else /* !HAVE_GETTEXT */
-#define _(msgid) msgid
-#endif /* !HAVE_GETTEXT */
-#endif /* !defined _ */
-
-#if !defined TZ_DOMAIN && defined HAVE_GETTEXT
-# define TZ_DOMAIN "tz"
-#endif
-
 #if ! HAVE_LOCALTIME_RZ
 # undef  timezone_t
 # 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[2];
+extern char *	tzname[];
+#endif
 
 /* The minimum and maximum finite time values.  */
 enum { atime_shift = CHAR_BIT * sizeof (time_t) - 2 };
@@ -266,6 +103,8 @@  static intmax_t	delta(struct tm *, struct tm *) ATTRIBUTE_PURE;
 static void dumptime(struct tm const *);
 static time_t hunt(timezone_t, char *, time_t, time_t);
 static void show(timezone_t, char *, time_t, bool);
+static void showtrans(char const *, struct tm const *, time_t, char const *,
+		      char const *);
 static const char *tformat(void);
 static time_t yeartot(intmax_t) ATTRIBUTE_PURE;
 
@@ -303,6 +142,19 @@  sumsize(size_t a, size_t b)
   return sum;
 }
 
+/* Return a pointer to a newly allocated buffer of size SIZE, exiting
+   on failure.  SIZE should be nonzero.  */
+static void *
+xmalloc(size_t size)
+{
+  void *p = malloc(size);
+  if (!p) {
+    perror(progname);
+    exit(EXIT_FAILURE);
+  }
+  return p;
+}
+
 #if ! HAVE_TZSET
 # undef tzset
 # define tzset zdump_tzset
@@ -390,21 +242,13 @@  tzalloc(char const *val)
 
     while (*e++)
       continue;
-    env = malloc(sumsize(sizeof *environ,
-			 (e - environ) * sizeof *environ));
-    if (! env) {
-      perror(progname);
-      exit(EXIT_FAILURE);
-    }
+    env = xmalloc(sumsize(sizeof *environ,
+			  (e - environ) * sizeof *environ));
     to = 1;
     for (e = environ; (env[to] = *e); e++)
       to += strncmp(*e, "TZ=", 3) != 0;
   }
-  env0 = malloc(sumsize(sizeof "TZ=", strlen(val)));
-  if (! env0) {
-    perror(progname);
-    exit(EXIT_FAILURE);
-  }
+  env0 = xmalloc(sumsize(sizeof "TZ=", strlen(val)));
   env[0] = strcat(strcpy(env0, "TZ="), val);
   environ = fakeenv = env;
   tzset();
@@ -525,11 +369,7 @@  saveabbr(char **buf, size_t *bufalloc, struct tm const *tmp)
 	 to avoid O(N**2) behavior on repeated calls.  */
       *bufalloc = sumsize(*bufalloc, ablen + 1);
 
-      *buf = malloc(*bufalloc);
-      if (! *buf) {
-	perror(progname);
-	exit(EXIT_FAILURE);
-      }
+      *buf = xmalloc(*bufalloc);
     }
     return strcpy(*buf, ab);
   }
@@ -550,10 +390,18 @@  static void
 usage(FILE * const stream, const int status)
 {
 	fprintf(stream,
-_("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n"
+_("%s: usage: %s OPTIONS ZONENAME ...\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"
+  "  -i         List transitions briefly (format is experimental)\n" \
+  "  -v         List transitions verbosely\n"
+  "  -V         List transitions a bit less verbosely\n"
+  "  --help     Output this help\n"
+  "  --version  Output version info\n"
   "\n"
   "Report bugs to %s.\n"),
-		       progname, progname, REPORT_BUGS_TO);
+		progname, progname, REPORT_BUGS_TO);
 	if (status == EXIT_SUCCESS)
 	  close_file(stream);
 	exit(status);
@@ -565,7 +413,6 @@  main(int argc, char *argv[])
 	/* These are static so that they're initially zero.  */
 	static char *		abbrev;
 	static size_t		abbrevsize;
-	static struct tm	newtm;
 
 	register int		i;
 	register bool		vflag;
@@ -575,11 +422,7 @@  main(int argc, char *argv[])
 	register time_t		cutlotime;
 	register time_t		cuthitime;
 	time_t			now;
-	time_t			t;
-	time_t			newt;
-	struct tm		tm;
-	register struct tm *	tmp;
-	register struct tm *	newtmp;
+	bool iflag = false;
 
 	cutlotime = absolute_min_time;
 	cuthitime = absolute_max_time;
@@ -601,9 +444,10 @@  main(int argc, char *argv[])
 	vflag = Vflag = false;
 	cutarg = cuttimes = NULL;
 	for (;;)
-	  switch (getopt(argc, argv, "c:t:vV")) {
+	  switch (getopt(argc, argv, "c:it:vV")) {
 	  case 'c': cutarg = optarg; break;
 	  case 't': cuttimes = optarg; break;
+	  case 'i': iflag = true; break;
 	  case 'v': vflag = true; break;
 	  case 'V': Vflag = true; break;
 	  case -1:
@@ -615,7 +459,7 @@  main(int argc, char *argv[])
 	  }
  arg_processing_done:;
 
-	if (vflag | Vflag) {
+	if (iflag | vflag | Vflag) {
 		intmax_t	lo;
 		intmax_t	hi;
 		char *loend, *hiend;
@@ -672,7 +516,9 @@  main(int argc, char *argv[])
 		}
 	}
 	gmtzinit();
-	now = time(NULL);
+	INITIALIZE (now);
+	if (! (iflag | vflag | Vflag))
+	  now = time(NULL);
 	longest = 0;
 	for (i = optind; i < argc; i++) {
 	  size_t arglen = strlen(argv[i]);
@@ -683,48 +529,66 @@  main(int argc, char *argv[])
 	for (i = optind; i < argc; ++i) {
 		timezone_t tz = tzalloc(argv[i]);
 		char const *ab;
+		time_t t;
+		struct tm tm, newtm;
+		bool tm_ok;
 		if (!tz) {
 		  perror(argv[i]);
 		  return EXIT_FAILURE;
 		}
-		if (! (vflag | Vflag)) {
+		if (! (iflag | vflag | Vflag)) {
 			show(tz, argv[i], now, false);
 			tzfree(tz);
 			continue;
 		}
 		warned = false;
 		t = absolute_min_time;
-		if (!Vflag) {
+		if (! (iflag | Vflag)) {
 			show(tz, argv[i], t, true);
 			t += SECSPERDAY;
 			show(tz, argv[i], t, true);
 		}
 		if (t < cutlotime)
 			t = cutlotime;
-		tmp = my_localtime_rz(tz, &t, &tm);
-		if (tmp)
+		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
+		if (tm_ok) {
 		  ab = saveabbr(&abbrev, &abbrevsize, &tm);
+		  if (iflag) {
+		    showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
+		    showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
+		  }
+		}
 		while (t < cuthitime) {
-			newt = ((t < absolute_max_time - SECSPERDAY / 2
-				 && t + SECSPERDAY / 2 < cuthitime)
-				? t + SECSPERDAY / 2
-				: cuthitime);
-			newtmp = localtime_rz(tz, &newt, &newtm);
-			if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
-				(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);
-					if (newtmp)
-					  ab = saveabbr(&abbrev, &abbrevsize,
-							&newtm);
-			}
-			t = newt;
-			tm = newtm;
-			tmp = newtmp;
+		  time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
+				  && t + SECSPERDAY / 2 < cuthitime)
+				 ? t + SECSPERDAY / 2
+				 : 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)) {
+		    newt = hunt(tz, argv[i], t, newt);
+		    newtmp = localtime_rz(tz, &newt, &newtm);
+		    newtm_ok = newtmp != NULL;
+		    if (iflag)
+		      showtrans("%Y-%m-%d\t%L\t%Q", newtmp, newt,
+				newtm_ok ? abbr(&newtm) : NULL, argv[i]);
+		    else {
+		      show(tz, argv[i], newt - 1, true);
+		      show(tz, argv[i], newt, true);
+		    }
+		  }
+		  t = newt;
+		  tm_ok = newtm_ok;
+		  if (newtm_ok) {
+		    ab = saveabbr(&abbrev, &abbrevsize, &newtm);
+		    tm = newtm;
+		  }
 		}
-		if (!Vflag) {
+		if (! (iflag | Vflag)) {
 			t = absolute_max_time;
 			t -= SECSPERDAY;
 			show(tz, argv[i], t, true);
@@ -790,12 +654,11 @@  hunt(timezone_t tz, char *name, time_t lot, time_t hit)
 	char const *		ab;
 	time_t			t;
 	struct tm		lotm;
-	register struct tm *	lotmp;
 	struct tm		tm;
-	register struct tm *	tmp;
+	bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
+	bool tm_ok;
 
-	lotmp = my_localtime_rz(tz, &lot, &lotm);
-	if (lotmp)
+	if (lotm_ok)
 	  ab = saveabbr(&loab, &loabsize, &lotm);
 	for ( ; ; ) {
 		time_t diff = hit - lot;
@@ -807,18 +670,17 @@  hunt(timezone_t tz, char *name, time_t lot, time_t hit)
 			++t;
 		else if (t >= hit)
 			--t;
-		tmp = my_localtime_rz(tz, &t, &tm);
-		if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
-			(delta(&tm, &lotm) == (t - lot) &&
-			tm.tm_isdst == lotm.tm_isdst &&
-			strcmp(abbr(&tm), ab) == 0)) {
-				lot = t;
-				lotm = tm;
-				lotmp = tmp;
+		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
+		if (lotm_ok & tm_ok
+		    ? (delta(&tm, &lotm) == t - lot
+		       && tm.tm_isdst == lotm.tm_isdst
+		       && strcmp(abbr(&tm), ab) == 0)
+		    : lotm_ok == tm_ok) {
+		  lot = t;
+		  if (tm_ok)
+		    lotm = tm;
 		} else	hit = t;
 	}
-	show(tz, name, lot, true);
-	show(tz, name, hit, true);
 	return hit;
 }
 
@@ -862,13 +724,20 @@  adjusted_yday(struct tm const *a, struct tm const *b)
 
 /* 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
-   offsets are east of Greenwich.  On failure, return LONG_MIN.  */
+   offsets are east of Greenwich.  On failure, return LONG_MIN.
+
+   If T is nonnull, *T is the timestamp that corresponds to A; call
+   my_gmtime_r and use its result instead of B.  Otherwise, B is the
+   possibly nonnull result of an earlier call to my_gmtime_r.  */
 static long
-gmtoff(struct tm const *a, struct tm const *b)
+gmtoff(struct tm const *a, time_t *t, struct tm const *b)
 {
 #ifdef TM_GMTOFF
   return a->TM_GMTOFF;
 #else
+  struct tm tm;
+  if (t)
+    b = my_gmtime_r(t, &tm);
   if (! b)
     return LONG_MIN;
   else {
@@ -907,7 +776,7 @@  show(timezone_t tz, char *zone, time_t t, bool v)
 		if (*abbr(tmp) != '\0')
 			printf(" %s", abbr(tmp));
 		if (v) {
-			long off = gmtoff(tmp, gmtmp);
+			long off = gmtoff(tmp, NULL, gmtmp);
 			printf(" isdst=%d", tmp->tm_isdst);
 			if (off != LONG_MIN)
 			  printf(" gmtoff=%ld", off);
@@ -918,6 +787,206 @@  show(timezone_t tz, char *zone, time_t t, bool v)
 		abbrok(abbr(tmp), zone);
 }
 
+/* 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.
+
+   Return the length of the resulting string.  If the string does not
+   fit, return the length that the string would have been if it had
+   fit; do not overrun the output buffer.  */
+static int
+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)
+	  : mm
+	  ? snprintf(buf, size, "%02d:%02d", hh, mm)
+	  : snprintf(buf, size, "%02d", hh));
+}
+
+/* Store into BUF, of size SIZE, a formatted UTC 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
+   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.
+
+   Return the length of the resulting string, or -1 if the result is
+   not representable as a string.  If the string does not fit, return
+   the length that the string would have been if it had fit; do not
+   overrun the output buffer.  */
+static int
+format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
+{
+  long off = gmtoff(tm, &t, NULL);
+  char sign = ((off < 0
+		|| (off == 0
+		    && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
+	       ? '-' : '+');
+  long hh;
+  int mm, ss;
+  if (off < 0)
+    {
+      if (off == LONG_MIN)
+	return -1;
+      off = -off;
+    }
+  ss = off % 60;
+  mm = off / 60 % 60;
+  hh = off / 60 / 60;
+  return (ss || 100 <= hh
+	  ? 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));
+}
+
+/* Store into BUF (of size SIZE) a quoted string representation of P.
+   If the representation's length is less than SIZE, return the
+   length; the representation is not null terminated.  Otherwise
+   return SIZE, to indicate that BUF is too small.  */
+static size_t
+format_quoted_string(char *buf, size_t size, char const *p)
+{
+  char *b = buf;
+  size_t s = size;
+  if (!s)
+    return size;
+  *b++ = '"', s--;
+  for (;;) {
+    char c = *p++;
+    if (s <= 1)
+      return size;
+    switch (c) {
+    default: *b++ = c, s--; continue;
+    case '\0': *b++ = '"', s--; return size - s;
+    case '"': case '\\': break;
+    case ' ': c = 's'; break;
+    case '\f': c = 'f'; break;
+    case '\n': c = 'n'; break;
+    case '\r': c = 'r'; break;
+    case '\t': c = 't'; break;
+    case '\v': c = 'v'; break;
+    }
+    *b++ = '\\', *b++ = c, s -= 2;
+  }
+}
+
+/* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
+   TM is the broken-down time, T the seconds count, AB the time zone
+   abbreviation, and ZONE_NAME the zone name.  Return true if
+   successful, false if the output would require more than SIZE bytes.
+   TIME_FMT uses the same format that strftime uses, with these
+   additions:
+
+   %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
+      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.  */
+
+static bool
+istrftime(char *buf, size_t size, char const *time_fmt,
+	  struct tm const *tm, time_t t, char const *ab, char const *zone_name)
+{
+  char *b = buf;
+  size_t s = size;
+  char const *f = time_fmt, *p;
+
+  for (p = f; ; p++)
+    if (*p == '%' && p[1] == '%')
+      p++;
+    else if (!*p
+	     || (*p == '%'
+		 && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
+      size_t formatted_len;
+      size_t f_prefix_len = p - f;
+      size_t f_prefix_copy_size = p - f + 2;
+      char fbuf[100];
+      bool oversized = sizeof fbuf <= f_prefix_copy_size;
+      char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
+      memcpy(f_prefix_copy, f, f_prefix_len);
+      strcpy(f_prefix_copy + f_prefix_len, "X");
+      formatted_len = strftime(b, s, f_prefix_copy, tm);
+      if (oversized)
+	free(f_prefix_copy);
+      if (formatted_len == 0)
+	return false;
+      formatted_len--;
+      b += formatted_len, s -= formatted_len;
+      if (!*p++)
+	break;
+      switch (*p) {
+      case 'f':
+	formatted_len = format_quoted_string(b, s, zone_name);
+	break;
+      case 'L':
+	formatted_len = format_local_time(b, s, tm);
+	break;
+      case 'Q':
+	{
+	  bool show_abbr;
+	  int offlen = format_utc_offset(b, s, tm, t);
+	  if (! (0 <= offlen && offlen < s))
+	    return false;
+	  show_abbr = strcmp(b, ab) != 0;
+	  b += offlen, s -= offlen;
+	  if (show_abbr) {
+	    char const *abp;
+	    size_t len;
+	    if (s <= 1)
+	      return false;
+	    *b++ = '\t', s--;
+	    for (abp = ab; is_alpha(*abp); abp++)
+	      continue;
+	    len = (!*abp && *ab
+		   ? 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);
+	}
+	break;
+      }
+      if (s <= formatted_len)
+	return false;
+      b += formatted_len, s -= formatted_len;
+      f = p + 1;
+    }
+  *b = '\0';
+  return true;
+}
+
+/* Show a time transition.  */
+static void
+showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
+	  char const *zone_name)
+{
+  if (!tm) {
+    printf(tformat(), t);
+    putchar('\n');
+  } else {
+    char stackbuf[1000];
+    size_t size = sizeof stackbuf;
+    char *buf = stackbuf;
+    char *bufalloc = NULL;
+    while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
+      size = sumsize(size, size);
+      free(bufalloc);
+      buf = bufalloc = xmalloc(size);
+    }
+    puts(buf);
+    free(bufalloc);
+  }
+}
+
 static char const *
 abbr(struct tm const *tmp)
 {
diff --git a/timezone/zic.c b/timezone/zic.c
index 78ab870..068fb43 100644
--- a/timezone/zic.c
+++ b/timezone/zic.c
@@ -5,10 +5,12 @@ 
 
 #include "version.h"
 #include "private.h"
-#include "locale.h"
 #include "tzfile.h"
 
+#include <fcntl.h>
+#include <locale.h>
 #include <stdarg.h>
+#include <stddef.h>
 
 #define	ZIC_VERSION_PRE_2013 '2'
 #define	ZIC_VERSION	'3'
@@ -16,12 +18,20 @@ 
 typedef int_fast64_t	zic_t;
 #define ZIC_MIN INT_FAST64_MIN
 #define ZIC_MAX INT_FAST64_MAX
+#define PRIdZIC PRIdFAST64
 #define SCNdZIC SCNdFAST64
 
 #ifndef ZIC_MAX_ABBR_LEN_WO_WARN
 #define ZIC_MAX_ABBR_LEN_WO_WARN	6
 #endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
 
+#ifdef HAVE_DIRECT_H
+# include <direct.h>
+# include <io.h>
+# undef mkdir
+# define mkdir(name, mode) _mkdir(name)
+#endif
+
 #if HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
@@ -31,9 +41,18 @@  typedef int_fast64_t	zic_t;
 #define MKDIR_UMASK 0755
 #endif
 
+/* 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 type and printf format for line numbers.  */
+typedef intmax_t lineno;
+#define PRIdLINENO PRIdMAX
+
 struct rule {
 	const char *	r_filename;
-	int		r_linenum;
+	lineno		r_linenum;
 	const char *	r_name;
 
 	zic_t		r_loyear;	/* for example, 1986 */
@@ -56,7 +75,7 @@  struct rule {
 	zic_t		r_stdoff;	/* offset from standard time */
 	const char *	r_abbrvar;	/* variable part of abbreviation */
 
-	int		r_todo;		/* a rule to do (used in outzone) */
+	bool		r_todo;		/* a rule to do (used in outzone) */
 	zic_t		r_temp;		/* used in outzone */
 };
 
@@ -70,7 +89,7 @@  struct rule {
 
 struct zone {
 	const char *	z_filename;
-	int		z_linenum;
+	lineno		z_linenum;
 
 	const char *	z_name;
 	zic_t		z_gmtoff;
@@ -81,23 +100,31 @@  struct zone {
 	zic_t		z_stdoff;
 
 	struct rule *	z_rules;
-	int		z_nrules;
+	ptrdiff_t	z_nrules;
 
 	struct rule	z_untilrule;
 	zic_t		z_untiltime;
 };
 
+#if !HAVE_POSIX_DECLS
 extern int	getopt(int argc, char * const argv[],
 			const char * options);
 extern int	link(const char * fromname, const char * toname);
 extern char *	optarg;
 extern int	optind;
+#endif
 
 #if ! HAVE_LINK
-# define link(from, to) (-1)
+# define link(from, to) (errno = ENOTSUP, -1)
 #endif
 #if ! HAVE_SYMLINK
-# define symlink(from, to) (-1)
+# define readlink(file, buf, size) (errno = ENOTSUP, -1)
+# define symlink(from, to) (errno = ENOTSUP, -1)
+# define S_ISLNK(m) 0
+#endif
+#ifndef AT_SYMLINK_FOLLOW
+# define linkat(fromdir, from, todir, to, flag) \
+    (itssymlink(from) ? (errno = ENOTSUP, -1) : link(from, to))
 #endif
 
 static void	addtt(zic_t starttime, int type);
@@ -105,7 +132,7 @@  static int	addtype(zic_t, char const *, bool, bool, bool);
 static void	leapadd(zic_t, bool, int, int);
 static void	adjleap(void);
 static void	associate(void);
-static void	dolink(const char * fromfield, const char * tofield);
+static void	dolink(const char *, const char *, bool);
 static char **	getfields(char * buf);
 static zic_t	gethms(const char * string, const char * errstring,
 		       bool);
@@ -116,24 +143,32 @@  static void	inrule(char ** fields, int nfields);
 static bool	inzcont(char ** fields, int nfields);
 static bool	inzone(char ** fields, int nfields);
 static bool	inzsub(char **, int, bool);
-static int	itsdir(const char * name);
+static bool	itsdir(char const *);
+static bool	itssymlink(char const *);
 static bool	is_alpha(char a);
 static char	lowerit(char);
-static bool	mkdirs(char *);
+static void	mkdirs(char const *, bool);
 static void	newabbr(const char * abbr);
 static zic_t	oadd(zic_t t1, zic_t t2);
-static void	outzone(const struct zone * zp, int ntzones);
+static void	outzone(const struct zone * zp, ptrdiff_t ntzones);
 static zic_t	rpytime(const struct rule * rp, zic_t wantedy);
 static void	rulesub(struct rule * rp,
 			const char * loyearp, const char * hiyearp,
 			const char * typep, const char * monthp,
 			const char * dayp, const char * timep);
 static zic_t	tadd(zic_t t1, zic_t t2);
-static bool	yearistype(int year, const char * type);
+static bool	yearistype(zic_t year, const char * type);
 
 /* Bound on length of what %z can expand to.  */
 enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 };
 
+/* If true, work around a bug in Qt 5.6.1 and earlier, which mishandles
+   tz binary files whose POSIX-TZ-style strings contain '<'; see
+   QTBUG-53071 <https://bugreports.qt.io/browse/QTBUG-53071>.  This
+   workaround will no longer be needed when Qt 5.6.1 and earlier are
+   obsolete, say in the year 2021.  */
+enum { WORK_AROUND_QTBUG_53071 = true };
+
 static int		charcnt;
 static bool		errors;
 static bool		warnings;
@@ -142,17 +177,17 @@  static int		leapcnt;
 static bool		leapseen;
 static zic_t		leapminyear;
 static zic_t		leapmaxyear;
-static int		linenum;
+static lineno		linenum;
 static int		max_abbrvar_len = PERCENT_Z_LEN_BOUND;
 static int		max_format_len;
 static zic_t		max_year;
 static zic_t		min_year;
 static bool		noise;
 static const char *	rfilename;
-static int		rlinenum;
+static lineno		rlinenum;
 static const char *	progname;
-static int		timecnt;
-static int		timecnt_alloc;
+static ptrdiff_t	timecnt;
+static ptrdiff_t	timecnt_alloc;
 static int		typecnt;
 
 /*
@@ -237,23 +272,23 @@  static int		typecnt;
 #define YR_ONLY		2
 
 static struct rule *	rules;
-static int		nrules;	/* number of rules */
-static int		nrules_alloc;
+static ptrdiff_t	nrules;	/* number of rules */
+static ptrdiff_t	nrules_alloc;
 
 static struct zone *	zones;
-static int		nzones;	/* number of zones */
-static int		nzones_alloc;
+static ptrdiff_t	nzones;	/* number of zones */
+static ptrdiff_t	nzones_alloc;
 
 struct link {
 	const char *	l_filename;
-	int		l_linenum;
+	lineno		l_linenum;
 	const char *	l_from;
 	const char *	l_to;
 };
 
 static struct link *	links;
-static int		nlinks;
-static int		nlinks_alloc;
+static ptrdiff_t	nlinks;
+static ptrdiff_t	nlinks_alloc;
 
 struct lookup {
 	const char *	l_word;
@@ -339,6 +374,7 @@  static const int	len_years[2] = {
 
 static struct attype {
 	zic_t		at;
+	bool		dontmerge;
 	unsigned char	type;
 } *			attypes;
 static zic_t		gmtoffs[TZ_MAX_TYPES];
@@ -406,15 +442,16 @@  ecpyalloc (char const *str)
 }
 
 static void *
-growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc)
+growalloc(void *ptr, size_t itemsize, ptrdiff_t nitems, ptrdiff_t *nitems_alloc)
 {
 	if (nitems < *nitems_alloc)
 		return ptr;
 	else {
-		int amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX;
+		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(_("int overflow"));
-		*nitems_alloc = *nitems_alloc + (*nitems_alloc >> 1) + 1;
+			memory_exhausted(_("integer overflow"));
+		*nitems_alloc += (*nitems_alloc >> 1) + 1;
 		return erealloc(ptr, size_product(*nitems_alloc, itemsize));
 	}
 }
@@ -424,8 +461,7 @@  growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc)
 */
 
 static void
-eats(const char *const name, const int num, const char *const rname,
-     const int rnum)
+eats(char const *name, lineno num, char const *rname, lineno rnum)
 {
 	filename = name;
 	linenum = num;
@@ -434,7 +470,7 @@  eats(const char *const name, const int num, const char *const rname,
 }
 
 static void
-eat(const char *const name, const int num)
+eat(char const *name, lineno num)
 {
 	eats(name, num, NULL, -1);
 }
@@ -448,10 +484,10 @@  verror(const char *const string, va_list args)
 	** on BSD systems.
 	*/
 	if (filename)
-	  fprintf(stderr, _("\"%s\", line %d: "), filename, linenum);
+	  fprintf(stderr, _("\"%s\", line %"PRIdLINENO": "), filename, linenum);
 	vfprintf(stderr, string, args);
 	if (rfilename != NULL)
-		fprintf(stderr, _(" (rule from \"%s\", line %d)"),
+		fprintf(stderr, _(" (rule from \"%s\", line %"PRIdLINENO")"),
 			rfilename, rlinenum);
 	fprintf(stderr, "\n");
 }
@@ -478,15 +514,15 @@  warning(const char *const string, ...)
 }
 
 static void
-close_file(FILE *stream, char const *name)
+close_file(FILE *stream, char const *dir, char const *name)
 {
   char const *e = (ferror(stream) ? _("I/O error")
 		   : fclose(stream) != 0 ? strerror(errno) : NULL);
   if (e) {
-    fprintf(stderr, "%s: ", progname);
-    if (name)
-      fprintf(stderr, "%s: ", name);
-    fprintf(stderr, "%s\n", e);
+    fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
+	    dir ? dir : "", dir ? "/" : "",
+	    name ? name : "", name ? ": " : "",
+	    e);
     exit(EXIT_FAILURE);
   }
 }
@@ -501,10 +537,30 @@  usage(FILE *stream, int status)
 	    "Report bugs to %s.\n"),
 	  progname, progname, REPORT_BUGS_TO);
   if (status == EXIT_SUCCESS)
-    close_file(stream, NULL);
+    close_file(stream, NULL, NULL);
   exit(status);
 }
 
+/* Change the working directory to DIR, possibly creating DIR and its
+   ancestors.  After this is done, all files are accessed with names
+   relative to DIR.  */
+static void
+change_directory (char const *dir)
+{
+  if (chdir(dir) != 0) {
+    int chdir_errno = errno;
+    if (chdir_errno == ENOENT) {
+      mkdirs(dir, false);
+      chdir_errno = chdir(dir) == 0 ? 0 : errno;
+    }
+    if (chdir_errno != 0) {
+      fprintf(stderr, _("%s: Can't chdir to %s: %s\n"),
+	      progname, dir, strerror(chdir_errno));
+      exit(EXIT_FAILURE);
+    }
+  }
+}
+
 static const char *	psxrules;
 static const char *	lcltime;
 static const char *	directory;
@@ -514,9 +570,8 @@  static const char *	yitcommand;
 int
 main(int argc, char **argv)
 {
-	register int	i;
-	register int	j;
-	register int	c;
+	register int c, k;
+	register ptrdiff_t i, j;
 
 #ifdef S_IWGRP
 	umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
@@ -534,12 +589,12 @@  main(int argc, char **argv)
 			_("wild compilation-time specification of zic_t"));
 		return EXIT_FAILURE;
 	}
-	for (i = 1; i < argc; ++i)
-		if (strcmp(argv[i], "--version") == 0) {
+	for (k = 1; k < argc; k++)
+		if (strcmp(argv[k], "--version") == 0) {
 			printf("zic %s%s\n", PKGVERSION, TZVERSION);
-			close_file(stdout, NULL);
+			close_file(stdout, NULL, NULL);
 			return EXIT_SUCCESS;
-		} else if (strcmp(argv[i], "--help") == 0) {
+		} else if (strcmp(argv[k], "--help") == 0) {
 			usage(stdout, EXIT_SUCCESS);
 		}
 	while ((c = getopt(argc, argv, "d:l:p:L:vsy:")) != EOF && c != -1)
@@ -615,11 +670,12 @@  _("%s: More than one -L option specified\n"),
 		adjleap();
 	}
 
-	for (i = optind; i < argc; ++i)
-		infile(argv[i]);
+	for (k = optind; k < argc; k++)
+		infile(argv[k]);
 	if (errors)
 		return EXIT_FAILURE;
 	associate();
+	change_directory(directory);
 	for (i = 0; i < nzones; i = j) {
 		/*
 		** Find the next non-continuation zone entry.
@@ -633,7 +689,7 @@  _("%s: More than one -L option specified\n"),
 	*/
 	for (i = 0; i < nlinks; ++i) {
 		eat(links[i].l_filename, links[i].l_linenum);
-		dolink(links[i].l_from, links[i].l_to);
+		dolink(links[i].l_from, links[i].l_to, false);
 		if (noise)
 			for (j = 0; j < nlinks; ++j)
 				if (strcmp(links[i].l_to,
@@ -642,11 +698,11 @@  _("%s: More than one -L option specified\n"),
 	}
 	if (lcltime != NULL) {
 		eat(_("command line"), 1);
-		dolink(lcltime, TZDEFAULT);
+		dolink(lcltime, TZDEFAULT, true);
 	}
 	if (psxrules != NULL) {
 		eat(_("command line"), 1);
-		dolink(psxrules, TZDEFRULES);
+		dolink(psxrules, TZDEFRULES, true);
 	}
 	if (warnings && (ferror(stderr) || fclose(stderr) != 0))
 	  return EXIT_FAILURE;
@@ -658,7 +714,7 @@  componentcheck(char const *name, char const *component,
 	       char const *component_end)
 {
 	enum { component_len_max = 14 };
-	size_t component_len = component_end - component;
+	ptrdiff_t component_len = component_end - component;
 	if (component_len == 0) {
 	  if (!*name)
 	    error (_("empty file name"));
@@ -673,8 +729,9 @@  componentcheck(char const *name, char const *component,
 	}
 	if (0 < component_len && component_len <= 2
 	    && component[0] == '.' && component_end[-1] == '.') {
+	  int len = component_len;
 	  error(_("file name '%s' contains '%.*s' component"),
-		name, (int) component_len, component);
+		name, len, component);
 	  return false;
 	}
 	if (noise) {
@@ -723,118 +780,138 @@  namecheck(const char *name)
 	return componentcheck(name, component, cp);
 }
 
+/* Create symlink contents suitable for symlinking FROM to TO, as a
+   freshly allocated string.  FROM should be a relative file name, and
+   is relative to the global variable DIRECTORY.  TO can be either
+   relative or absolute.  */
 static char *
-relname(char const *dir, char const *base)
-{
-  if (*base == '/')
-    return ecpyalloc(base);
-  else {
-    size_t dir_len = strlen(dir);
-    bool needs_slash = dir_len && dir[dir_len - 1] != '/';
-    char *result = emalloc(dir_len + needs_slash + strlen(base) + 1);
-    result[dir_len] = '/';
-    strcpy(result + dir_len + needs_slash, base);
-    return memcpy(result, dir, dir_len);
+relname(char const *from, char const *to)
+{
+  size_t i, taillen, dotdotetcsize;
+  size_t dir_len = 0, dotdots = 0, linksize = SIZE_MAX;
+  char const *f = from;
+  char *result = NULL;
+  if (*to == '/') {
+    /* Make F absolute too.  */
+    size_t len = strlen(directory);
+    bool needslash = len && directory[len - 1] != '/';
+    linksize = len + needslash + strlen(from) + 1;
+    f = result = emalloc(linksize);
+    strcpy(result, directory);
+    result[len] = '/';
+    strcpy(result + len + needslash, from);
+  }
+  for (i = 0; f[i] && f[i] == to[i]; i++)
+    if (f[i] == '/')
+      dir_len = i + 1;
+  for (; to[i]; i++)
+    dotdots += to[i] == '/' && to[i - 1] != '/';
+  taillen = strlen(f + dir_len);
+  dotdotetcsize = 3 * dotdots + taillen + 1;
+  if (dotdotetcsize <= linksize) {
+    if (!result)
+      result = emalloc(dotdotetcsize);
+    for (i = 0; i < dotdots; i++)
+      memcpy(result + 3 * i, "../", 3);
+    memmove(result + 3 * dotdots, f + dir_len, taillen + 1);
   }
+  return result;
+}
+
+/* Hard link FROM to TO, following any symbolic links.
+   Return 0 if successful, an error number otherwise.  */
+static int
+hardlinkerr(char const *from, char const *to)
+{
+  int r = linkat(AT_FDCWD, from, AT_FDCWD, to, AT_SYMLINK_FOLLOW);
+  return r == 0 ? 0 : errno;
 }
 
 static void
-dolink(char const *fromfield, char const *tofield)
+dolink(char const *fromfield, char const *tofield, bool staysymlink)
 {
-	register char *	fromname;
-	register char *	toname;
-	register int fromisdir;
+	bool todirs_made = false;
+	int link_errno;
 
-	fromname = relname(directory, fromfield);
-	toname = relname(directory, tofield);
 	/*
 	** We get to be careful here since
 	** there's a fair chance of root running us.
 	*/
-	fromisdir = itsdir(fromname);
-	if (fromisdir) {
-		char const *e = strerror(fromisdir < 0 ? errno : EPERM);
-		fprintf(stderr, _("%s: link from %s failed: %s"),
-			progname, fromname, e);
+	if (itsdir(fromfield)) {
+		fprintf(stderr, _("%s: link from %s/%s failed: %s\n"),
+			progname, directory, fromfield, strerror(EPERM));
 		exit(EXIT_FAILURE);
 	}
-	if (itsdir(toname) <= 0)
-		remove(toname);
-	if (link(fromname, toname) != 0) {
-		int	result;
-
-		if (! mkdirs(toname))
-			exit(EXIT_FAILURE);
-
-		result = link(fromname, toname);
-		if (result != 0) {
-				const char *s = fromfield;
-				const char *t;
-				char *p;
-				size_t dotdots = 0;
-				register char * symlinkcontents = NULL;
-
-				do
-					 t = s;
-				while ((s = strchr(s, '/'))
-				       && ! strncmp (fromfield, tofield,
-						     ++s - fromfield));
-
-				for (s = tofield + (t - fromfield); *s; s++)
-				  dotdots += *s == '/';
-				symlinkcontents
-				  = emalloc(3 * dotdots + strlen(t) + 1);
-				for (p = symlinkcontents; dotdots-- != 0; p += 3)
-				  memcpy(p, "../", 3);
-				strcpy(p, t);
-				result = symlink(symlinkcontents, toname);
-				if (result == 0)
-warning(_("hard link failed, symbolic link used"));
-				free(symlinkcontents);
-		}
-		if (result != 0) {
-			FILE *fp, *tp;
-			int c;
-			fp = fopen(fromname, "rb");
-			if (!fp) {
-				const char *e = strerror(errno);
-				fprintf(stderr,
-					       _("%s: Can't read %s: %s\n"),
-					       progname, fromname, e);
-				exit(EXIT_FAILURE);
-			}
-			tp = fopen(toname, "wb");
-			if (!tp) {
-				const char *e = strerror(errno);
-				fprintf(stderr,
-					       _("%s: Can't create %s: %s\n"),
-					       progname, toname, e);
-				exit(EXIT_FAILURE);
-			}
-			while ((c = getc(fp)) != EOF)
-				putc(c, tp);
-			close_file(fp, fromname);
-			close_file(tp, toname);
-			warning(_("link failed, copy used"));
-		}
+	if (staysymlink)
+	  staysymlink = itssymlink(tofield);
+	if (remove(tofield) == 0)
+	  todirs_made = true;
+	else if (errno != ENOENT) {
+	  char const *e = strerror(errno);
+	  fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
+		  progname, directory, tofield, e);
+	  exit(EXIT_FAILURE);
+	}
+	link_errno = staysymlink ? ENOTSUP : hardlinkerr(fromfield, tofield);
+	if (link_errno == ENOENT && !todirs_made) {
+	  mkdirs(tofield, true);
+	  todirs_made = true;
+	  link_errno = hardlinkerr(fromfield, tofield);
+	}
+	if (link_errno != 0) {
+	  bool absolute = *fromfield == '/';
+	  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) {
+	    mkdirs(tofield, true);
+	    symlink_errno = symlink(contents, tofield) == 0 ? 0 : errno;
+	  }
+	  free(linkalloc);
+	  if (symlink_errno == 0) {
+	    if (link_errno != ENOTSUP)
+	      warning(_("symbolic link used because hard link failed: %s"),
+		      strerror(link_errno));
+	  } else {
+	    FILE *fp, *tp;
+	    int c;
+	    fp = fopen(fromfield, "rb");
+	    if (!fp) {
+	      char const *e = strerror(errno);
+	      fprintf(stderr, _("%s: Can't read %s/%s: %s\n"),
+		      progname, directory, fromfield, e);
+	      exit(EXIT_FAILURE);
+	    }
+	    tp = fopen(tofield, "wb");
+	    if (!tp) {
+	      char const *e = strerror(errno);
+	      fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
+		      progname, directory, tofield, e);
+	      exit(EXIT_FAILURE);
+	    }
+	    while ((c = getc(fp)) != EOF)
+	      putc(c, tp);
+	    close_file(fp, directory, fromfield);
+	    close_file(tp, directory, tofield);
+	    if (link_errno != ENOTSUP)
+	      warning(_("copy used because hard link failed: %s"),
+		      strerror(link_errno));
+	    else if (symlink_errno != ENOTSUP)
+	      warning(_("copy used because symbolic link failed: %s"),
+		      strerror(symlink_errno));
+	  }
 	}
-	free(fromname);
-	free(toname);
 }
 
 #define TIME_T_BITS_IN_FILE	64
 
-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);
+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.
 
-   zic does not output time stamps before this, partly because they
-   are physically suspect, and partly because GNOME mishandles them; see
-   GNOME bug 730332 <https://bugzilla.gnome.org/show_bug.cgi?id=730332>.
-
    For the time of the Big Bang, see:
 
    Ade PAR, Aghanim N, Armitage-Caplan C et al.  Planck 2013 results.
@@ -855,26 +932,49 @@  static zic_t const max_time = MAXVAL (zic_t, TIME_T_BITS_IN_FILE);
 #define BIG_BANG (- (1LL << 59))
 #endif
 
-static const zic_t big_bang_time = BIG_BANG;
+/* If true, work around GNOME bug 730332
+   <https://bugzilla.gnome.org/show_bug.cgi?id=730332>
+   by refusing to output time stamps before BIG_BANG.
+   Such time stamps are physically suspect anyway.
 
-/* Return 1 if NAME is a directory, 0 if it's something else, -1 if trouble.  */
-static int
+   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)
 {
 	struct stat st;
 	int res = stat(name, &st);
-	if (res != 0)
-		return res;
 #ifdef S_ISDIR
-	return S_ISDIR(st.st_mode) != 0;
-#else
-	{
-		char *nameslashdot = relname(name, ".");
-		res = stat(nameslashdot, &st);
+	if (res == 0)
+		return S_ISDIR(st.st_mode) != 0;
+#endif
+	if (res == 0 || errno == EOVERFLOW) {
+		size_t n = strlen(name);
+		char *nameslashdot = emalloc(n + 3);
+		bool dir;
+		memcpy(nameslashdot, name, n);
+		strcpy(&nameslashdot[n], &"/."[! (n && name[n - 1] != '/')]);
+		dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW;
 		free(nameslashdot);
-		return res == 0;
+		return dir;
 	}
-#endif
+	return false;
+}
+
+/* Return true if NAME is a symbolic link.  */
+static bool
+itssymlink(char const *name)
+{
+  char c;
+  return 0 <= readlink(name, &c, 1);
 }
 
 /*
@@ -897,8 +997,7 @@  associate(void)
 {
 	register struct zone *	zp;
 	register struct rule *	rp;
-	register int		base, out;
-	register int		i, j;
+	register ptrdiff_t i, j, base, out;
 
 	if (nrules != 0) {
 		qsort(rules, nrules, sizeof *rules, rcomp);
@@ -976,7 +1075,7 @@  infile(const char *name)
 	register const struct lookup *	lp;
 	register int			nfields;
 	register bool			wantcont;
-	register int			num;
+	register lineno			num;
 	char				buf[BUFSIZ];
 
 	if (strcmp(name, "-") == 0) {
@@ -1017,7 +1116,7 @@  infile(const char *name)
 			lp = byword(fields[0], line_codes);
 			if (lp == NULL)
 				error(_("input line of unknown type"));
-			else switch ((int) (lp->l_value)) {
+			else switch (lp->l_value) {
 				case LC_RULE:
 					inrule(fields, nfields);
 					wantcont = false;
@@ -1046,7 +1145,7 @@  _("%s: panic: Invalid l_value %d\n"),
 		}
 		free(fields);
 	}
-	close_file(fp, filename);
+	close_file(fp, NULL, filename);
 	if (wantcont)
 		error(_("expected continuation line not found"));
 }
@@ -1129,7 +1228,7 @@  inrule(char **fields, int nfields)
 static bool
 inzone(char **fields, int nfields)
 {
-	register int	i;
+	register ptrdiff_t i;
 
 	if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) {
 		error(_("wrong number of fields on Zone line"));
@@ -1150,8 +1249,8 @@  _("\"Zone %s\" line and -p option are mutually exclusive"),
 	for (i = 0; i < nzones; ++i)
 		if (zones[i].z_name != NULL &&
 			strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) {
-				error(
-_("duplicate zone name %s (file \"%s\", line %d)"),
+				error(_("duplicate zone name %s"
+					" (file \"%s\", line %"PRIdLINENO")"),
 					fields[ZF_NAME],
 					zones[i].z_filename,
 					zones[i].z_linenum);
@@ -1263,7 +1362,7 @@  inleap(char **fields, int nfields)
 {
 	register const char *		cp;
 	register const struct lookup *	lp;
-	register int			i, j;
+	register zic_t			i, j;
 	zic_t				year;
 	int				month, day;
 	zic_t				dayoff, tod;
@@ -1355,7 +1454,7 @@  inleap(char **fields, int nfields)
 			return;
 		}
 		t = tadd(t, tod);
-		if (t < big_bang_time) {
+		if (t < early_time) {
 			error(_("leap second precedes Big Bang"));
 			return;
 		}
@@ -1435,7 +1534,7 @@  rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	cp = loyearp;
 	lp = byword(cp, begin_years);
 	rp->r_lowasnum = lp == NULL;
-	if (!rp->r_lowasnum) switch ((int) lp->l_value) {
+	if (!rp->r_lowasnum) switch (lp->l_value) {
 		case YR_MINIMUM:
 			rp->r_loyear = ZIC_MIN;
 			break;
@@ -1454,7 +1553,7 @@  rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
 	cp = hiyearp;
 	lp = byword(cp, end_years);
 	rp->r_hiwasnum = lp == NULL;
-	if (!rp->r_hiwasnum) switch ((int) lp->l_value) {
+	if (!rp->r_hiwasnum) switch (lp->l_value) {
 		case YR_MINIMUM:
 			rp->r_hiyear = ZIC_MIN;
 			break;
@@ -1592,15 +1691,18 @@  static void
 writezone(const char *const name, const char *const string, char version)
 {
 	register FILE *			fp;
-	register int			i, j;
+	register ptrdiff_t		i, j;
 	register int			leapcnt32, leapi32;
-	register int			timecnt32, timei32;
+	register ptrdiff_t		timecnt32, timei32;
 	register int			pass;
-	char *				fullname;
 	static const struct tzhead	tzh0;
 	static struct tzhead		tzh;
-	zic_t *ats = emalloc(size_product(timecnt, sizeof *ats + 1));
-	void *typesptr = ats + timecnt;
+	bool dir_checked = false;
+	zic_t one = 1;
+	zic_t y2038_boundary = one << 31;
+	ptrdiff_t nats = timecnt + WORK_AROUND_QTBUG_53071;
+	zic_t *ats = emalloc(size_product(nats, sizeof *ats + 1));
+	void *typesptr = ats + nats;
 	unsigned char *types = typesptr;
 
 	/*
@@ -1612,12 +1714,11 @@  writezone(const char *const name, const char *const string, char version)
 	** Optimize.
 	*/
 	{
-		int	fromi;
-		int	toi;
+		ptrdiff_t fromi, toi;
 
 		toi = 0;
 		fromi = 0;
-		while (fromi < timecnt && attypes[fromi].at < big_bang_time)
+		while (fromi < timecnt && attypes[fromi].at < early_time)
 			++fromi;
 		for ( ; fromi < timecnt; ++fromi) {
 			if (toi > 1 && ((attypes[fromi].at +
@@ -1628,15 +1729,23 @@  writezone(const char *const name, const char *const string, char version)
 						attypes[fromi].type;
 					continue;
 			}
-			if (toi == 0 ||
-				attypes[toi - 1].type != attypes[fromi].type)
+			if (toi == 0
+			    || attypes[fromi].dontmerge
+			    || attypes[toi - 1].type != attypes[fromi].type)
 					attypes[toi++] = attypes[fromi];
 		}
 		timecnt = toi;
 	}
-	if (noise && timecnt > 1200)
+
+	if (noise && timecnt > 1200) {
+	  if (timecnt > TZ_MAX_TIMES)
+		warning(_("reference clients mishandle"
+			  " more than %d transition times"),
+			TZ_MAX_TIMES);
+	  else
 		warning(_("pre-2014 clients may mishandle"
 			  " more than 1200 transition times"));
+	}
 	/*
 	** Transfer.
 	*/
@@ -1644,6 +1753,19 @@  writezone(const char *const name, const char *const string, char version)
 		ats[i] = attypes[i].at;
 		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.
 	*/
@@ -1681,50 +1803,58 @@  writezone(const char *const name, const char *const string, char version)
 		--leapcnt32;
 		++leapi32;
 	}
-	fullname = relname(directory, name);
 	/*
 	** Remove old file, if any, to snap links.
 	*/
-	if (itsdir(fullname) <= 0 && remove(fullname) != 0 && errno != ENOENT) {
+	if (remove(name) == 0)
+		dir_checked = true;
+	else if (errno != ENOENT) {
 		const char *e = strerror(errno);
 
-		fprintf(stderr, _("%s: Can't remove %s: %s\n"),
-			progname, fullname, e);
+		fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
+			progname, directory, name, e);
 		exit(EXIT_FAILURE);
 	}
-	if ((fp = fopen(fullname, "wb")) == NULL) {
-		if (! mkdirs(fullname))
-			exit(EXIT_FAILURE);
-		if ((fp = fopen(fullname, "wb")) == NULL) {
-			const char *e = strerror(errno);
-
-			fprintf(stderr, _("%s: Can't create %s: %s\n"),
-				progname, fullname, e);
-			exit(EXIT_FAILURE);
-		}
+	fp = fopen(name, "wb");
+	if (!fp) {
+	  int fopen_errno = errno;
+	  if (fopen_errno == ENOENT && !dir_checked) {
+	    mkdirs(name, true);
+	    fp = fopen(name, "wb");
+	    fopen_errno = errno;
+	  }
+	  if (!fp) {
+	    fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
+		    progname, directory, name, strerror(fopen_errno));
+	    exit(EXIT_FAILURE);
+	  }
 	}
 	for (pass = 1; pass <= 2; ++pass) {
-		register int	thistimei, thistimecnt;
-		register int	thisleapi, thisleapcnt;
-		register int	thistimelim, thisleaplim;
+		register ptrdiff_t thistimei, thistimecnt, thistimelim;
+		register int	thisleapi, thisleapcnt, thisleaplim;
 		int		writetype[TZ_MAX_TYPES];
 		int		typemap[TZ_MAX_TYPES];
 		register int	thistypecnt;
 		char		thischars[TZ_MAX_CHARS];
-		char		thischarcnt;
+		int		thischarcnt;
+		bool		toomanytimes;
 		int		indmap[TZ_MAX_CHARS];
 
 		if (pass == 1) {
 			thistimei = timei32;
 			thistimecnt = timecnt32;
+			toomanytimes = thistimecnt >> 31 >> 1 != 0;
 			thisleapi = leapi32;
 			thisleapcnt = leapcnt32;
 		} else {
 			thistimei = 0;
 			thistimecnt = timecnt;
+			toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
 			thisleapi = 0;
 			thisleapcnt = leapcnt;
 		}
+		if (toomanytimes)
+		  error(_("too many transition times"));
 		thistimelim = thistimei + thistimecnt;
 		thisleaplim = thisleapi + thisleapcnt;
 		for (i = 0; i < typecnt; ++i)
@@ -1811,8 +1941,7 @@  writezone(const char *const name, const char *const string, char version)
 				if (strcmp(&thischars[j], thisabbr) == 0)
 					break;
 			if (j == thischarcnt) {
-				strcpy(&thischars[(int) thischarcnt],
-					thisabbr);
+				strcpy(&thischars[thischarcnt], thisabbr);
 				thischarcnt += strlen(thisabbr) + 1;
 			}
 			indmap[abbrinds[i]] = j;
@@ -1894,9 +2023,8 @@  writezone(const char *const name, const char *const string, char version)
 				putc(ttisgmts[i], fp);
 	}
 	fprintf(fp, "\n%s\n", string);
-	close_file(fp, fullname);
+	close_file(fp, directory, name);
 	free(ats);
-	free(fullname);
 }
 
 static char const *
@@ -2097,13 +2225,13 @@  rule_cmp(struct rule const *a, struct rule const *b)
 enum { YEAR_BY_YEAR_ZONE = 1 };
 
 static int
-stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
+stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
 {
 	register const struct zone *	zp;
 	register struct rule *		rp;
 	register struct rule *		stdrp;
 	register struct rule *		dstrp;
-	register int			i;
+	register ptrdiff_t		i;
 	register const char *		abbrvar;
 	register int			compat = 0;
 	register int			c;
@@ -2216,11 +2344,11 @@  stringzone(char *result, const struct zone *const zpfirst, const int zonecount)
 }
 
 static void
-outzone(const struct zone *zpfirst, int zonecount)
+outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
 {
 	register const struct zone *	zp;
 	register struct rule *		rp;
-	register int			i, j;
+	register ptrdiff_t		i, j;
 	register bool			usestart, useuntil;
 	register zic_t			starttime, untiltime;
 	register zic_t			gmtoff;
@@ -2239,6 +2367,10 @@  outzone(const struct zone *zpfirst, int zonecount)
 	register int			compat;
 	register bool			do_extend;
 	register char			version;
+	ptrdiff_t lastatmax = -1;
+	zic_t one = 1;
+	zic_t y2038_boundary = one << 31;
+	zic_t max_year0;
 
 	max_abbr_len = 2 + max_format_len + max_abbrvar_len;
 	max_envvar_len = 2 * max_abbr_len + 5 * 9;
@@ -2334,21 +2466,22 @@  outzone(const struct zone *zpfirst, int zonecount)
 	}
 	/*
 	** For the benefit of older systems,
-	** generate data from 1900 through 2037.
+	** generate data from 1900 through 2038.
 	*/
 	if (min_year > 1900)
 		min_year = 1900;
-	if (max_year < 2037)
-		max_year = 2037;
+	max_year0 = max_year;
+	if (max_year < 2038)
+		max_year = 2038;
 	for (i = 0; i < zonecount; ++i) {
 		/*
 		** A guess that may well be corrected later.
 		*/
 		stdoff = 0;
 		zp = &zpfirst[i];
-		usestart = i > 0 && (zp - 1)->z_untiltime > big_bang_time;
+		usestart = i > 0 && (zp - 1)->z_untiltime > early_time;
 		useuntil = i < (zonecount - 1);
-		if (useuntil && zp->z_untiltime <= big_bang_time)
+		if (useuntil && zp->z_untiltime <= early_time)
 			continue;
 		gmtoff = zp->z_gmtoff;
 		eat(zp->z_filename, zp->z_linenum);
@@ -2363,7 +2496,7 @@  outzone(const struct zone *zpfirst, int zonecount)
 			if (usestart) {
 				addtt(starttime, type);
 				usestart = false;
-			} else	addtt(big_bang_time, type);
+			} else	addtt(early_time, type);
 		} else for (year = min_year; year <= max_year; ++year) {
 			if (useuntil && year > zp->z_untilrule.r_hiyear)
 				break;
@@ -2378,11 +2511,15 @@  outzone(const struct zone *zpfirst, int zonecount)
 				rp->r_todo = year >= rp->r_loyear &&
 						year <= rp->r_hiyear &&
 						yearistype(year, rp->r_yrtype);
-				if (rp->r_todo)
+				if (rp->r_todo) {
 					rp->r_temp = rpytime(rp, year);
+					rp->r_todo
+					  = (rp->r_temp < y2038_boundary
+					     || year <= max_year0);
+				}
 			}
 			for ( ; ; ) {
-				register int	k;
+				register ptrdiff_t k;
 				register zic_t	jtime, ktime;
 				register zic_t	offset;
 
@@ -2471,6 +2608,10 @@  outzone(const struct zone *zpfirst, int zonecount)
 				offset = oadd(zp->z_gmtoff, rp->r_stdoff);
 				type = addtype(offset, ab, rp->r_stdoff != 0,
 					rp->r_todisstd, rp->r_todisgmt);
+				if (rp->r_hiyear == ZIC_MAX
+				    && ! (0 <= lastatmax
+					  && ktime < attypes[lastatmax].at))
+				  lastatmax = timecnt;
 				addtt(ktime, type);
 			}
 		}
@@ -2502,6 +2643,8 @@  error(_("can't determine time zone abbreviation to use just after until time"));
 				starttime = tadd(starttime, -gmtoff);
 		}
 	}
+	if (0 <= lastatmax)
+	  attypes[lastatmax].dontmerge = true;
 	if (do_extend) {
 		/*
 		** If we're extending the explicitly listed observations
@@ -2523,21 +2666,8 @@  error(_("can't determine time zone abbreviation to use just after until time"));
 			if (attypes[i].at > lastat->at)
 				lastat = &attypes[i];
 		if (lastat->at < rpytime(&xr, max_year - 1)) {
-			/*
-			** Create new type code for the redundant entry,
-			** to prevent it being optimized away.
-			*/
-			if (typecnt >= TZ_MAX_TYPES) {
-				error(_("too many local time types"));
-				exit(EXIT_FAILURE);
-			}
-			gmtoffs[typecnt] = gmtoffs[lastat->type];
-			isdsts[typecnt] = isdsts[lastat->type];
-			ttisstds[typecnt] = ttisstds[lastat->type];
-			ttisgmts[typecnt] = ttisgmts[lastat->type];
-			abbrinds[typecnt] = abbrinds[lastat->type];
-			++typecnt;
 			addtt(rpytime(&xr, max_year + 1), typecnt-1);
+			attypes[timecnt - 1].dontmerge = true;
 		}
 	}
 	writezone(zpfirst->z_name, envvar, version);
@@ -2549,8 +2679,8 @@  error(_("can't determine time zone abbreviation to use just after until time"));
 static void
 addtt(zic_t starttime, int type)
 {
-	if (starttime <= big_bang_time ||
-		(timecnt == 1 && attypes[0].at < big_bang_time)) {
+	if (starttime <= early_time
+	    || (timecnt == 1 && attypes[0].at < early_time)) {
 		gmtoffs[0] = gmtoffs[type];
 		isdsts[0] = isdsts[type];
 		ttisstds[0] = ttisstds[type];
@@ -2565,6 +2695,7 @@  addtt(zic_t starttime, int type)
 	}
 	attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
 	attypes[timecnt].at = starttime;
+	attypes[timecnt].dontmerge = false;
 	attypes[timecnt].type = type;
 	++timecnt;
 }
@@ -2657,28 +2788,48 @@  adjleap(void)
 	}
 }
 
+static char *
+shellquote(char *b, char const *s)
+{
+  *b++ = '\'';
+  while (*s) {
+    if (*s == '\'')
+      *b++ = '\'', *b++ = '\\', *b++ = '\'';
+    *b++ = *s++;
+  }
+  *b++ = '\'';
+  return b;
+}
+
 static bool
-yearistype(int year, const char *type)
+yearistype(zic_t year, const char *type)
 {
-	static char *	buf;
-	int		result;
+	char *buf;
+	char *b;
+	int result;
 
 	if (type == NULL || *type == '\0')
 		return true;
-	buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type));
-	sprintf(buf, "%s %d %s", yitcommand, year, type);
+	buf = emalloc(1 + 4 * strlen(yitcommand) + 2
+		      + INT_STRLEN_MAXIMUM(zic_t) + 2 + 4 * strlen(type) + 2);
+	b = shellquote(buf, yitcommand);
+	*b++ = ' ';
+	b += sprintf(b, "%"PRIdZIC, year);
+	*b++ = ' ';
+	b = shellquote(b, type);
+	*b = '\0';
 	result = system(buf);
-	if (WIFEXITED(result)) switch (WEXITSTATUS(result)) {
-		case 0:
-			return true;
-		case 1:
-			return false;
+	if (WIFEXITED(result)) {
+	  int status = WEXITSTATUS(result);
+	  if (status <= 1) {
+	    free(buf);
+	    return status == 0;
+	  }
 	}
 	error(_("Wild result from command execution"));
 	fprintf(stderr, _("%s: command was '%s', result was %d\n"),
 		progname, buf, result);
-	for ( ; ; )
-		exit(EXIT_FAILURE);
+	exit(EXIT_FAILURE);
 }
 
 /* Is A a space character in the C locale?  */
@@ -2806,10 +2957,8 @@  getfields(register char *cp)
 				if (*dp != '\0')
 					++dp;
 				else {
-					error(_(
-						"Odd number of quotation marks"
-						));
-					exit(1);
+				  error(_("Odd number of quotation marks"));
+				  exit(EXIT_FAILURE);
 				}
 		} while (*cp && *cp != '#' && !is_space(*cp));
 		if (is_space(*cp))
@@ -2972,44 +3121,49 @@  mp = _("time zone abbreviation differs from POSIX standard");
 	charcnt += i;
 }
 
-static bool
-mkdirs(char *argname)
+/* Ensure that the directories of ARGNAME exist, by making any missing
+   ones.  If ANCESTORS, do this only for ARGNAME's ancestors; otherwise,
+   do it for ARGNAME too.  Exit with failure if there is trouble.
+   Do not consider an existing non-directory to be trouble.  */
+static void
+mkdirs(char const *argname, bool ancestors)
 {
 	register char *	name;
 	register char *	cp;
 
-	if (argname == NULL || *argname == '\0')
-		return true;
 	cp = name = ecpyalloc(argname);
-	while ((cp = strchr(cp + 1, '/')) != 0) {
-		*cp = '\0';
+
+	/* Do not mkdir a root directory, as it must exist.  */
 #ifdef HAVE_DOS_FILE_NAMES
-		/*
-		** DOS drive specifier?
-		*/
-		if (is_alpha(name[0]) && name[1] == ':' && name[2] == '\0') {
-				*cp = '/';
-				continue;
-		}
+	if (is_alpha(name[0]) && name[1] == ':')
+	  cp += 2;
 #endif
+	while (*cp == '/')
+	  cp++;
+
+	while (cp && ((cp = strchr(cp, '/')) || !ancestors)) {
+		if (cp)
+		  *cp = '\0';
 		/*
 		** Try to create it.  It's OK if creation fails because
 		** the directory already exists, perhaps because some
-		** other process just created it.
+		** other process just created it.  For simplicity do
+		** not check first whether it already exists, as that
+		** is checked anyway if the mkdir fails.
 		*/
 		if (mkdir(name, MKDIR_UMASK) != 0) {
+			/* For speed, skip itsdir if errno == EEXIST.  Since
+			   mkdirs is called only after open fails with ENOENT
+			   on a subfile, EEXIST implies itsdir here.  */
 			int err = errno;
-			if (itsdir(name) <= 0) {
-				char const *e = strerror(err);
-				warning(_("%s: Can't create directory"
-					  " %s: %s"),
-					progname, name, e);
-				free(name);
-				return false;
+			if (err != EEXIST && !itsdir(name)) {
+				error(_("%s: Can't create directory %s: %s"),
+				      progname, name, strerror(err));
+				exit(EXIT_FAILURE);
 			}
 		}
-		*cp = '/';
+		if (cp)
+		  *cp++ = '/';
 	}
 	free(name);
-	return true;
 }