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