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