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