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