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