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