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