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