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