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