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