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