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