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