xref: /freebsd/contrib/tzcode/zdump.c (revision bc42155199b5b0b479311e05b07aee7f6f9c5172)
1*bc421551SDag-Erling Smørgrav /* Dump time zone data in a textual format.  */
2*bc421551SDag-Erling Smørgrav 
3*bc421551SDag-Erling Smørgrav /*
4*bc421551SDag-Erling Smørgrav ** This file is in the public domain, so clarified as of
5*bc421551SDag-Erling Smørgrav ** 2009-05-17 by Arthur David Olson.
6*bc421551SDag-Erling Smørgrav */
7*bc421551SDag-Erling Smørgrav 
8*bc421551SDag-Erling Smørgrav #include "version.h"
9*bc421551SDag-Erling Smørgrav 
10*bc421551SDag-Erling Smørgrav #ifndef NETBSD_INSPIRED
11*bc421551SDag-Erling Smørgrav # define NETBSD_INSPIRED 1
12*bc421551SDag-Erling Smørgrav #endif
13*bc421551SDag-Erling Smørgrav 
14*bc421551SDag-Erling Smørgrav #include "private.h"
15*bc421551SDag-Erling Smørgrav #include <stdio.h>
16*bc421551SDag-Erling Smørgrav 
17*bc421551SDag-Erling Smørgrav #ifndef HAVE_SNPRINTF
18*bc421551SDag-Erling Smørgrav # define HAVE_SNPRINTF (199901 <= __STDC_VERSION__)
19*bc421551SDag-Erling Smørgrav #endif
20*bc421551SDag-Erling Smørgrav 
21*bc421551SDag-Erling Smørgrav #ifndef HAVE_LOCALTIME_R
22*bc421551SDag-Erling Smørgrav # define HAVE_LOCALTIME_R 1
23*bc421551SDag-Erling Smørgrav #endif
24*bc421551SDag-Erling Smørgrav 
25*bc421551SDag-Erling Smørgrav #ifndef HAVE_LOCALTIME_RZ
26*bc421551SDag-Erling Smørgrav # ifdef TM_ZONE
27*bc421551SDag-Erling Smørgrav #  define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
28*bc421551SDag-Erling Smørgrav # else
29*bc421551SDag-Erling Smørgrav #  define HAVE_LOCALTIME_RZ 0
30*bc421551SDag-Erling Smørgrav # endif
31*bc421551SDag-Erling Smørgrav #endif
32*bc421551SDag-Erling Smørgrav 
33*bc421551SDag-Erling Smørgrav #ifndef HAVE_TZSET
34*bc421551SDag-Erling Smørgrav # define HAVE_TZSET 1
35*bc421551SDag-Erling Smørgrav #endif
36*bc421551SDag-Erling Smørgrav 
37*bc421551SDag-Erling Smørgrav #ifndef ZDUMP_LO_YEAR
38*bc421551SDag-Erling Smørgrav # define ZDUMP_LO_YEAR (-500)
39*bc421551SDag-Erling Smørgrav #endif /* !defined ZDUMP_LO_YEAR */
40*bc421551SDag-Erling Smørgrav 
41*bc421551SDag-Erling Smørgrav #ifndef ZDUMP_HI_YEAR
42*bc421551SDag-Erling Smørgrav # define ZDUMP_HI_YEAR 2500
43*bc421551SDag-Erling Smørgrav #endif /* !defined ZDUMP_HI_YEAR */
44*bc421551SDag-Erling Smørgrav 
45*bc421551SDag-Erling Smørgrav #define SECSPERNYEAR	(SECSPERDAY * DAYSPERNYEAR)
46*bc421551SDag-Erling Smørgrav #define SECSPERLYEAR	(SECSPERNYEAR + SECSPERDAY)
47*bc421551SDag-Erling Smørgrav #define SECSPER400YEARS	(SECSPERNYEAR * (intmax_t) (300 + 3)	\
48*bc421551SDag-Erling Smørgrav 			 + SECSPERLYEAR * (intmax_t) (100 - 3))
49*bc421551SDag-Erling Smørgrav 
50*bc421551SDag-Erling Smørgrav /*
51*bc421551SDag-Erling Smørgrav ** True if SECSPER400YEARS is known to be representable as an
52*bc421551SDag-Erling Smørgrav ** intmax_t.  It's OK that SECSPER400YEARS_FITS can in theory be false
53*bc421551SDag-Erling Smørgrav ** even if SECSPER400YEARS is representable, because when that happens
54*bc421551SDag-Erling Smørgrav ** the code merely runs a bit more slowly, and this slowness doesn't
55*bc421551SDag-Erling Smørgrav ** occur on any practical platform.
56*bc421551SDag-Erling Smørgrav */
57*bc421551SDag-Erling Smørgrav enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
58*bc421551SDag-Erling Smørgrav 
59*bc421551SDag-Erling Smørgrav #if HAVE_GETTEXT
60*bc421551SDag-Erling Smørgrav # include <locale.h> /* for setlocale */
61*bc421551SDag-Erling Smørgrav #endif /* HAVE_GETTEXT */
62*bc421551SDag-Erling Smørgrav 
63*bc421551SDag-Erling Smørgrav #if ! HAVE_LOCALTIME_RZ
64*bc421551SDag-Erling Smørgrav # undef  timezone_t
65*bc421551SDag-Erling Smørgrav # define timezone_t char **
66*bc421551SDag-Erling Smørgrav #endif
67*bc421551SDag-Erling Smørgrav 
68*bc421551SDag-Erling Smørgrav #if !HAVE_POSIX_DECLS
69*bc421551SDag-Erling Smørgrav extern int	getopt(int argc, char * const argv[],
70*bc421551SDag-Erling Smørgrav 			const char * options);
71*bc421551SDag-Erling Smørgrav extern char *	optarg;
72*bc421551SDag-Erling Smørgrav extern int	optind;
73*bc421551SDag-Erling Smørgrav #endif
74*bc421551SDag-Erling Smørgrav 
75*bc421551SDag-Erling Smørgrav /* The minimum and maximum finite time values.  */
76*bc421551SDag-Erling Smørgrav enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 };
77*bc421551SDag-Erling Smørgrav static time_t const absolute_min_time =
78*bc421551SDag-Erling Smørgrav   ((time_t) -1 < 0
79*bc421551SDag-Erling Smørgrav    ? (- ((time_t) ~ (time_t) 0 < 0)
80*bc421551SDag-Erling Smørgrav       - (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
81*bc421551SDag-Erling Smørgrav    : 0);
82*bc421551SDag-Erling Smørgrav static time_t const absolute_max_time =
83*bc421551SDag-Erling Smørgrav   ((time_t) -1 < 0
84*bc421551SDag-Erling Smørgrav    ? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
85*bc421551SDag-Erling Smørgrav    : -1);
86*bc421551SDag-Erling Smørgrav static size_t	longest;
87*bc421551SDag-Erling Smørgrav static char const *progname;
88*bc421551SDag-Erling Smørgrav static bool	warned;
89*bc421551SDag-Erling Smørgrav static bool	errout;
90*bc421551SDag-Erling Smørgrav 
91*bc421551SDag-Erling Smørgrav static char const *abbr(struct tm const *);
92*bc421551SDag-Erling Smørgrav static intmax_t	delta(struct tm *, struct tm *) ATTRIBUTE_REPRODUCIBLE;
93*bc421551SDag-Erling Smørgrav static void dumptime(struct tm const *);
94*bc421551SDag-Erling Smørgrav static time_t hunt(timezone_t, time_t, time_t, bool);
95*bc421551SDag-Erling Smørgrav static void show(timezone_t, char *, time_t, bool);
96*bc421551SDag-Erling Smørgrav static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
97*bc421551SDag-Erling Smørgrav static void showtrans(char const *, struct tm const *, time_t, char const *,
98*bc421551SDag-Erling Smørgrav 		      char const *);
99*bc421551SDag-Erling Smørgrav static const char *tformat(void);
100*bc421551SDag-Erling Smørgrav static time_t yeartot(intmax_t) ATTRIBUTE_REPRODUCIBLE;
101*bc421551SDag-Erling Smørgrav 
102*bc421551SDag-Erling Smørgrav /* Is C an ASCII digit?  */
103*bc421551SDag-Erling Smørgrav static bool
104*bc421551SDag-Erling Smørgrav is_digit(char c)
105*bc421551SDag-Erling Smørgrav {
106*bc421551SDag-Erling Smørgrav   return '0' <= c && c <= '9';
107*bc421551SDag-Erling Smørgrav }
108*bc421551SDag-Erling Smørgrav 
109*bc421551SDag-Erling Smørgrav /* Is A an alphabetic character in the C locale?  */
110*bc421551SDag-Erling Smørgrav static bool
111*bc421551SDag-Erling Smørgrav is_alpha(char a)
112*bc421551SDag-Erling Smørgrav {
113*bc421551SDag-Erling Smørgrav 	switch (a) {
114*bc421551SDag-Erling Smørgrav 	  default:
115*bc421551SDag-Erling Smørgrav 		return false;
116*bc421551SDag-Erling Smørgrav 	  case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
117*bc421551SDag-Erling Smørgrav 	  case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
118*bc421551SDag-Erling Smørgrav 	  case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
119*bc421551SDag-Erling Smørgrav 	  case 'V': case 'W': case 'X': case 'Y': case 'Z':
120*bc421551SDag-Erling Smørgrav 	  case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
121*bc421551SDag-Erling Smørgrav 	  case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
122*bc421551SDag-Erling Smørgrav 	  case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
123*bc421551SDag-Erling Smørgrav 	  case 'v': case 'w': case 'x': case 'y': case 'z':
124*bc421551SDag-Erling Smørgrav 		return true;
125*bc421551SDag-Erling Smørgrav 	}
126*bc421551SDag-Erling Smørgrav }
127*bc421551SDag-Erling Smørgrav 
128*bc421551SDag-Erling Smørgrav static ATTRIBUTE_NORETURN void
129*bc421551SDag-Erling Smørgrav size_overflow(void)
130*bc421551SDag-Erling Smørgrav {
131*bc421551SDag-Erling Smørgrav   fprintf(stderr, _("%s: size overflow\n"), progname);
132*bc421551SDag-Erling Smørgrav   exit(EXIT_FAILURE);
133*bc421551SDag-Erling Smørgrav }
134*bc421551SDag-Erling Smørgrav 
135*bc421551SDag-Erling Smørgrav /* Return A + B, exiting if the result would overflow either ptrdiff_t
136*bc421551SDag-Erling Smørgrav    or size_t.  */
137*bc421551SDag-Erling Smørgrav static ATTRIBUTE_REPRODUCIBLE ptrdiff_t
138*bc421551SDag-Erling Smørgrav sumsize(size_t a, size_t b)
139*bc421551SDag-Erling Smørgrav {
140*bc421551SDag-Erling Smørgrav #ifdef ckd_add
141*bc421551SDag-Erling Smørgrav   ptrdiff_t sum;
142*bc421551SDag-Erling Smørgrav   if (!ckd_add(&sum, a, b) /* && sum <= SIZE_MAX */)
143*bc421551SDag-Erling Smørgrav     return sum;
144*bc421551SDag-Erling Smørgrav #else
145*bc421551SDag-Erling Smørgrav   ptrdiff_t sum_max = min(PTRDIFF_MAX, SIZE_MAX);
146*bc421551SDag-Erling Smørgrav   if (a <= sum_max && b <= sum_max - a)
147*bc421551SDag-Erling Smørgrav     return a + b;
148*bc421551SDag-Erling Smørgrav #endif
149*bc421551SDag-Erling Smørgrav   size_overflow();
150*bc421551SDag-Erling Smørgrav }
151*bc421551SDag-Erling Smørgrav 
152*bc421551SDag-Erling Smørgrav /* Return a pointer to a newly allocated buffer of size SIZE, exiting
153*bc421551SDag-Erling Smørgrav    on failure.  SIZE should be nonzero.  */
154*bc421551SDag-Erling Smørgrav static void * ATTRIBUTE_MALLOC
155*bc421551SDag-Erling Smørgrav xmalloc(size_t size)
156*bc421551SDag-Erling Smørgrav {
157*bc421551SDag-Erling Smørgrav   void *p = malloc(size);
158*bc421551SDag-Erling Smørgrav   if (!p) {
159*bc421551SDag-Erling Smørgrav     fprintf(stderr, _("%s: Memory exhausted\n"), progname);
160*bc421551SDag-Erling Smørgrav     exit(EXIT_FAILURE);
161*bc421551SDag-Erling Smørgrav   }
162*bc421551SDag-Erling Smørgrav   return p;
163*bc421551SDag-Erling Smørgrav }
164*bc421551SDag-Erling Smørgrav 
165*bc421551SDag-Erling Smørgrav #if ! HAVE_TZSET
166*bc421551SDag-Erling Smørgrav # undef tzset
167*bc421551SDag-Erling Smørgrav # define tzset zdump_tzset
168*bc421551SDag-Erling Smørgrav static void tzset(void) { }
169*bc421551SDag-Erling Smørgrav #endif
170*bc421551SDag-Erling Smørgrav 
171*bc421551SDag-Erling Smørgrav /* Assume gmtime_r works if localtime_r does.
172*bc421551SDag-Erling Smørgrav    A replacement localtime_r is defined below if needed.  */
173*bc421551SDag-Erling Smørgrav #if ! HAVE_LOCALTIME_R
174*bc421551SDag-Erling Smørgrav 
175*bc421551SDag-Erling Smørgrav # undef gmtime_r
176*bc421551SDag-Erling Smørgrav # define gmtime_r zdump_gmtime_r
177*bc421551SDag-Erling Smørgrav 
178*bc421551SDag-Erling Smørgrav static struct tm *
179*bc421551SDag-Erling Smørgrav gmtime_r(time_t *tp, struct tm *tmp)
180*bc421551SDag-Erling Smørgrav {
181*bc421551SDag-Erling Smørgrav   struct tm *r = gmtime(tp);
182*bc421551SDag-Erling Smørgrav   if (r) {
183*bc421551SDag-Erling Smørgrav     *tmp = *r;
184*bc421551SDag-Erling Smørgrav     r = tmp;
185*bc421551SDag-Erling Smørgrav   }
186*bc421551SDag-Erling Smørgrav   return r;
187*bc421551SDag-Erling Smørgrav }
188*bc421551SDag-Erling Smørgrav 
189*bc421551SDag-Erling Smørgrav #endif
190*bc421551SDag-Erling Smørgrav 
191*bc421551SDag-Erling Smørgrav /* Platforms with TM_ZONE don't need tzname, so they can use the
192*bc421551SDag-Erling Smørgrav    faster localtime_rz or localtime_r if available.  */
193*bc421551SDag-Erling Smørgrav 
194*bc421551SDag-Erling Smørgrav #if defined TM_ZONE && HAVE_LOCALTIME_RZ
195*bc421551SDag-Erling Smørgrav # define USE_LOCALTIME_RZ true
196*bc421551SDag-Erling Smørgrav #else
197*bc421551SDag-Erling Smørgrav # define USE_LOCALTIME_RZ false
198*bc421551SDag-Erling Smørgrav #endif
199*bc421551SDag-Erling Smørgrav 
200*bc421551SDag-Erling Smørgrav #if ! USE_LOCALTIME_RZ
201*bc421551SDag-Erling Smørgrav 
202*bc421551SDag-Erling Smørgrav # if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
203*bc421551SDag-Erling Smørgrav #  undef localtime_r
204*bc421551SDag-Erling Smørgrav #  define localtime_r zdump_localtime_r
205*bc421551SDag-Erling Smørgrav static struct tm *
206*bc421551SDag-Erling Smørgrav localtime_r(time_t *tp, struct tm *tmp)
207*bc421551SDag-Erling Smørgrav {
208*bc421551SDag-Erling Smørgrav   struct tm *r = localtime(tp);
209*bc421551SDag-Erling Smørgrav   if (r) {
210*bc421551SDag-Erling Smørgrav     *tmp = *r;
211*bc421551SDag-Erling Smørgrav     r = tmp;
212*bc421551SDag-Erling Smørgrav   }
213*bc421551SDag-Erling Smørgrav   return r;
214*bc421551SDag-Erling Smørgrav }
215*bc421551SDag-Erling Smørgrav # endif
216*bc421551SDag-Erling Smørgrav 
217*bc421551SDag-Erling Smørgrav # undef localtime_rz
218*bc421551SDag-Erling Smørgrav # define localtime_rz zdump_localtime_rz
219*bc421551SDag-Erling Smørgrav static struct tm *
220*bc421551SDag-Erling Smørgrav localtime_rz(timezone_t rz __unused, time_t *tp, struct tm *tmp)
221*bc421551SDag-Erling Smørgrav {
222*bc421551SDag-Erling Smørgrav   return localtime_r(tp, tmp);
223*bc421551SDag-Erling Smørgrav }
224*bc421551SDag-Erling Smørgrav 
225*bc421551SDag-Erling Smørgrav # ifdef TYPECHECK
226*bc421551SDag-Erling Smørgrav #  undef mktime_z
227*bc421551SDag-Erling Smørgrav #  define mktime_z zdump_mktime_z
228*bc421551SDag-Erling Smørgrav static time_t
229*bc421551SDag-Erling Smørgrav mktime_z(timezone_t tz, struct tm *tmp)
230*bc421551SDag-Erling Smørgrav {
231*bc421551SDag-Erling Smørgrav   return mktime(tmp);
232*bc421551SDag-Erling Smørgrav }
233*bc421551SDag-Erling Smørgrav # endif
234*bc421551SDag-Erling Smørgrav 
235*bc421551SDag-Erling Smørgrav # undef tzalloc
236*bc421551SDag-Erling Smørgrav # undef tzfree
237*bc421551SDag-Erling Smørgrav # define tzalloc zdump_tzalloc
238*bc421551SDag-Erling Smørgrav # define tzfree zdump_tzfree
239*bc421551SDag-Erling Smørgrav 
240*bc421551SDag-Erling Smørgrav static timezone_t
241*bc421551SDag-Erling Smørgrav tzalloc(char const *val)
242*bc421551SDag-Erling Smørgrav {
243*bc421551SDag-Erling Smørgrav # if HAVE_SETENV
244*bc421551SDag-Erling Smørgrav   if (setenv("TZ", val, 1) != 0) {
245*bc421551SDag-Erling Smørgrav     perror("setenv");
246*bc421551SDag-Erling Smørgrav     exit(EXIT_FAILURE);
247*bc421551SDag-Erling Smørgrav   }
248*bc421551SDag-Erling Smørgrav   tzset();
249*bc421551SDag-Erling Smørgrav   return &optarg;  /* Any valid non-null char ** will do.  */
250*bc421551SDag-Erling Smørgrav # else
251*bc421551SDag-Erling Smørgrav   enum { TZeqlen = 3 };
252*bc421551SDag-Erling Smørgrav   static char const TZeq[TZeqlen] = "TZ=";
253*bc421551SDag-Erling Smørgrav   static char **fakeenv;
254*bc421551SDag-Erling Smørgrav   static ptrdiff_t fakeenv0size;
255*bc421551SDag-Erling Smørgrav   void *freeable = NULL;
256*bc421551SDag-Erling Smørgrav   char **env = fakeenv, **initial_environ;
257*bc421551SDag-Erling Smørgrav   size_t valsize = strlen(val) + 1;
258*bc421551SDag-Erling Smørgrav   if (fakeenv0size < valsize) {
259*bc421551SDag-Erling Smørgrav     char **e = environ, **to;
260*bc421551SDag-Erling Smørgrav     ptrdiff_t initial_nenvptrs = 1;  /* Counting the trailing NULL pointer.  */
261*bc421551SDag-Erling Smørgrav 
262*bc421551SDag-Erling Smørgrav     while (*e++) {
263*bc421551SDag-Erling Smørgrav #  ifdef ckd_add
264*bc421551SDag-Erling Smørgrav       if (ckd_add(&initial_nenvptrs, initial_envptrs, 1)
265*bc421551SDag-Erling Smørgrav 	  || SIZE_MAX < initial_envptrs)
266*bc421551SDag-Erling Smørgrav 	size_overflow();
267*bc421551SDag-Erling Smørgrav #  else
268*bc421551SDag-Erling Smørgrav       if (initial_nenvptrs == min(PTRDIFF_MAX, SIZE_MAX) / sizeof *environ)
269*bc421551SDag-Erling Smørgrav 	size_overflow();
270*bc421551SDag-Erling Smørgrav       initial_nenvptrs++;
271*bc421551SDag-Erling Smørgrav #  endif
272*bc421551SDag-Erling Smørgrav     }
273*bc421551SDag-Erling Smørgrav     fakeenv0size = sumsize(valsize, valsize);
274*bc421551SDag-Erling Smørgrav     fakeenv0size = max(fakeenv0size, 64);
275*bc421551SDag-Erling Smørgrav     freeable = env;
276*bc421551SDag-Erling Smørgrav     fakeenv = env =
277*bc421551SDag-Erling Smørgrav       xmalloc(sumsize(sumsize(sizeof *environ,
278*bc421551SDag-Erling Smørgrav 			      initial_nenvptrs * sizeof *environ),
279*bc421551SDag-Erling Smørgrav 		      sumsize(TZeqlen, fakeenv0size)));
280*bc421551SDag-Erling Smørgrav     to = env + 1;
281*bc421551SDag-Erling Smørgrav     for (e = environ; (*to = *e); e++)
282*bc421551SDag-Erling Smørgrav       to += strncmp(*e, TZeq, TZeqlen) != 0;
283*bc421551SDag-Erling Smørgrav     env[0] = memcpy(to + 1, TZeq, TZeqlen);
284*bc421551SDag-Erling Smørgrav   }
285*bc421551SDag-Erling Smørgrav   memcpy(env[0] + TZeqlen, val, valsize);
286*bc421551SDag-Erling Smørgrav   initial_environ = environ;
287*bc421551SDag-Erling Smørgrav   environ = env;
288*bc421551SDag-Erling Smørgrav   tzset();
289*bc421551SDag-Erling Smørgrav   free(freeable);
290*bc421551SDag-Erling Smørgrav   return initial_environ;
291*bc421551SDag-Erling Smørgrav # endif
292*bc421551SDag-Erling Smørgrav }
293*bc421551SDag-Erling Smørgrav 
294*bc421551SDag-Erling Smørgrav static void
295*bc421551SDag-Erling Smørgrav tzfree(timezone_t initial_environ)
296*bc421551SDag-Erling Smørgrav {
297*bc421551SDag-Erling Smørgrav # if !HAVE_SETENV
298*bc421551SDag-Erling Smørgrav   environ = initial_environ;
299*bc421551SDag-Erling Smørgrav   tzset();
300*bc421551SDag-Erling Smørgrav # else
301*bc421551SDag-Erling Smørgrav   (void)initial_environ;
302*bc421551SDag-Erling Smørgrav # endif
303*bc421551SDag-Erling Smørgrav }
304*bc421551SDag-Erling Smørgrav #endif /* ! USE_LOCALTIME_RZ */
305*bc421551SDag-Erling Smørgrav 
306*bc421551SDag-Erling Smørgrav /* A UT time zone, and its initializer.  */
307*bc421551SDag-Erling Smørgrav static timezone_t gmtz;
308*bc421551SDag-Erling Smørgrav static void
309*bc421551SDag-Erling Smørgrav gmtzinit(void)
310*bc421551SDag-Erling Smørgrav {
311*bc421551SDag-Erling Smørgrav   if (USE_LOCALTIME_RZ) {
312*bc421551SDag-Erling Smørgrav     /* Try "GMT" first to find out whether this is one of the rare
313*bc421551SDag-Erling Smørgrav        platforms where time_t counts leap seconds; this works due to
314*bc421551SDag-Erling Smørgrav        the "Zone GMT 0 - GMT" line in the "etcetera" file.  If "GMT"
315*bc421551SDag-Erling Smørgrav        fails, fall back on "GMT0" which might be similar due to the
316*bc421551SDag-Erling Smørgrav        "Link GMT GMT0" line in the "backward" file, and which
317*bc421551SDag-Erling Smørgrav        should work on all POSIX platforms.  The rest of zdump does not
318*bc421551SDag-Erling Smørgrav        use the "GMT" abbreviation that comes from this setting, so it
319*bc421551SDag-Erling Smørgrav        is OK to use "GMT" here rather than the more-modern "UTC" which
320*bc421551SDag-Erling Smørgrav        would not work on platforms that omit the "backward" file.  */
321*bc421551SDag-Erling Smørgrav     gmtz = tzalloc("GMT");
322*bc421551SDag-Erling Smørgrav     if (!gmtz) {
323*bc421551SDag-Erling Smørgrav       static char const gmt0[] = "GMT0";
324*bc421551SDag-Erling Smørgrav       gmtz = tzalloc(gmt0);
325*bc421551SDag-Erling Smørgrav       if (!gmtz) {
326*bc421551SDag-Erling Smørgrav 	perror(gmt0);
327*bc421551SDag-Erling Smørgrav 	exit(EXIT_FAILURE);
328*bc421551SDag-Erling Smørgrav       }
329*bc421551SDag-Erling Smørgrav     }
330*bc421551SDag-Erling Smørgrav   }
331*bc421551SDag-Erling Smørgrav }
332*bc421551SDag-Erling Smørgrav 
333*bc421551SDag-Erling Smørgrav /* Convert *TP to UT, storing the broken-down time into *TMP.
334*bc421551SDag-Erling Smørgrav    Return TMP if successful, NULL otherwise.  This is like gmtime_r(TP, TMP),
335*bc421551SDag-Erling Smørgrav    except typically faster if USE_LOCALTIME_RZ.  */
336*bc421551SDag-Erling Smørgrav static struct tm *
337*bc421551SDag-Erling Smørgrav my_gmtime_r(time_t *tp, struct tm *tmp)
338*bc421551SDag-Erling Smørgrav {
339*bc421551SDag-Erling Smørgrav   return USE_LOCALTIME_RZ ? localtime_rz(gmtz, tp, tmp) : gmtime_r(tp, tmp);
340*bc421551SDag-Erling Smørgrav }
341*bc421551SDag-Erling Smørgrav 
342*bc421551SDag-Erling Smørgrav #ifndef TYPECHECK
343*bc421551SDag-Erling Smørgrav # define my_localtime_rz localtime_rz
344*bc421551SDag-Erling Smørgrav #else /* !defined TYPECHECK */
345*bc421551SDag-Erling Smørgrav 
346*bc421551SDag-Erling Smørgrav static struct tm *
347*bc421551SDag-Erling Smørgrav my_localtime_rz(timezone_t tz, time_t *tp, struct tm *tmp)
348*bc421551SDag-Erling Smørgrav {
349*bc421551SDag-Erling Smørgrav 	tmp = localtime_rz(tz, tp, tmp);
350*bc421551SDag-Erling Smørgrav 	if (tmp) {
351*bc421551SDag-Erling Smørgrav 		struct tm	tm;
352*bc421551SDag-Erling Smørgrav 		register time_t	t;
353*bc421551SDag-Erling Smørgrav 
354*bc421551SDag-Erling Smørgrav 		tm = *tmp;
355*bc421551SDag-Erling Smørgrav 		t = mktime_z(tz, &tm);
356*bc421551SDag-Erling Smørgrav 		if (t != *tp) {
357*bc421551SDag-Erling Smørgrav 			fflush(stdout);
358*bc421551SDag-Erling Smørgrav 			fprintf(stderr, "\n%s: ", progname);
359*bc421551SDag-Erling Smørgrav 			fprintf(stderr, tformat(), *tp);
360*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " ->");
361*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " year=%d", tmp->tm_year);
362*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " mon=%d", tmp->tm_mon);
363*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " mday=%d", tmp->tm_mday);
364*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " hour=%d", tmp->tm_hour);
365*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " min=%d", tmp->tm_min);
366*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " sec=%d", tmp->tm_sec);
367*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " isdst=%d", tmp->tm_isdst);
368*bc421551SDag-Erling Smørgrav 			fprintf(stderr, " -> ");
369*bc421551SDag-Erling Smørgrav 			fprintf(stderr, tformat(), t);
370*bc421551SDag-Erling Smørgrav 			fprintf(stderr, "\n");
371*bc421551SDag-Erling Smørgrav 			errout = true;
372*bc421551SDag-Erling Smørgrav 		}
373*bc421551SDag-Erling Smørgrav 	}
374*bc421551SDag-Erling Smørgrav 	return tmp;
375*bc421551SDag-Erling Smørgrav }
376*bc421551SDag-Erling Smørgrav #endif /* !defined TYPECHECK */
377*bc421551SDag-Erling Smørgrav 
378*bc421551SDag-Erling Smørgrav static void
379*bc421551SDag-Erling Smørgrav abbrok(const char *const abbrp, const char *const zone)
380*bc421551SDag-Erling Smørgrav {
381*bc421551SDag-Erling Smørgrav 	register const char *	cp;
382*bc421551SDag-Erling Smørgrav 	register const char *	wp;
383*bc421551SDag-Erling Smørgrav 
384*bc421551SDag-Erling Smørgrav 	if (warned)
385*bc421551SDag-Erling Smørgrav 		return;
386*bc421551SDag-Erling Smørgrav 	cp = abbrp;
387*bc421551SDag-Erling Smørgrav 	while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
388*bc421551SDag-Erling Smørgrav 		++cp;
389*bc421551SDag-Erling Smørgrav 	if (*cp)
390*bc421551SDag-Erling Smørgrav 	  wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
391*bc421551SDag-Erling Smørgrav 	else if (cp - abbrp < 3)
392*bc421551SDag-Erling Smørgrav 	  wp = _("has fewer than 3 characters");
393*bc421551SDag-Erling Smørgrav 	else if (cp - abbrp > 6)
394*bc421551SDag-Erling Smørgrav 	  wp = _("has more than 6 characters");
395*bc421551SDag-Erling Smørgrav 	else
396*bc421551SDag-Erling Smørgrav 	  return;
397*bc421551SDag-Erling Smørgrav 	fflush(stdout);
398*bc421551SDag-Erling Smørgrav 	fprintf(stderr,
399*bc421551SDag-Erling Smørgrav 		_("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
400*bc421551SDag-Erling Smørgrav 		progname, zone, abbrp, wp);
401*bc421551SDag-Erling Smørgrav 	warned = errout = true;
402*bc421551SDag-Erling Smørgrav }
403*bc421551SDag-Erling Smørgrav 
404*bc421551SDag-Erling Smørgrav /* Return a time zone abbreviation.  If the abbreviation needs to be
405*bc421551SDag-Erling Smørgrav    saved, use *BUF (of size *BUFALLOC) to save it, and return the
406*bc421551SDag-Erling Smørgrav    abbreviation in the possibly-reallocated *BUF.  Otherwise, just
407*bc421551SDag-Erling Smørgrav    return the abbreviation.  Get the abbreviation from TMP.
408*bc421551SDag-Erling Smørgrav    Exit on memory allocation failure.  */
409*bc421551SDag-Erling Smørgrav static char const *
410*bc421551SDag-Erling Smørgrav saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
411*bc421551SDag-Erling Smørgrav {
412*bc421551SDag-Erling Smørgrav   char const *ab = abbr(tmp);
413*bc421551SDag-Erling Smørgrav   if (HAVE_LOCALTIME_RZ)
414*bc421551SDag-Erling Smørgrav     return ab;
415*bc421551SDag-Erling Smørgrav   else {
416*bc421551SDag-Erling Smørgrav     size_t ablen = strlen(ab);
417*bc421551SDag-Erling Smørgrav     if ((size_t)*bufalloc <= ablen) {
418*bc421551SDag-Erling Smørgrav       free(*buf);
419*bc421551SDag-Erling Smørgrav 
420*bc421551SDag-Erling Smørgrav       /* Make the new buffer at least twice as long as the old,
421*bc421551SDag-Erling Smørgrav 	 to avoid O(N**2) behavior on repeated calls.  */
422*bc421551SDag-Erling Smørgrav       *bufalloc = sumsize(*bufalloc, ablen + 1);
423*bc421551SDag-Erling Smørgrav 
424*bc421551SDag-Erling Smørgrav       *buf = xmalloc(*bufalloc);
425*bc421551SDag-Erling Smørgrav     }
426*bc421551SDag-Erling Smørgrav     return strcpy(*buf, ab);
427*bc421551SDag-Erling Smørgrav   }
428*bc421551SDag-Erling Smørgrav }
429*bc421551SDag-Erling Smørgrav 
430*bc421551SDag-Erling Smørgrav static void
431*bc421551SDag-Erling Smørgrav close_file(FILE *stream)
432*bc421551SDag-Erling Smørgrav {
433*bc421551SDag-Erling Smørgrav   char const *e = (ferror(stream) ? _("I/O error")
434*bc421551SDag-Erling Smørgrav 		   : fclose(stream) != 0 ? strerror(errno) : NULL);
435*bc421551SDag-Erling Smørgrav   if (e) {
436*bc421551SDag-Erling Smørgrav     fprintf(stderr, "%s: %s\n", progname, e);
437*bc421551SDag-Erling Smørgrav     exit(EXIT_FAILURE);
438*bc421551SDag-Erling Smørgrav   }
439*bc421551SDag-Erling Smørgrav }
440*bc421551SDag-Erling Smørgrav 
441*bc421551SDag-Erling Smørgrav static void
442*bc421551SDag-Erling Smørgrav usage(FILE * const stream, const int status)
443*bc421551SDag-Erling Smørgrav {
444*bc421551SDag-Erling Smørgrav 	fprintf(stream,
445*bc421551SDag-Erling Smørgrav _("%s: usage: %s OPTIONS TIMEZONE ...\n"
446*bc421551SDag-Erling Smørgrav   "Options include:\n"
447*bc421551SDag-Erling Smørgrav   "  -c [L,]U   Start at year L (default -500), end before year U (default 2500)\n"
448*bc421551SDag-Erling Smørgrav   "  -t [L,]U   Start at time L, end before time U (in seconds since 1970)\n"
449*bc421551SDag-Erling Smørgrav   "  -i         List transitions briefly (format is experimental)\n" \
450*bc421551SDag-Erling Smørgrav   "  -v         List transitions verbosely\n"
451*bc421551SDag-Erling Smørgrav   "  -V         List transitions a bit less verbosely\n"
452*bc421551SDag-Erling Smørgrav   "  --help     Output this help\n"
453*bc421551SDag-Erling Smørgrav   "  --version  Output version info\n"
454*bc421551SDag-Erling Smørgrav   "\n"
455*bc421551SDag-Erling Smørgrav   "Report bugs to %s.\n"),
456*bc421551SDag-Erling Smørgrav 		progname, progname, REPORT_BUGS_TO);
457*bc421551SDag-Erling Smørgrav 	if (status == EXIT_SUCCESS)
458*bc421551SDag-Erling Smørgrav 	  close_file(stream);
459*bc421551SDag-Erling Smørgrav 	exit(status);
460*bc421551SDag-Erling Smørgrav }
461*bc421551SDag-Erling Smørgrav 
462*bc421551SDag-Erling Smørgrav int
463*bc421551SDag-Erling Smørgrav main(int argc, char *argv[])
464*bc421551SDag-Erling Smørgrav {
465*bc421551SDag-Erling Smørgrav 	/* These are static so that they're initially zero.  */
466*bc421551SDag-Erling Smørgrav 	static char *		abbrev;
467*bc421551SDag-Erling Smørgrav 	static ptrdiff_t	abbrevsize;
468*bc421551SDag-Erling Smørgrav 
469*bc421551SDag-Erling Smørgrav 	register int		i;
470*bc421551SDag-Erling Smørgrav 	register bool		vflag;
471*bc421551SDag-Erling Smørgrav 	register bool		Vflag;
472*bc421551SDag-Erling Smørgrav 	register char *		cutarg;
473*bc421551SDag-Erling Smørgrav 	register char *		cuttimes;
474*bc421551SDag-Erling Smørgrav 	register time_t		cutlotime;
475*bc421551SDag-Erling Smørgrav 	register time_t		cuthitime;
476*bc421551SDag-Erling Smørgrav 	time_t			now;
477*bc421551SDag-Erling Smørgrav 	bool iflag = false;
478*bc421551SDag-Erling Smørgrav 
479*bc421551SDag-Erling Smørgrav 	cutlotime = absolute_min_time;
480*bc421551SDag-Erling Smørgrav 	cuthitime = absolute_max_time;
481*bc421551SDag-Erling Smørgrav #if HAVE_GETTEXT
482*bc421551SDag-Erling Smørgrav 	setlocale(LC_ALL, "");
483*bc421551SDag-Erling Smørgrav # ifdef TZ_DOMAINDIR
484*bc421551SDag-Erling Smørgrav 	bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
485*bc421551SDag-Erling Smørgrav # endif /* defined TEXTDOMAINDIR */
486*bc421551SDag-Erling Smørgrav 	textdomain(TZ_DOMAIN);
487*bc421551SDag-Erling Smørgrav #endif /* HAVE_GETTEXT */
488*bc421551SDag-Erling Smørgrav 	progname = argv[0] ? argv[0] : "zdump";
489*bc421551SDag-Erling Smørgrav 	for (i = 1; i < argc; ++i)
490*bc421551SDag-Erling Smørgrav 		if (strcmp(argv[i], "--version") == 0) {
491*bc421551SDag-Erling Smørgrav 			printf("zdump %s%s\n", PKGVERSION, TZVERSION);
492*bc421551SDag-Erling Smørgrav 			return EXIT_SUCCESS;
493*bc421551SDag-Erling Smørgrav 		} else if (strcmp(argv[i], "--help") == 0) {
494*bc421551SDag-Erling Smørgrav 			usage(stdout, EXIT_SUCCESS);
495*bc421551SDag-Erling Smørgrav 		}
496*bc421551SDag-Erling Smørgrav 	vflag = Vflag = false;
497*bc421551SDag-Erling Smørgrav 	cutarg = cuttimes = NULL;
498*bc421551SDag-Erling Smørgrav 	for (;;)
499*bc421551SDag-Erling Smørgrav 	  switch (getopt(argc, argv, "c:it:vV")) {
500*bc421551SDag-Erling Smørgrav 	  case 'c': cutarg = optarg; break;
501*bc421551SDag-Erling Smørgrav 	  case 't': cuttimes = optarg; break;
502*bc421551SDag-Erling Smørgrav 	  case 'i': iflag = true; break;
503*bc421551SDag-Erling Smørgrav 	  case 'v': vflag = true; break;
504*bc421551SDag-Erling Smørgrav 	  case 'V': Vflag = true; break;
505*bc421551SDag-Erling Smørgrav 	  case -1:
506*bc421551SDag-Erling Smørgrav 	    if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
507*bc421551SDag-Erling Smørgrav 	      goto arg_processing_done;
508*bc421551SDag-Erling Smørgrav 	    ATTRIBUTE_FALLTHROUGH;
509*bc421551SDag-Erling Smørgrav 	  default:
510*bc421551SDag-Erling Smørgrav 	    usage(stderr, EXIT_FAILURE);
511*bc421551SDag-Erling Smørgrav 	  }
512*bc421551SDag-Erling Smørgrav  arg_processing_done:;
513*bc421551SDag-Erling Smørgrav 
514*bc421551SDag-Erling Smørgrav 	if (iflag | vflag | Vflag) {
515*bc421551SDag-Erling Smørgrav 		intmax_t	lo;
516*bc421551SDag-Erling Smørgrav 		intmax_t	hi;
517*bc421551SDag-Erling Smørgrav 		char *loend, *hiend;
518*bc421551SDag-Erling Smørgrav 		register intmax_t cutloyear = ZDUMP_LO_YEAR;
519*bc421551SDag-Erling Smørgrav 		register intmax_t cuthiyear = ZDUMP_HI_YEAR;
520*bc421551SDag-Erling Smørgrav 		if (cutarg != NULL) {
521*bc421551SDag-Erling Smørgrav 			lo = strtoimax(cutarg, &loend, 10);
522*bc421551SDag-Erling Smørgrav 			if (cutarg != loend && !*loend) {
523*bc421551SDag-Erling Smørgrav 				hi = lo;
524*bc421551SDag-Erling Smørgrav 				cuthiyear = hi;
525*bc421551SDag-Erling Smørgrav 			} else if (cutarg != loend && *loend == ','
526*bc421551SDag-Erling Smørgrav 				   && (hi = strtoimax(loend + 1, &hiend, 10),
527*bc421551SDag-Erling Smørgrav 				       loend + 1 != hiend && !*hiend)) {
528*bc421551SDag-Erling Smørgrav 				cutloyear = lo;
529*bc421551SDag-Erling Smørgrav 				cuthiyear = hi;
530*bc421551SDag-Erling Smørgrav 			} else {
531*bc421551SDag-Erling Smørgrav 				fprintf(stderr, _("%s: wild -c argument %s\n"),
532*bc421551SDag-Erling Smørgrav 					progname, cutarg);
533*bc421551SDag-Erling Smørgrav 				return EXIT_FAILURE;
534*bc421551SDag-Erling Smørgrav 			}
535*bc421551SDag-Erling Smørgrav 		}
536*bc421551SDag-Erling Smørgrav 		if (cutarg != NULL || cuttimes == NULL) {
537*bc421551SDag-Erling Smørgrav 			cutlotime = yeartot(cutloyear);
538*bc421551SDag-Erling Smørgrav 			cuthitime = yeartot(cuthiyear);
539*bc421551SDag-Erling Smørgrav 		}
540*bc421551SDag-Erling Smørgrav 		if (cuttimes != NULL) {
541*bc421551SDag-Erling Smørgrav 			lo = strtoimax(cuttimes, &loend, 10);
542*bc421551SDag-Erling Smørgrav 			if (cuttimes != loend && !*loend) {
543*bc421551SDag-Erling Smørgrav 				hi = lo;
544*bc421551SDag-Erling Smørgrav 				if (hi < cuthitime) {
545*bc421551SDag-Erling Smørgrav 					if (hi < absolute_min_time + 1)
546*bc421551SDag-Erling Smørgrav 					  hi = absolute_min_time + 1;
547*bc421551SDag-Erling Smørgrav 					cuthitime = hi;
548*bc421551SDag-Erling Smørgrav 				}
549*bc421551SDag-Erling Smørgrav 			} else if (cuttimes != loend && *loend == ','
550*bc421551SDag-Erling Smørgrav 				   && (hi = strtoimax(loend + 1, &hiend, 10),
551*bc421551SDag-Erling Smørgrav 				       loend + 1 != hiend && !*hiend)) {
552*bc421551SDag-Erling Smørgrav 				if (cutlotime < lo) {
553*bc421551SDag-Erling Smørgrav 					if (absolute_max_time < lo)
554*bc421551SDag-Erling Smørgrav 						lo = absolute_max_time;
555*bc421551SDag-Erling Smørgrav 					cutlotime = lo;
556*bc421551SDag-Erling Smørgrav 				}
557*bc421551SDag-Erling Smørgrav 				if (hi < cuthitime) {
558*bc421551SDag-Erling Smørgrav 					if (hi < absolute_min_time + 1)
559*bc421551SDag-Erling Smørgrav 					  hi = absolute_min_time + 1;
560*bc421551SDag-Erling Smørgrav 					cuthitime = hi;
561*bc421551SDag-Erling Smørgrav 				}
562*bc421551SDag-Erling Smørgrav 			} else {
563*bc421551SDag-Erling Smørgrav 				fprintf(stderr,
564*bc421551SDag-Erling Smørgrav 					_("%s: wild -t argument %s\n"),
565*bc421551SDag-Erling Smørgrav 					progname, cuttimes);
566*bc421551SDag-Erling Smørgrav 				return EXIT_FAILURE;
567*bc421551SDag-Erling Smørgrav 			}
568*bc421551SDag-Erling Smørgrav 		}
569*bc421551SDag-Erling Smørgrav 	}
570*bc421551SDag-Erling Smørgrav 	gmtzinit();
571*bc421551SDag-Erling Smørgrav 	if (iflag | vflag | Vflag)
572*bc421551SDag-Erling Smørgrav 	  now = 0;
573*bc421551SDag-Erling Smørgrav 	else {
574*bc421551SDag-Erling Smørgrav 	  now = time(NULL);
575*bc421551SDag-Erling Smørgrav 	  now |= !now;
576*bc421551SDag-Erling Smørgrav 	}
577*bc421551SDag-Erling Smørgrav 	longest = 0;
578*bc421551SDag-Erling Smørgrav 	for (i = optind; i < argc; i++) {
579*bc421551SDag-Erling Smørgrav 	  size_t arglen = strlen(argv[i]);
580*bc421551SDag-Erling Smørgrav 	  if (longest < arglen)
581*bc421551SDag-Erling Smørgrav 	    longest = min(arglen, INT_MAX);
582*bc421551SDag-Erling Smørgrav 	}
583*bc421551SDag-Erling Smørgrav 
584*bc421551SDag-Erling Smørgrav 	for (i = optind; i < argc; ++i) {
585*bc421551SDag-Erling Smørgrav 		timezone_t tz = tzalloc(argv[i]);
586*bc421551SDag-Erling Smørgrav 		char const *ab;
587*bc421551SDag-Erling Smørgrav 		time_t t;
588*bc421551SDag-Erling Smørgrav 		struct tm tm, newtm;
589*bc421551SDag-Erling Smørgrav 		bool tm_ok;
590*bc421551SDag-Erling Smørgrav 		if (!tz) {
591*bc421551SDag-Erling Smørgrav 		  perror(argv[i]);
592*bc421551SDag-Erling Smørgrav 		  return EXIT_FAILURE;
593*bc421551SDag-Erling Smørgrav 		}
594*bc421551SDag-Erling Smørgrav 		if (now) {
595*bc421551SDag-Erling Smørgrav 			show(tz, argv[i], now, false);
596*bc421551SDag-Erling Smørgrav 			tzfree(tz);
597*bc421551SDag-Erling Smørgrav 			continue;
598*bc421551SDag-Erling Smørgrav 		}
599*bc421551SDag-Erling Smørgrav 		warned = false;
600*bc421551SDag-Erling Smørgrav 		t = absolute_min_time;
601*bc421551SDag-Erling Smørgrav 		if (! (iflag | Vflag)) {
602*bc421551SDag-Erling Smørgrav 			show(tz, argv[i], t, true);
603*bc421551SDag-Erling Smørgrav 			if (my_localtime_rz(tz, &t, &tm) == NULL
604*bc421551SDag-Erling Smørgrav 			    && t < cutlotime) {
605*bc421551SDag-Erling Smørgrav 				time_t newt = cutlotime;
606*bc421551SDag-Erling Smørgrav 				if (my_localtime_rz(tz, &newt, &newtm) != NULL)
607*bc421551SDag-Erling Smørgrav 				  showextrema(tz, argv[i], t, NULL, newt);
608*bc421551SDag-Erling Smørgrav 			}
609*bc421551SDag-Erling Smørgrav 		}
610*bc421551SDag-Erling Smørgrav 		if (t + 1 < cutlotime)
611*bc421551SDag-Erling Smørgrav 		  t = cutlotime - 1;
612*bc421551SDag-Erling Smørgrav 		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
613*bc421551SDag-Erling Smørgrav 		if (tm_ok) {
614*bc421551SDag-Erling Smørgrav 		  ab = saveabbr(&abbrev, &abbrevsize, &tm);
615*bc421551SDag-Erling Smørgrav 		  if (iflag) {
616*bc421551SDag-Erling Smørgrav 		    showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
617*bc421551SDag-Erling Smørgrav 		    showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
618*bc421551SDag-Erling Smørgrav 		  }
619*bc421551SDag-Erling Smørgrav 		} else
620*bc421551SDag-Erling Smørgrav 		  ab = NULL;
621*bc421551SDag-Erling Smørgrav 		while (t < cuthitime - 1) {
622*bc421551SDag-Erling Smørgrav 		  time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
623*bc421551SDag-Erling Smørgrav 				  && t + SECSPERDAY / 2 < cuthitime - 1)
624*bc421551SDag-Erling Smørgrav 				 ? t + SECSPERDAY / 2
625*bc421551SDag-Erling Smørgrav 				 : cuthitime - 1);
626*bc421551SDag-Erling Smørgrav 		  struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
627*bc421551SDag-Erling Smørgrav 		  bool newtm_ok = newtmp != NULL;
628*bc421551SDag-Erling Smørgrav 		  if (tm_ok != newtm_ok
629*bc421551SDag-Erling Smørgrav 		      || (ab && (delta(&newtm, &tm) != newt - t
630*bc421551SDag-Erling Smørgrav 				 || newtm.tm_isdst != tm.tm_isdst
631*bc421551SDag-Erling Smørgrav 				 || strcmp(abbr(&newtm), ab) != 0))) {
632*bc421551SDag-Erling Smørgrav 		    newt = hunt(tz, t, newt, false);
633*bc421551SDag-Erling Smørgrav 		    newtmp = localtime_rz(tz, &newt, &newtm);
634*bc421551SDag-Erling Smørgrav 		    newtm_ok = newtmp != NULL;
635*bc421551SDag-Erling Smørgrav 		    if (iflag)
636*bc421551SDag-Erling Smørgrav 		      showtrans("%Y-%m-%d\t%L\t%Q", newtmp, newt,
637*bc421551SDag-Erling Smørgrav 				newtm_ok ? abbr(&newtm) : NULL, argv[i]);
638*bc421551SDag-Erling Smørgrav 		    else {
639*bc421551SDag-Erling Smørgrav 		      show(tz, argv[i], newt - 1, true);
640*bc421551SDag-Erling Smørgrav 		      show(tz, argv[i], newt, true);
641*bc421551SDag-Erling Smørgrav 		    }
642*bc421551SDag-Erling Smørgrav 		  }
643*bc421551SDag-Erling Smørgrav 		  t = newt;
644*bc421551SDag-Erling Smørgrav 		  tm_ok = newtm_ok;
645*bc421551SDag-Erling Smørgrav 		  if (newtm_ok) {
646*bc421551SDag-Erling Smørgrav 		    ab = saveabbr(&abbrev, &abbrevsize, &newtm);
647*bc421551SDag-Erling Smørgrav 		    tm = newtm;
648*bc421551SDag-Erling Smørgrav 		  }
649*bc421551SDag-Erling Smørgrav 		}
650*bc421551SDag-Erling Smørgrav 		if (! (iflag | Vflag)) {
651*bc421551SDag-Erling Smørgrav 			time_t newt = absolute_max_time;
652*bc421551SDag-Erling Smørgrav 			t = cuthitime;
653*bc421551SDag-Erling Smørgrav 			if (t < newt) {
654*bc421551SDag-Erling Smørgrav 			  struct tm *tmp = my_localtime_rz(tz, &t, &tm);
655*bc421551SDag-Erling Smørgrav 			  if (tmp != NULL
656*bc421551SDag-Erling Smørgrav 			      && my_localtime_rz(tz, &newt, &newtm) == NULL)
657*bc421551SDag-Erling Smørgrav 			    showextrema(tz, argv[i], t, tmp, newt);
658*bc421551SDag-Erling Smørgrav 			}
659*bc421551SDag-Erling Smørgrav 			show(tz, argv[i], absolute_max_time, true);
660*bc421551SDag-Erling Smørgrav 		}
661*bc421551SDag-Erling Smørgrav 		tzfree(tz);
662*bc421551SDag-Erling Smørgrav 	}
663*bc421551SDag-Erling Smørgrav 	close_file(stdout);
664*bc421551SDag-Erling Smørgrav 	if (errout && (ferror(stderr) || fclose(stderr) != 0))
665*bc421551SDag-Erling Smørgrav 	  return EXIT_FAILURE;
666*bc421551SDag-Erling Smørgrav 	return EXIT_SUCCESS;
667*bc421551SDag-Erling Smørgrav }
668*bc421551SDag-Erling Smørgrav 
669*bc421551SDag-Erling Smørgrav static time_t
670*bc421551SDag-Erling Smørgrav yeartot(intmax_t y)
671*bc421551SDag-Erling Smørgrav {
672*bc421551SDag-Erling Smørgrav 	register intmax_t	myy, seconds, years;
673*bc421551SDag-Erling Smørgrav 	register time_t		t;
674*bc421551SDag-Erling Smørgrav 
675*bc421551SDag-Erling Smørgrav 	myy = EPOCH_YEAR;
676*bc421551SDag-Erling Smørgrav 	t = 0;
677*bc421551SDag-Erling Smørgrav 	while (myy < y) {
678*bc421551SDag-Erling Smørgrav 		if (SECSPER400YEARS_FITS && 400 <= y - myy) {
679*bc421551SDag-Erling Smørgrav 			intmax_t diff400 = (y - myy) / 400;
680*bc421551SDag-Erling Smørgrav 			if (INTMAX_MAX / SECSPER400YEARS < diff400)
681*bc421551SDag-Erling Smørgrav 				return absolute_max_time;
682*bc421551SDag-Erling Smørgrav 			seconds = diff400 * SECSPER400YEARS;
683*bc421551SDag-Erling Smørgrav 			years = diff400 * 400;
684*bc421551SDag-Erling Smørgrav                 } else {
685*bc421551SDag-Erling Smørgrav 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
686*bc421551SDag-Erling Smørgrav 			years = 1;
687*bc421551SDag-Erling Smørgrav 		}
688*bc421551SDag-Erling Smørgrav 		myy += years;
689*bc421551SDag-Erling Smørgrav 		if (t > absolute_max_time - seconds)
690*bc421551SDag-Erling Smørgrav 			return absolute_max_time;
691*bc421551SDag-Erling Smørgrav 		t += seconds;
692*bc421551SDag-Erling Smørgrav 	}
693*bc421551SDag-Erling Smørgrav 	while (y < myy) {
694*bc421551SDag-Erling Smørgrav 		if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
695*bc421551SDag-Erling Smørgrav 			intmax_t diff400 = (myy - y) / 400;
696*bc421551SDag-Erling Smørgrav 			if (INTMAX_MAX / SECSPER400YEARS < diff400)
697*bc421551SDag-Erling Smørgrav 				return absolute_min_time;
698*bc421551SDag-Erling Smørgrav 			seconds = diff400 * SECSPER400YEARS;
699*bc421551SDag-Erling Smørgrav 			years = diff400 * 400;
700*bc421551SDag-Erling Smørgrav 		} else {
701*bc421551SDag-Erling Smørgrav 			seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
702*bc421551SDag-Erling Smørgrav 			years = 1;
703*bc421551SDag-Erling Smørgrav 		}
704*bc421551SDag-Erling Smørgrav 		myy -= years;
705*bc421551SDag-Erling Smørgrav 		if (t < absolute_min_time + seconds)
706*bc421551SDag-Erling Smørgrav 			return absolute_min_time;
707*bc421551SDag-Erling Smørgrav 		t -= seconds;
708*bc421551SDag-Erling Smørgrav 	}
709*bc421551SDag-Erling Smørgrav 	return t;
710*bc421551SDag-Erling Smørgrav }
711*bc421551SDag-Erling Smørgrav 
712*bc421551SDag-Erling Smørgrav /* Search for a discontinuity in timezone TZ, in the
713*bc421551SDag-Erling Smørgrav    timestamps ranging from LOT through HIT.  LOT and HIT disagree
714*bc421551SDag-Erling Smørgrav    about some aspect of timezone.  If ONLY_OK, search only for
715*bc421551SDag-Erling Smørgrav    definedness changes, i.e., localtime succeeds on one side of the
716*bc421551SDag-Erling Smørgrav    transition but fails on the other side.  Return the timestamp just
717*bc421551SDag-Erling Smørgrav    before the transition from LOT's settings.  */
718*bc421551SDag-Erling Smørgrav 
719*bc421551SDag-Erling Smørgrav static time_t
720*bc421551SDag-Erling Smørgrav hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
721*bc421551SDag-Erling Smørgrav {
722*bc421551SDag-Erling Smørgrav 	static char *		loab;
723*bc421551SDag-Erling Smørgrav 	static ptrdiff_t	loabsize;
724*bc421551SDag-Erling Smørgrav 	struct tm		lotm;
725*bc421551SDag-Erling Smørgrav 	struct tm		tm;
726*bc421551SDag-Erling Smørgrav 
727*bc421551SDag-Erling Smørgrav 	/* Convert LOT into a broken-down time here, even though our
728*bc421551SDag-Erling Smørgrav 	   caller already did that.  On platforms without TM_ZONE,
729*bc421551SDag-Erling Smørgrav 	   tzname may have been altered since our caller broke down
730*bc421551SDag-Erling Smørgrav 	   LOT, and tzname needs to be changed back.  */
731*bc421551SDag-Erling Smørgrav 	bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
732*bc421551SDag-Erling Smørgrav 	bool tm_ok;
733*bc421551SDag-Erling Smørgrav 	char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL;
734*bc421551SDag-Erling Smørgrav 
735*bc421551SDag-Erling Smørgrav 	for ( ; ; ) {
736*bc421551SDag-Erling Smørgrav 		/* T = average of LOT and HIT, rounding down.
737*bc421551SDag-Erling Smørgrav 		   Avoid overflow, even on oddball C89 platforms
738*bc421551SDag-Erling Smørgrav 		   where / rounds down and TIME_T_MIN == -TIME_T_MAX
739*bc421551SDag-Erling Smørgrav 		   so lot / 2 + hit / 2 might overflow.  */
740*bc421551SDag-Erling Smørgrav 		time_t t = (lot / 2
741*bc421551SDag-Erling Smørgrav 			    - ((lot % 2 + hit % 2) < 0)
742*bc421551SDag-Erling Smørgrav 			    + ((lot % 2 + hit % 2) == 2)
743*bc421551SDag-Erling Smørgrav 			    + hit / 2);
744*bc421551SDag-Erling Smørgrav 		if (t == lot)
745*bc421551SDag-Erling Smørgrav 			break;
746*bc421551SDag-Erling Smørgrav 		tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
747*bc421551SDag-Erling Smørgrav 		if (lotm_ok == tm_ok
748*bc421551SDag-Erling Smørgrav 		    && (only_ok
749*bc421551SDag-Erling Smørgrav 			|| (ab && tm.tm_isdst == lotm.tm_isdst
750*bc421551SDag-Erling Smørgrav 			    && delta(&tm, &lotm) == t - lot
751*bc421551SDag-Erling Smørgrav 			    && strcmp(abbr(&tm), ab) == 0))) {
752*bc421551SDag-Erling Smørgrav 		  lot = t;
753*bc421551SDag-Erling Smørgrav 		  if (tm_ok)
754*bc421551SDag-Erling Smørgrav 		    lotm = tm;
755*bc421551SDag-Erling Smørgrav 		} else	hit = t;
756*bc421551SDag-Erling Smørgrav 	}
757*bc421551SDag-Erling Smørgrav 	return hit;
758*bc421551SDag-Erling Smørgrav }
759*bc421551SDag-Erling Smørgrav 
760*bc421551SDag-Erling Smørgrav /*
761*bc421551SDag-Erling Smørgrav ** Thanks to Paul Eggert for logic used in delta_nonneg.
762*bc421551SDag-Erling Smørgrav */
763*bc421551SDag-Erling Smørgrav 
764*bc421551SDag-Erling Smørgrav static intmax_t
765*bc421551SDag-Erling Smørgrav delta_nonneg(struct tm *newp, struct tm *oldp)
766*bc421551SDag-Erling Smørgrav {
767*bc421551SDag-Erling Smørgrav 	intmax_t oldy = oldp->tm_year;
768*bc421551SDag-Erling Smørgrav 	int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT;
769*bc421551SDag-Erling Smørgrav 	intmax_t sec = SECSPERREPEAT, result = cycles * sec;
770*bc421551SDag-Erling Smørgrav 	int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
771*bc421551SDag-Erling Smørgrav 	for ( ; tmy < newp->tm_year; ++tmy)
772*bc421551SDag-Erling Smørgrav 		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
773*bc421551SDag-Erling Smørgrav 	result += newp->tm_yday - oldp->tm_yday;
774*bc421551SDag-Erling Smørgrav 	result *= HOURSPERDAY;
775*bc421551SDag-Erling Smørgrav 	result += newp->tm_hour - oldp->tm_hour;
776*bc421551SDag-Erling Smørgrav 	result *= MINSPERHOUR;
777*bc421551SDag-Erling Smørgrav 	result += newp->tm_min - oldp->tm_min;
778*bc421551SDag-Erling Smørgrav 	result *= SECSPERMIN;
779*bc421551SDag-Erling Smørgrav 	result += newp->tm_sec - oldp->tm_sec;
780*bc421551SDag-Erling Smørgrav 	return result;
781*bc421551SDag-Erling Smørgrav }
782*bc421551SDag-Erling Smørgrav 
783*bc421551SDag-Erling Smørgrav static intmax_t
784*bc421551SDag-Erling Smørgrav delta(struct tm *newp, struct tm *oldp)
785*bc421551SDag-Erling Smørgrav {
786*bc421551SDag-Erling Smørgrav   return (newp->tm_year < oldp->tm_year
787*bc421551SDag-Erling Smørgrav 	  ? -delta_nonneg(oldp, newp)
788*bc421551SDag-Erling Smørgrav 	  : delta_nonneg(newp, oldp));
789*bc421551SDag-Erling Smørgrav }
790*bc421551SDag-Erling Smørgrav 
791*bc421551SDag-Erling Smørgrav #ifndef TM_GMTOFF
792*bc421551SDag-Erling Smørgrav /* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
793*bc421551SDag-Erling Smørgrav    Assume A and B differ by at most one year.  */
794*bc421551SDag-Erling Smørgrav static int
795*bc421551SDag-Erling Smørgrav adjusted_yday(struct tm const *a, struct tm const *b)
796*bc421551SDag-Erling Smørgrav {
797*bc421551SDag-Erling Smørgrav   int yday = a->tm_yday;
798*bc421551SDag-Erling Smørgrav   if (b->tm_year < a->tm_year)
799*bc421551SDag-Erling Smørgrav     yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
800*bc421551SDag-Erling Smørgrav   return yday;
801*bc421551SDag-Erling Smørgrav }
802*bc421551SDag-Erling Smørgrav #endif
803*bc421551SDag-Erling Smørgrav 
804*bc421551SDag-Erling Smørgrav /* If A is the broken-down local time and B the broken-down UT for
805*bc421551SDag-Erling Smørgrav    the same instant, return A's UT offset in seconds, where positive
806*bc421551SDag-Erling Smørgrav    offsets are east of Greenwich.  On failure, return LONG_MIN.
807*bc421551SDag-Erling Smørgrav 
808*bc421551SDag-Erling Smørgrav    If T is nonnull, *T is the timestamp that corresponds to A; call
809*bc421551SDag-Erling Smørgrav    my_gmtime_r and use its result instead of B.  Otherwise, B is the
810*bc421551SDag-Erling Smørgrav    possibly nonnull result of an earlier call to my_gmtime_r.  */
811*bc421551SDag-Erling Smørgrav static long
812*bc421551SDag-Erling Smørgrav gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
813*bc421551SDag-Erling Smørgrav        ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
814*bc421551SDag-Erling Smørgrav {
815*bc421551SDag-Erling Smørgrav #ifdef TM_GMTOFF
816*bc421551SDag-Erling Smørgrav   return a->TM_GMTOFF;
817*bc421551SDag-Erling Smørgrav #else
818*bc421551SDag-Erling Smørgrav   struct tm tm;
819*bc421551SDag-Erling Smørgrav   if (t)
820*bc421551SDag-Erling Smørgrav     b = my_gmtime_r(t, &tm);
821*bc421551SDag-Erling Smørgrav   if (! b)
822*bc421551SDag-Erling Smørgrav     return LONG_MIN;
823*bc421551SDag-Erling Smørgrav   else {
824*bc421551SDag-Erling Smørgrav     int ayday = adjusted_yday(a, b);
825*bc421551SDag-Erling Smørgrav     int byday = adjusted_yday(b, a);
826*bc421551SDag-Erling Smørgrav     int days = ayday - byday;
827*bc421551SDag-Erling Smørgrav     long hours = a->tm_hour - b->tm_hour + 24 * days;
828*bc421551SDag-Erling Smørgrav     long minutes = a->tm_min - b->tm_min + 60 * hours;
829*bc421551SDag-Erling Smørgrav     long seconds = a->tm_sec - b->tm_sec + 60 * minutes;
830*bc421551SDag-Erling Smørgrav     return seconds;
831*bc421551SDag-Erling Smørgrav   }
832*bc421551SDag-Erling Smørgrav #endif
833*bc421551SDag-Erling Smørgrav }
834*bc421551SDag-Erling Smørgrav 
835*bc421551SDag-Erling Smørgrav static void
836*bc421551SDag-Erling Smørgrav show(timezone_t tz, char *zone, time_t t, bool v)
837*bc421551SDag-Erling Smørgrav {
838*bc421551SDag-Erling Smørgrav 	register struct tm *	tmp;
839*bc421551SDag-Erling Smørgrav 	register struct tm *	gmtmp;
840*bc421551SDag-Erling Smørgrav 	struct tm tm, gmtm;
841*bc421551SDag-Erling Smørgrav 
842*bc421551SDag-Erling Smørgrav 	printf("%-*s  ", (int)longest, zone);
843*bc421551SDag-Erling Smørgrav 	if (v) {
844*bc421551SDag-Erling Smørgrav 		gmtmp = my_gmtime_r(&t, &gmtm);
845*bc421551SDag-Erling Smørgrav 		if (gmtmp == NULL) {
846*bc421551SDag-Erling Smørgrav 			printf(tformat(), t);
847*bc421551SDag-Erling Smørgrav 			printf(_(" (gmtime failed)"));
848*bc421551SDag-Erling Smørgrav 		} else {
849*bc421551SDag-Erling Smørgrav 			dumptime(gmtmp);
850*bc421551SDag-Erling Smørgrav 			printf(" UT");
851*bc421551SDag-Erling Smørgrav 		}
852*bc421551SDag-Erling Smørgrav 		printf(" = ");
853*bc421551SDag-Erling Smørgrav 	}
854*bc421551SDag-Erling Smørgrav 	tmp = my_localtime_rz(tz, &t, &tm);
855*bc421551SDag-Erling Smørgrav 	if (tmp == NULL) {
856*bc421551SDag-Erling Smørgrav 		printf(tformat(), t);
857*bc421551SDag-Erling Smørgrav 		printf(_(" (localtime failed)"));
858*bc421551SDag-Erling Smørgrav 	} else {
859*bc421551SDag-Erling Smørgrav 		dumptime(tmp);
860*bc421551SDag-Erling Smørgrav 		if (*abbr(tmp) != '\0')
861*bc421551SDag-Erling Smørgrav 			printf(" %s", abbr(tmp));
862*bc421551SDag-Erling Smørgrav 		if (v) {
863*bc421551SDag-Erling Smørgrav 			long off = gmtoff(tmp, NULL, gmtmp);
864*bc421551SDag-Erling Smørgrav 			printf(" isdst=%d", tmp->tm_isdst);
865*bc421551SDag-Erling Smørgrav 			if (off != LONG_MIN)
866*bc421551SDag-Erling Smørgrav 			  printf(" gmtoff=%ld", off);
867*bc421551SDag-Erling Smørgrav 		}
868*bc421551SDag-Erling Smørgrav 	}
869*bc421551SDag-Erling Smørgrav 	printf("\n");
870*bc421551SDag-Erling Smørgrav 	if (tmp != NULL && *abbr(tmp) != '\0')
871*bc421551SDag-Erling Smørgrav 		abbrok(abbr(tmp), zone);
872*bc421551SDag-Erling Smørgrav }
873*bc421551SDag-Erling Smørgrav 
874*bc421551SDag-Erling Smørgrav /* Show timestamps just before and just after a transition between
875*bc421551SDag-Erling Smørgrav    defined and undefined (or vice versa) in either localtime or
876*bc421551SDag-Erling Smørgrav    gmtime.  These transitions are for timezone TZ with name ZONE, in
877*bc421551SDag-Erling Smørgrav    the range from LO (with broken-down time LOTMP if that is nonnull)
878*bc421551SDag-Erling Smørgrav    through HI.  LO and HI disagree on definedness.  */
879*bc421551SDag-Erling Smørgrav 
880*bc421551SDag-Erling Smørgrav static void
881*bc421551SDag-Erling Smørgrav showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
882*bc421551SDag-Erling Smørgrav {
883*bc421551SDag-Erling Smørgrav   struct tm localtm[2], gmtm[2];
884*bc421551SDag-Erling Smørgrav   time_t t, boundary = hunt(tz, lo, hi, true);
885*bc421551SDag-Erling Smørgrav   bool old = false;
886*bc421551SDag-Erling Smørgrav   hi = (SECSPERDAY < hi - boundary
887*bc421551SDag-Erling Smørgrav 	? boundary + SECSPERDAY
888*bc421551SDag-Erling Smørgrav 	: hi + (hi < TIME_T_MAX));
889*bc421551SDag-Erling Smørgrav   if (SECSPERDAY < boundary - lo) {
890*bc421551SDag-Erling Smørgrav     lo = boundary - SECSPERDAY;
891*bc421551SDag-Erling Smørgrav     lotmp = my_localtime_rz(tz, &lo, &localtm[old]);
892*bc421551SDag-Erling Smørgrav   }
893*bc421551SDag-Erling Smørgrav   if (lotmp)
894*bc421551SDag-Erling Smørgrav     localtm[old] = *lotmp;
895*bc421551SDag-Erling Smørgrav   else
896*bc421551SDag-Erling Smørgrav     localtm[old].tm_sec = -1;
897*bc421551SDag-Erling Smørgrav   if (! my_gmtime_r(&lo, &gmtm[old]))
898*bc421551SDag-Erling Smørgrav     gmtm[old].tm_sec = -1;
899*bc421551SDag-Erling Smørgrav 
900*bc421551SDag-Erling Smørgrav   /* Search sequentially for definedness transitions.  Although this
901*bc421551SDag-Erling Smørgrav      could be sped up by refining 'hunt' to search for either
902*bc421551SDag-Erling Smørgrav      localtime or gmtime definedness transitions, it hardly seems
903*bc421551SDag-Erling Smørgrav      worth the trouble.  */
904*bc421551SDag-Erling Smørgrav   for (t = lo + 1; t < hi; t++) {
905*bc421551SDag-Erling Smørgrav     bool new = !old;
906*bc421551SDag-Erling Smørgrav     if (! my_localtime_rz(tz, &t, &localtm[new]))
907*bc421551SDag-Erling Smørgrav       localtm[new].tm_sec = -1;
908*bc421551SDag-Erling Smørgrav     if (! my_gmtime_r(&t, &gmtm[new]))
909*bc421551SDag-Erling Smørgrav       gmtm[new].tm_sec = -1;
910*bc421551SDag-Erling Smørgrav     if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))
911*bc421551SDag-Erling Smørgrav 	| ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {
912*bc421551SDag-Erling Smørgrav       show(tz, zone, t - 1, true);
913*bc421551SDag-Erling Smørgrav       show(tz, zone, t, true);
914*bc421551SDag-Erling Smørgrav     }
915*bc421551SDag-Erling Smørgrav     old = new;
916*bc421551SDag-Erling Smørgrav   }
917*bc421551SDag-Erling Smørgrav }
918*bc421551SDag-Erling Smørgrav 
919*bc421551SDag-Erling Smørgrav #if HAVE_SNPRINTF
920*bc421551SDag-Erling Smørgrav # define my_snprintf snprintf
921*bc421551SDag-Erling Smørgrav #else
922*bc421551SDag-Erling Smørgrav # include <stdarg.h>
923*bc421551SDag-Erling Smørgrav 
924*bc421551SDag-Erling Smørgrav /* A substitute for snprintf that is good enough for zdump.  */
925*bc421551SDag-Erling Smørgrav static int ATTRIBUTE_FORMAT((printf, 3, 4))
926*bc421551SDag-Erling Smørgrav my_snprintf(char *s, size_t size, char const *format, ...)
927*bc421551SDag-Erling Smørgrav {
928*bc421551SDag-Erling Smørgrav   int n;
929*bc421551SDag-Erling Smørgrav   va_list args;
930*bc421551SDag-Erling Smørgrav   char const *arg;
931*bc421551SDag-Erling Smørgrav   size_t arglen, slen;
932*bc421551SDag-Erling Smørgrav   char buf[1024];
933*bc421551SDag-Erling Smørgrav   va_start(args, format);
934*bc421551SDag-Erling Smørgrav   if (strcmp(format, "%s") == 0) {
935*bc421551SDag-Erling Smørgrav     arg = va_arg(args, char const *);
936*bc421551SDag-Erling Smørgrav     arglen = strlen(arg);
937*bc421551SDag-Erling Smørgrav   } else {
938*bc421551SDag-Erling Smørgrav     n = vsprintf(buf, format, args);
939*bc421551SDag-Erling Smørgrav     if (n < 0) {
940*bc421551SDag-Erling Smørgrav       va_end(args);
941*bc421551SDag-Erling Smørgrav       return n;
942*bc421551SDag-Erling Smørgrav     }
943*bc421551SDag-Erling Smørgrav     arg = buf;
944*bc421551SDag-Erling Smørgrav     arglen = n;
945*bc421551SDag-Erling Smørgrav   }
946*bc421551SDag-Erling Smørgrav   slen = arglen < size ? arglen : size - 1;
947*bc421551SDag-Erling Smørgrav   memcpy(s, arg, slen);
948*bc421551SDag-Erling Smørgrav   s[slen] = '\0';
949*bc421551SDag-Erling Smørgrav   n = arglen <= INT_MAX ? arglen : -1;
950*bc421551SDag-Erling Smørgrav   va_end(args);
951*bc421551SDag-Erling Smørgrav   return n;
952*bc421551SDag-Erling Smørgrav }
953*bc421551SDag-Erling Smørgrav #endif
954*bc421551SDag-Erling Smørgrav 
955*bc421551SDag-Erling Smørgrav /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
956*bc421551SDag-Erling Smørgrav    Use ISO 8601 format +HH:MM:SS.  Omit :SS if SS is zero, and omit
957*bc421551SDag-Erling Smørgrav    :MM too if MM is also zero.
958*bc421551SDag-Erling Smørgrav 
959*bc421551SDag-Erling Smørgrav    Return the length of the resulting string.  If the string does not
960*bc421551SDag-Erling Smørgrav    fit, return the length that the string would have been if it had
961*bc421551SDag-Erling Smørgrav    fit; do not overrun the output buffer.  */
962*bc421551SDag-Erling Smørgrav static int
963*bc421551SDag-Erling Smørgrav format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
964*bc421551SDag-Erling Smørgrav {
965*bc421551SDag-Erling Smørgrav   int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
966*bc421551SDag-Erling Smørgrav   return (ss
967*bc421551SDag-Erling Smørgrav 	  ? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
968*bc421551SDag-Erling Smørgrav 	  : mm
969*bc421551SDag-Erling Smørgrav 	  ? my_snprintf(buf, size, "%02d:%02d", hh, mm)
970*bc421551SDag-Erling Smørgrav 	  : my_snprintf(buf, size, "%02d", hh));
971*bc421551SDag-Erling Smørgrav }
972*bc421551SDag-Erling Smørgrav 
973*bc421551SDag-Erling Smørgrav /* Store into BUF, of size SIZE, a formatted UT offset for the
974*bc421551SDag-Erling Smørgrav    localtime *TM corresponding to time T.  Use ISO 8601 format
975*bc421551SDag-Erling Smørgrav    +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
976*bc421551SDag-Erling Smørgrav    format -00 for unknown UT offsets.  If the hour needs more than
977*bc421551SDag-Erling Smørgrav    two digits to represent, extend the length of HH as needed.
978*bc421551SDag-Erling Smørgrav    Otherwise, omit SS if SS is zero, and omit MM too if MM is also
979*bc421551SDag-Erling Smørgrav    zero.
980*bc421551SDag-Erling Smørgrav 
981*bc421551SDag-Erling Smørgrav    Return the length of the resulting string, or -1 if the result is
982*bc421551SDag-Erling Smørgrav    not representable as a string.  If the string does not fit, return
983*bc421551SDag-Erling Smørgrav    the length that the string would have been if it had fit; do not
984*bc421551SDag-Erling Smørgrav    overrun the output buffer.  */
985*bc421551SDag-Erling Smørgrav static int
986*bc421551SDag-Erling Smørgrav format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
987*bc421551SDag-Erling Smørgrav {
988*bc421551SDag-Erling Smørgrav   long off = gmtoff(tm, &t, NULL);
989*bc421551SDag-Erling Smørgrav   char sign = ((off < 0
990*bc421551SDag-Erling Smørgrav 		|| (off == 0
991*bc421551SDag-Erling Smørgrav 		    && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
992*bc421551SDag-Erling Smørgrav 	       ? '-' : '+');
993*bc421551SDag-Erling Smørgrav   long hh;
994*bc421551SDag-Erling Smørgrav   int mm, ss;
995*bc421551SDag-Erling Smørgrav   if (off < 0)
996*bc421551SDag-Erling Smørgrav     {
997*bc421551SDag-Erling Smørgrav       if (off == LONG_MIN)
998*bc421551SDag-Erling Smørgrav 	return -1;
999*bc421551SDag-Erling Smørgrav       off = -off;
1000*bc421551SDag-Erling Smørgrav     }
1001*bc421551SDag-Erling Smørgrav   ss = off % 60;
1002*bc421551SDag-Erling Smørgrav   mm = off / 60 % 60;
1003*bc421551SDag-Erling Smørgrav   hh = off / 60 / 60;
1004*bc421551SDag-Erling Smørgrav   return (ss || 100 <= hh
1005*bc421551SDag-Erling Smørgrav 	  ? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
1006*bc421551SDag-Erling Smørgrav 	  : mm
1007*bc421551SDag-Erling Smørgrav 	  ? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
1008*bc421551SDag-Erling Smørgrav 	  : my_snprintf(buf, size, "%c%02ld", sign, hh));
1009*bc421551SDag-Erling Smørgrav }
1010*bc421551SDag-Erling Smørgrav 
1011*bc421551SDag-Erling Smørgrav /* Store into BUF (of size SIZE) a quoted string representation of P.
1012*bc421551SDag-Erling Smørgrav    If the representation's length is less than SIZE, return the
1013*bc421551SDag-Erling Smørgrav    length; the representation is not null terminated.  Otherwise
1014*bc421551SDag-Erling Smørgrav    return SIZE, to indicate that BUF is too small.  */
1015*bc421551SDag-Erling Smørgrav static ptrdiff_t
1016*bc421551SDag-Erling Smørgrav format_quoted_string(char *buf, ptrdiff_t size, char const *p)
1017*bc421551SDag-Erling Smørgrav {
1018*bc421551SDag-Erling Smørgrav   char *b = buf;
1019*bc421551SDag-Erling Smørgrav   ptrdiff_t s = size;
1020*bc421551SDag-Erling Smørgrav   if (!s)
1021*bc421551SDag-Erling Smørgrav     return size;
1022*bc421551SDag-Erling Smørgrav   *b++ = '"', s--;
1023*bc421551SDag-Erling Smørgrav   for (;;) {
1024*bc421551SDag-Erling Smørgrav     char c = *p++;
1025*bc421551SDag-Erling Smørgrav     if (s <= 1)
1026*bc421551SDag-Erling Smørgrav       return size;
1027*bc421551SDag-Erling Smørgrav     switch (c) {
1028*bc421551SDag-Erling Smørgrav     default: *b++ = c, s--; continue;
1029*bc421551SDag-Erling Smørgrav     case '\0': *b++ = '"', s--; return size - s;
1030*bc421551SDag-Erling Smørgrav     case '"': case '\\': break;
1031*bc421551SDag-Erling Smørgrav     case ' ': c = 's'; break;
1032*bc421551SDag-Erling Smørgrav     case '\f': c = 'f'; break;
1033*bc421551SDag-Erling Smørgrav     case '\n': c = 'n'; break;
1034*bc421551SDag-Erling Smørgrav     case '\r': c = 'r'; break;
1035*bc421551SDag-Erling Smørgrav     case '\t': c = 't'; break;
1036*bc421551SDag-Erling Smørgrav     case '\v': c = 'v'; break;
1037*bc421551SDag-Erling Smørgrav     }
1038*bc421551SDag-Erling Smørgrav     *b++ = '\\', *b++ = c, s -= 2;
1039*bc421551SDag-Erling Smørgrav   }
1040*bc421551SDag-Erling Smørgrav }
1041*bc421551SDag-Erling Smørgrav 
1042*bc421551SDag-Erling Smørgrav /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
1043*bc421551SDag-Erling Smørgrav    TM is the broken-down time, T the seconds count, AB the time zone
1044*bc421551SDag-Erling Smørgrav    abbreviation, and ZONE_NAME the zone name.  Return true if
1045*bc421551SDag-Erling Smørgrav    successful, false if the output would require more than SIZE bytes.
1046*bc421551SDag-Erling Smørgrav    TIME_FMT uses the same format that strftime uses, with these
1047*bc421551SDag-Erling Smørgrav    additions:
1048*bc421551SDag-Erling Smørgrav 
1049*bc421551SDag-Erling Smørgrav    %f zone name
1050*bc421551SDag-Erling Smørgrav    %L local time as per format_local_time
1051*bc421551SDag-Erling Smørgrav    %Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
1052*bc421551SDag-Erling Smørgrav       and D is the isdst flag; except omit D if it is zero, omit %Z if
1053*bc421551SDag-Erling Smørgrav       it equals U, quote and escape %Z if it contains nonalphabetics,
1054*bc421551SDag-Erling Smørgrav       and omit any trailing tabs.  */
1055*bc421551SDag-Erling Smørgrav 
1056*bc421551SDag-Erling Smørgrav static bool
1057*bc421551SDag-Erling Smørgrav istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
1058*bc421551SDag-Erling Smørgrav 	  struct tm const *tm, time_t t, char const *ab, char const *zone_name)
1059*bc421551SDag-Erling Smørgrav {
1060*bc421551SDag-Erling Smørgrav   char *b = buf;
1061*bc421551SDag-Erling Smørgrav   ptrdiff_t s = size;
1062*bc421551SDag-Erling Smørgrav   char const *f = time_fmt, *p;
1063*bc421551SDag-Erling Smørgrav 
1064*bc421551SDag-Erling Smørgrav   for (p = f; ; p++)
1065*bc421551SDag-Erling Smørgrav     if (*p == '%' && p[1] == '%')
1066*bc421551SDag-Erling Smørgrav       p++;
1067*bc421551SDag-Erling Smørgrav     else if (!*p
1068*bc421551SDag-Erling Smørgrav 	     || (*p == '%'
1069*bc421551SDag-Erling Smørgrav 		 && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
1070*bc421551SDag-Erling Smørgrav       ptrdiff_t formatted_len;
1071*bc421551SDag-Erling Smørgrav       ptrdiff_t f_prefix_len = p - f;
1072*bc421551SDag-Erling Smørgrav       ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2);
1073*bc421551SDag-Erling Smørgrav       char fbuf[100];
1074*bc421551SDag-Erling Smørgrav       bool oversized = sizeof fbuf <= (size_t)f_prefix_copy_size;
1075*bc421551SDag-Erling Smørgrav       char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
1076*bc421551SDag-Erling Smørgrav       memcpy(f_prefix_copy, f, f_prefix_len);
1077*bc421551SDag-Erling Smørgrav       strcpy(f_prefix_copy + f_prefix_len, "X");
1078*bc421551SDag-Erling Smørgrav       formatted_len = strftime(b, s, f_prefix_copy, tm);
1079*bc421551SDag-Erling Smørgrav       if (oversized)
1080*bc421551SDag-Erling Smørgrav 	free(f_prefix_copy);
1081*bc421551SDag-Erling Smørgrav       if (formatted_len == 0)
1082*bc421551SDag-Erling Smørgrav 	return false;
1083*bc421551SDag-Erling Smørgrav       formatted_len--;
1084*bc421551SDag-Erling Smørgrav       b += formatted_len, s -= formatted_len;
1085*bc421551SDag-Erling Smørgrav       if (!*p++)
1086*bc421551SDag-Erling Smørgrav 	break;
1087*bc421551SDag-Erling Smørgrav       switch (*p) {
1088*bc421551SDag-Erling Smørgrav       case 'f':
1089*bc421551SDag-Erling Smørgrav 	formatted_len = format_quoted_string(b, s, zone_name);
1090*bc421551SDag-Erling Smørgrav 	break;
1091*bc421551SDag-Erling Smørgrav       case 'L':
1092*bc421551SDag-Erling Smørgrav 	formatted_len = format_local_time(b, s, tm);
1093*bc421551SDag-Erling Smørgrav 	break;
1094*bc421551SDag-Erling Smørgrav       case 'Q':
1095*bc421551SDag-Erling Smørgrav 	{
1096*bc421551SDag-Erling Smørgrav 	  bool show_abbr;
1097*bc421551SDag-Erling Smørgrav 	  int offlen = format_utc_offset(b, s, tm, t);
1098*bc421551SDag-Erling Smørgrav 	  if (! (0 <= offlen && offlen < s))
1099*bc421551SDag-Erling Smørgrav 	    return false;
1100*bc421551SDag-Erling Smørgrav 	  show_abbr = strcmp(b, ab) != 0;
1101*bc421551SDag-Erling Smørgrav 	  b += offlen, s -= offlen;
1102*bc421551SDag-Erling Smørgrav 	  if (show_abbr) {
1103*bc421551SDag-Erling Smørgrav 	    char const *abp;
1104*bc421551SDag-Erling Smørgrav 	    ptrdiff_t len;
1105*bc421551SDag-Erling Smørgrav 	    if (s <= 1)
1106*bc421551SDag-Erling Smørgrav 	      return false;
1107*bc421551SDag-Erling Smørgrav 	    *b++ = '\t', s--;
1108*bc421551SDag-Erling Smørgrav 	    for (abp = ab; is_alpha(*abp); abp++)
1109*bc421551SDag-Erling Smørgrav 	      continue;
1110*bc421551SDag-Erling Smørgrav 	    len = (!*abp && *ab
1111*bc421551SDag-Erling Smørgrav 		   ? my_snprintf(b, s, "%s", ab)
1112*bc421551SDag-Erling Smørgrav 		   : format_quoted_string(b, s, ab));
1113*bc421551SDag-Erling Smørgrav 	    if (s <= len)
1114*bc421551SDag-Erling Smørgrav 	      return false;
1115*bc421551SDag-Erling Smørgrav 	    b += len, s -= len;
1116*bc421551SDag-Erling Smørgrav 	  }
1117*bc421551SDag-Erling Smørgrav 	  formatted_len
1118*bc421551SDag-Erling Smørgrav 	    = (tm->tm_isdst
1119*bc421551SDag-Erling Smørgrav 	       ? my_snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
1120*bc421551SDag-Erling Smørgrav 	       : 0);
1121*bc421551SDag-Erling Smørgrav 	}
1122*bc421551SDag-Erling Smørgrav 	break;
1123*bc421551SDag-Erling Smørgrav       }
1124*bc421551SDag-Erling Smørgrav       if (s <= formatted_len)
1125*bc421551SDag-Erling Smørgrav 	return false;
1126*bc421551SDag-Erling Smørgrav       b += formatted_len, s -= formatted_len;
1127*bc421551SDag-Erling Smørgrav       f = p + 1;
1128*bc421551SDag-Erling Smørgrav     }
1129*bc421551SDag-Erling Smørgrav   *b = '\0';
1130*bc421551SDag-Erling Smørgrav   return true;
1131*bc421551SDag-Erling Smørgrav }
1132*bc421551SDag-Erling Smørgrav 
1133*bc421551SDag-Erling Smørgrav /* Show a time transition.  */
1134*bc421551SDag-Erling Smørgrav static void
1135*bc421551SDag-Erling Smørgrav showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
1136*bc421551SDag-Erling Smørgrav 	  char const *zone_name)
1137*bc421551SDag-Erling Smørgrav {
1138*bc421551SDag-Erling Smørgrav   if (!tm) {
1139*bc421551SDag-Erling Smørgrav     printf(tformat(), t);
1140*bc421551SDag-Erling Smørgrav     putchar('\n');
1141*bc421551SDag-Erling Smørgrav   } else {
1142*bc421551SDag-Erling Smørgrav     char stackbuf[1000];
1143*bc421551SDag-Erling Smørgrav     ptrdiff_t size = sizeof stackbuf;
1144*bc421551SDag-Erling Smørgrav     char *buf = stackbuf;
1145*bc421551SDag-Erling Smørgrav     char *bufalloc = NULL;
1146*bc421551SDag-Erling Smørgrav     while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
1147*bc421551SDag-Erling Smørgrav       size = sumsize(size, size);
1148*bc421551SDag-Erling Smørgrav       free(bufalloc);
1149*bc421551SDag-Erling Smørgrav       buf = bufalloc = xmalloc(size);
1150*bc421551SDag-Erling Smørgrav     }
1151*bc421551SDag-Erling Smørgrav     puts(buf);
1152*bc421551SDag-Erling Smørgrav     free(bufalloc);
1153*bc421551SDag-Erling Smørgrav   }
1154*bc421551SDag-Erling Smørgrav }
1155*bc421551SDag-Erling Smørgrav 
1156*bc421551SDag-Erling Smørgrav static char const *
1157*bc421551SDag-Erling Smørgrav abbr(struct tm const *tmp)
1158*bc421551SDag-Erling Smørgrav {
1159*bc421551SDag-Erling Smørgrav #ifdef TM_ZONE
1160*bc421551SDag-Erling Smørgrav 	return tmp->TM_ZONE;
1161*bc421551SDag-Erling Smørgrav #else
1162*bc421551SDag-Erling Smørgrav # if HAVE_TZNAME
1163*bc421551SDag-Erling Smørgrav 	if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
1164*bc421551SDag-Erling Smørgrav 	  return tzname[0 < tmp->tm_isdst];
1165*bc421551SDag-Erling Smørgrav # endif
1166*bc421551SDag-Erling Smørgrav 	return "";
1167*bc421551SDag-Erling Smørgrav #endif
1168*bc421551SDag-Erling Smørgrav }
1169*bc421551SDag-Erling Smørgrav 
1170*bc421551SDag-Erling Smørgrav /*
1171*bc421551SDag-Erling Smørgrav ** The code below can fail on certain theoretical systems;
1172*bc421551SDag-Erling Smørgrav ** it works on all known real-world systems as of 2022-01-25.
1173*bc421551SDag-Erling Smørgrav */
1174*bc421551SDag-Erling Smørgrav 
1175*bc421551SDag-Erling Smørgrav static const char *
1176*bc421551SDag-Erling Smørgrav tformat(void)
1177*bc421551SDag-Erling Smørgrav {
1178*bc421551SDag-Erling Smørgrav #if HAVE_GENERIC
1179*bc421551SDag-Erling Smørgrav 	/* C11-style _Generic is more likely to return the correct
1180*bc421551SDag-Erling Smørgrav 	   format when distinct types have the same size.  */
1181*bc421551SDag-Erling Smørgrav 	char const *fmt =
1182*bc421551SDag-Erling Smørgrav 	  _Generic(+ (time_t) 0,
1183*bc421551SDag-Erling Smørgrav 		   int: "%d", long: "%ld", long long: "%lld",
1184*bc421551SDag-Erling Smørgrav 		   unsigned: "%u", unsigned long: "%lu",
1185*bc421551SDag-Erling Smørgrav 		   unsigned long long: "%llu",
1186*bc421551SDag-Erling Smørgrav 		   default: NULL);
1187*bc421551SDag-Erling Smørgrav 	if (fmt)
1188*bc421551SDag-Erling Smørgrav 	  return fmt;
1189*bc421551SDag-Erling Smørgrav 	fmt = _Generic((time_t) 0,
1190*bc421551SDag-Erling Smørgrav 		       intmax_t: "%"PRIdMAX, uintmax_t: "%"PRIuMAX,
1191*bc421551SDag-Erling Smørgrav 		       default: NULL);
1192*bc421551SDag-Erling Smørgrav 	if (fmt)
1193*bc421551SDag-Erling Smørgrav 	  return fmt;
1194*bc421551SDag-Erling Smørgrav #endif
1195*bc421551SDag-Erling Smørgrav 	if (0 > (time_t) -1) {		/* signed */
1196*bc421551SDag-Erling Smørgrav 		if (sizeof(time_t) == sizeof(intmax_t))
1197*bc421551SDag-Erling Smørgrav 			return "%"PRIdMAX;
1198*bc421551SDag-Erling Smørgrav 		if (sizeof(time_t) > sizeof(long))
1199*bc421551SDag-Erling Smørgrav 			return "%lld";
1200*bc421551SDag-Erling Smørgrav 		if (sizeof(time_t) > sizeof(int))
1201*bc421551SDag-Erling Smørgrav 			return "%ld";
1202*bc421551SDag-Erling Smørgrav 		return "%d";
1203*bc421551SDag-Erling Smørgrav 	}
1204*bc421551SDag-Erling Smørgrav #ifdef PRIuMAX
1205*bc421551SDag-Erling Smørgrav 	if (sizeof(time_t) == sizeof(uintmax_t))
1206*bc421551SDag-Erling Smørgrav 		return "%"PRIuMAX;
1207*bc421551SDag-Erling Smørgrav #endif
1208*bc421551SDag-Erling Smørgrav 	if (sizeof(time_t) > sizeof(unsigned long))
1209*bc421551SDag-Erling Smørgrav 		return "%llu";
1210*bc421551SDag-Erling Smørgrav 	if (sizeof(time_t) > sizeof(unsigned int))
1211*bc421551SDag-Erling Smørgrav 		return "%lu";
1212*bc421551SDag-Erling Smørgrav 	return "%u";
1213*bc421551SDag-Erling Smørgrav }
1214*bc421551SDag-Erling Smørgrav 
1215*bc421551SDag-Erling Smørgrav static void
1216*bc421551SDag-Erling Smørgrav dumptime(register const struct tm *timeptr)
1217*bc421551SDag-Erling Smørgrav {
1218*bc421551SDag-Erling Smørgrav 	static const char	wday_name[][4] = {
1219*bc421551SDag-Erling Smørgrav 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1220*bc421551SDag-Erling Smørgrav 	};
1221*bc421551SDag-Erling Smørgrav 	static const char	mon_name[][4] = {
1222*bc421551SDag-Erling Smørgrav 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
1223*bc421551SDag-Erling Smørgrav 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1224*bc421551SDag-Erling Smørgrav 	};
1225*bc421551SDag-Erling Smørgrav 	register int		lead;
1226*bc421551SDag-Erling Smørgrav 	register int		trail;
1227*bc421551SDag-Erling Smørgrav 	int DIVISOR = 10;
1228*bc421551SDag-Erling Smørgrav 
1229*bc421551SDag-Erling Smørgrav 	/*
1230*bc421551SDag-Erling Smørgrav 	** The packaged localtime_rz and gmtime_r never put out-of-range
1231*bc421551SDag-Erling Smørgrav 	** values in tm_wday or tm_mon, but since this code might be compiled
1232*bc421551SDag-Erling Smørgrav 	** with other (perhaps experimental) versions, paranoia is in order.
1233*bc421551SDag-Erling Smørgrav 	*/
1234*bc421551SDag-Erling Smørgrav 	printf("%s %s%3d %.2d:%.2d:%.2d ",
1235*bc421551SDag-Erling Smørgrav 		((0 <= timeptr->tm_wday
1236*bc421551SDag-Erling Smørgrav 		  && timeptr->tm_wday < (int)(sizeof wday_name / sizeof wday_name[0]))
1237*bc421551SDag-Erling Smørgrav 		 ? wday_name[timeptr->tm_wday] : "???"),
1238*bc421551SDag-Erling Smørgrav 		((0 <= timeptr->tm_mon
1239*bc421551SDag-Erling Smørgrav 		  && timeptr->tm_mon < (int)(sizeof mon_name / sizeof mon_name[0]))
1240*bc421551SDag-Erling Smørgrav 		 ? mon_name[timeptr->tm_mon] : "???"),
1241*bc421551SDag-Erling Smørgrav 		timeptr->tm_mday, timeptr->tm_hour,
1242*bc421551SDag-Erling Smørgrav 		timeptr->tm_min, timeptr->tm_sec);
1243*bc421551SDag-Erling Smørgrav 	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
1244*bc421551SDag-Erling Smørgrav 	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
1245*bc421551SDag-Erling Smørgrav 		trail / DIVISOR;
1246*bc421551SDag-Erling Smørgrav 	trail %= DIVISOR;
1247*bc421551SDag-Erling Smørgrav 	if (trail < 0 && lead > 0) {
1248*bc421551SDag-Erling Smørgrav 		trail += DIVISOR;
1249*bc421551SDag-Erling Smørgrav 		--lead;
1250*bc421551SDag-Erling Smørgrav 	} else if (lead < 0 && trail > 0) {
1251*bc421551SDag-Erling Smørgrav 		trail -= DIVISOR;
1252*bc421551SDag-Erling Smørgrav 		++lead;
1253*bc421551SDag-Erling Smørgrav 	}
1254*bc421551SDag-Erling Smørgrav 	if (lead == 0)
1255*bc421551SDag-Erling Smørgrav 		printf("%d", trail);
1256*bc421551SDag-Erling Smørgrav 	else	printf("%d%d", lead, ((trail < 0) ? -trail : trail));
1257*bc421551SDag-Erling Smørgrav }
1258