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