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