zdump.c revision 1.48.2.2 1 /* $NetBSD: zdump.c,v 1.48.2.2 2018/10/20 06:58:22 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.2 2018/10/20 06:58:22 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 return n;
815 arg = buf;
816 arglen = n;
817 }
818 slen = arglen < size ? arglen : size - 1;
819 memcpy(s, arg, slen);
820 s[slen] = '\0';
821 n = arglen <= INT_MAX ? arglen : -1;
822 va_end(args);
823 return n;
824 }
825 #endif
826
827 /* Store into BUF, of size SIZE, a formatted local time taken from *TM.
828 Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit
829 :MM too if MM is also zero.
830
831 Return the length of the resulting string. If the string does not
832 fit, return the length that the string would have been if it had
833 fit; do not overrun the output buffer. */
834 static int
835 format_local_time(char *buf, size_t size, struct tm const *tm)
836 {
837 int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
838 return (ss
839 ? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
840 : mm
841 ? my_snprintf(buf, size, "%02d:%02d", hh, mm)
842 : my_snprintf(buf, size, "%02d", hh));
843 }
844
845 /* Store into BUF, of size SIZE, a formatted UT offset for the
846 localtime *TM corresponding to time T. Use ISO 8601 format
847 +HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
848 format -00 for unknown UT offsets. If the hour needs more than
849 two digits to represent, extend the length of HH as needed.
850 Otherwise, omit SS if SS is zero, and omit MM too if MM is also
851 zero.
852
853 Return the length of the resulting string, or -1 if the result is
854 not representable as a string. If the string does not fit, return
855 the length that the string would have been if it had fit; do not
856 overrun the output buffer. */
857 static int
858 format_utc_offset(char *buf, size_t size, struct tm const *tm, time_t t)
859 {
860 long off = gmtoff(tm, &t, NULL);
861 char sign = ((off < 0
862 || (off == 0
863 && (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
864 ? '-' : '+');
865 long hh;
866 int mm, ss;
867 if (off < 0)
868 {
869 if (off == LONG_MIN)
870 return -1;
871 off = -off;
872 }
873 ss = off % 60;
874 mm = off / 60 % 60;
875 hh = off / 60 / 60;
876 return (ss || 100 <= hh
877 ? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
878 : mm
879 ? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
880 : my_snprintf(buf, size, "%c%02ld", sign, hh));
881 }
882
883 /* Store into BUF (of size SIZE) a quoted string representation of P.
884 If the representation's length is less than SIZE, return the
885 length; the representation is not null terminated. Otherwise
886 return SIZE, to indicate that BUF is too small. */
887 static size_t
888 format_quoted_string(char *buf, size_t size, char const *p)
889 {
890 char *b = buf;
891 size_t s = size;
892 if (!s)
893 return size;
894 *b++ = '"', s--;
895 for (;;) {
896 char c = *p++;
897 if (s <= 1)
898 return size;
899 switch (c) {
900 default: *b++ = c, s--; continue;
901 case '\0': *b++ = '"', s--; return size - s;
902 case '"': case '\\': break;
903 case ' ': c = 's'; break;
904 case '\f': c = 'f'; break;
905 case '\n': c = 'n'; break;
906 case '\r': c = 'r'; break;
907 case '\t': c = 't'; break;
908 case '\v': c = 'v'; break;
909 }
910 *b++ = '\\', *b++ = c, s -= 2;
911 }
912 }
913
914 /* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
915 TM is the broken-down time, T the seconds count, AB the time zone
916 abbreviation, and ZONE_NAME the zone name. Return true if
917 successful, false if the output would require more than SIZE bytes.
918 TIME_FMT uses the same format that strftime uses, with these
919 additions:
920
921 %f zone name
922 %L local time as per format_local_time
923 %Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
924 and D is the isdst flag; except omit D if it is zero, omit %Z if
925 it equals U, quote and escape %Z if it contains nonalphabetics,
926 and omit any trailing tabs. */
927
928 static bool
929 istrftime(char *buf, size_t size, char const *time_fmt,
930 struct tm const *tm, time_t t, char const *ab, char const *zone_name)
931 {
932 char *b = buf;
933 size_t s = size;
934 char const *f = time_fmt, *p;
935
936 for (p = f; ; p++)
937 if (*p == '%' && p[1] == '%')
938 p++;
939 else if (!*p
940 || (*p == '%'
941 && (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
942 size_t formatted_len;
943 size_t f_prefix_len = p - f;
944 size_t f_prefix_copy_size = p - f + 2;
945 char fbuf[100];
946 bool oversized = sizeof fbuf <= f_prefix_copy_size;
947 char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
948 memcpy(f_prefix_copy, f, f_prefix_len);
949 strcpy(f_prefix_copy + f_prefix_len, "X");
950 formatted_len = strftime(b, s, f_prefix_copy, tm);
951 if (oversized)
952 free(f_prefix_copy);
953 if (formatted_len == 0)
954 return false;
955 formatted_len--;
956 b += formatted_len, s -= formatted_len;
957 if (!*p++)
958 break;
959 switch (*p) {
960 case 'f':
961 formatted_len = format_quoted_string(b, s, zone_name);
962 break;
963 case 'L':
964 formatted_len = format_local_time(b, s, tm);
965 break;
966 case 'Q':
967 {
968 bool show_abbr;
969 int offlen = format_utc_offset(b, s, tm, t);
970 if (! (0 <= offlen && (size_t)offlen < s))
971 return false;
972 show_abbr = strcmp(b, ab) != 0;
973 b += offlen, s -= offlen;
974 if (show_abbr) {
975 char const *abp;
976 size_t len;
977 if (s <= 1)
978 return false;
979 *b++ = '\t', s--;
980 for (abp = ab; is_alpha(*abp); abp++)
981 continue;
982 len = (!*abp && *ab
983 ? (size_t)my_snprintf(b, s, "%s", ab)
984 : format_quoted_string(b, s, ab));
985 if (s <= len)
986 return false;
987 b += len, s -= len;
988 }
989 formatted_len
990 = (tm->tm_isdst
991 ? my_snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
992 : 0);
993 }
994 break;
995 }
996 if (s <= formatted_len)
997 return false;
998 b += formatted_len, s -= formatted_len;
999 f = p + 1;
1000 }
1001 *b = '\0';
1002 return true;
1003 }
1004
1005 /* Show a time transition. */
1006 static void
1007 showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
1008 char const *zone_name)
1009 {
1010 if (!tm) {
1011 printf(tformat(), t);
1012 putchar('\n');
1013 } else {
1014 char stackbuf[1000];
1015 size_t size = sizeof stackbuf;
1016 char *buf = stackbuf;
1017 char *bufalloc = NULL;
1018 while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
1019 size = sumsize(size, size);
1020 free(bufalloc);
1021 buf = bufalloc = xmalloc(size);
1022 }
1023 puts(buf);
1024 free(bufalloc);
1025 }
1026 }
1027
1028 static const char *
1029 abbr(struct tm const *tmp)
1030 {
1031 #ifdef TM_ZONE
1032 return tmp->TM_ZONE;
1033 #else
1034 # if HAVE_TZNAME
1035 if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
1036 return tzname[0 < tmp->tm_isdst];
1037 # endif
1038 return "";
1039 #endif
1040 }
1041
1042 /*
1043 ** The code below can fail on certain theoretical systems;
1044 ** it works on all known real-world systems as of 2004-12-30.
1045 */
1046
1047 static const char *
1048 tformat(void)
1049 {
1050 if (0 > (time_t) -1) { /* signed */
1051 if (sizeof (time_t) == sizeof (intmax_t))
1052 return "%"PRIdMAX;
1053 if (sizeof (time_t) > sizeof (long))
1054 return "%lld";
1055 if (sizeof (time_t) > sizeof (int))
1056 return "%ld";
1057 return "%d";
1058 }
1059 #ifdef PRIuMAX
1060 if (sizeof (time_t) == sizeof (uintmax_t))
1061 return "%"PRIuMAX;
1062 #endif
1063 if (sizeof (time_t) > sizeof (unsigned long))
1064 return "%llu";
1065 if (sizeof (time_t) > sizeof (unsigned int))
1066 return "%lu";
1067 return "%u";
1068 }
1069
1070 static void
1071 dumptime(const struct tm *timeptr)
1072 {
1073 static const char wday_name[][4] = {
1074 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1075 };
1076 static const char mon_name[][4] = {
1077 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1078 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1079 };
1080 const char * wn;
1081 const char * mn;
1082 int lead;
1083 int trail;
1084
1085 if (timeptr == NULL) {
1086 printf("NULL");
1087 return;
1088 }
1089 /*
1090 ** The packaged localtime_rz and gmtime_r never put out-of-range
1091 ** values in tm_wday or tm_mon, but since this code might be compiled
1092 ** with other (perhaps experimental) versions, paranoia is in order.
1093 */
1094 if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
1095 (int) (sizeof wday_name / sizeof wday_name[0]))
1096 wn = "???";
1097 else wn = wday_name[timeptr->tm_wday];
1098 if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
1099 (int) (sizeof mon_name / sizeof mon_name[0]))
1100 mn = "???";
1101 else mn = mon_name[timeptr->tm_mon];
1102 printf("%s %s%3d %.2d:%.2d:%.2d ",
1103 wn, mn,
1104 timeptr->tm_mday, timeptr->tm_hour,
1105 timeptr->tm_min, timeptr->tm_sec);
1106 #define DIVISOR 10
1107 trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
1108 lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
1109 trail / DIVISOR;
1110 trail %= DIVISOR;
1111 if (trail < 0 && lead > 0) {
1112 trail += DIVISOR;
1113 --lead;
1114 } else if (lead < 0 && trail > 0) {
1115 trail -= DIVISOR;
1116 ++lead;
1117 }
1118 if (lead == 0)
1119 printf("%d", trail);
1120 else printf("%d%d", lead, ((trail < 0) ? -trail : trail));
1121 }
1122