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