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