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