1 /* $NetBSD: zdump.c,v 1.25 2012/08/09 12:38:26 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.25 2012/08/09 12:38:26 christos Exp $"); 10 #endif /* !defined lint */ 11 12 #include "version.h" 13 /* 14 ** This code has been made independent of the rest of the time 15 ** conversion package to increase confidence in the verification it provides. 16 ** You can use this code to help in verifying other implementations. 17 */ 18 19 #include "stdio.h" /* for stdout, stderr */ 20 #include "string.h" /* for strcpy */ 21 #include "sys/types.h" /* for time_t */ 22 #include "time.h" /* for struct tm */ 23 #include "stdlib.h" /* for exit, malloc, atoi */ 24 #include <err.h> 25 #include "float.h" /* for FLT_MAX and DBL_MAX */ 26 #include "ctype.h" /* for isalpha et al. */ 27 #ifndef isascii 28 #define isascii(x) 1 29 #endif /* !defined isascii */ 30 31 #ifndef ZDUMP_LO_YEAR 32 #define ZDUMP_LO_YEAR (-500) 33 #endif /* !defined ZDUMP_LO_YEAR */ 34 35 #ifndef ZDUMP_HI_YEAR 36 #define ZDUMP_HI_YEAR 2500 37 #endif /* !defined ZDUMP_HI_YEAR */ 38 39 #ifndef MAX_STRING_LENGTH 40 #define MAX_STRING_LENGTH 1024 41 #endif /* !defined MAX_STRING_LENGTH */ 42 43 #ifndef TRUE 44 #define TRUE 1 45 #endif /* !defined TRUE */ 46 47 #ifndef FALSE 48 #define FALSE 0 49 #endif /* !defined FALSE */ 50 51 #ifndef EXIT_SUCCESS 52 #define EXIT_SUCCESS 0 53 #endif /* !defined EXIT_SUCCESS */ 54 55 #ifndef EXIT_FAILURE 56 #define EXIT_FAILURE 1 57 #endif /* !defined EXIT_FAILURE */ 58 59 #ifndef SECSPERMIN 60 #define SECSPERMIN 60 61 #endif /* !defined SECSPERMIN */ 62 63 #ifndef MINSPERHOUR 64 #define MINSPERHOUR 60 65 #endif /* !defined MINSPERHOUR */ 66 67 #ifndef SECSPERHOUR 68 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) 69 #endif /* !defined SECSPERHOUR */ 70 71 #ifndef HOURSPERDAY 72 #define HOURSPERDAY 24 73 #endif /* !defined HOURSPERDAY */ 74 75 #ifndef EPOCH_YEAR 76 #define EPOCH_YEAR 1970 77 #endif /* !defined EPOCH_YEAR */ 78 79 #ifndef TM_YEAR_BASE 80 #define TM_YEAR_BASE 1900 81 #endif /* !defined TM_YEAR_BASE */ 82 83 #ifndef DAYSPERNYEAR 84 #define DAYSPERNYEAR 365 85 #endif /* !defined DAYSPERNYEAR */ 86 87 #ifndef isleap 88 #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) 89 #endif /* !defined isleap */ 90 91 #ifndef isleap_sum 92 /* 93 ** See tzfile.h for details on isleap_sum. 94 */ 95 #define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) 96 #endif /* !defined isleap_sum */ 97 98 #define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) 99 #define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR) 100 #define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY) 101 102 #ifndef HAVE_GETTEXT 103 #define HAVE_GETTEXT 0 104 #endif 105 #if HAVE_GETTEXT 106 #include "locale.h" /* for setlocale */ 107 #include "libintl.h" 108 #endif /* HAVE_GETTEXT */ 109 110 #ifndef GNUC_or_lint 111 #ifdef lint 112 #define GNUC_or_lint 113 #else /* !defined lint */ 114 #ifdef __GNUC__ 115 #define GNUC_or_lint 116 #endif /* defined __GNUC__ */ 117 #endif /* !defined lint */ 118 #endif /* !defined GNUC_or_lint */ 119 120 #ifndef INITIALIZE 121 #ifdef GNUC_or_lint 122 #define INITIALIZE(x) ((x) = 0) 123 #else /* !defined GNUC_or_lint */ 124 #define INITIALIZE(x) 125 #endif /* !defined GNUC_or_lint */ 126 #endif /* !defined INITIALIZE */ 127 128 /* 129 ** For the benefit of GNU folk... 130 ** `_(MSGID)' uses the current locale's message library string for MSGID. 131 ** The default is to use gettext if available, and use MSGID otherwise. 132 */ 133 134 #ifndef _ 135 #if HAVE_GETTEXT 136 #define _(msgid) gettext(msgid) 137 #else /* !HAVE_GETTEXT */ 138 #define _(msgid) msgid 139 #endif /* !HAVE_GETTEXT */ 140 #endif /* !defined _ */ 141 142 #ifndef TZ_DOMAIN 143 #define TZ_DOMAIN "tz" 144 #endif /* !defined TZ_DOMAIN */ 145 146 extern char ** environ; 147 extern int getopt(int argc, char * const argv[], 148 const char * options); 149 extern char * optarg; 150 extern int optind; 151 152 static time_t absolute_min_time; 153 static time_t absolute_max_time; 154 static size_t longest; 155 static char * progname; 156 static int warned; 157 158 static const char * abbr(struct tm * tmp); 159 static void abbrok(const char * abbrp, const char * zone); 160 static long delta(struct tm * newp, struct tm * oldp); 161 static void dumptime(const struct tm * tmp); 162 static time_t hunt(char * name, time_t lot, time_t hit); 163 int main(int, char **); 164 static void setabsolutes(void); 165 static void show(char * zone, time_t t, int v); 166 static const char * tformat(void); 167 static time_t yeartot(long y); 168 169 #ifndef TYPECHECK 170 #define my_localtime localtime 171 #else /* !defined TYPECHECK */ 172 static struct tm * 173 my_localtime(tp) 174 time_t * tp; 175 { 176 register struct tm * tmp; 177 178 tmp = localtime(tp); 179 if (tp != NULL && tmp != NULL) { 180 struct tm tm; 181 register time_t t; 182 183 tm = *tmp; 184 t = mktime(&tm); 185 if (t - *tp >= 1 || *tp - t >= 1) { 186 (void) fflush(stdout); 187 (void) fprintf(stderr, "\n%s: ", progname); 188 (void) fprintf(stderr, tformat(), *tp); 189 (void) fprintf(stderr, " ->"); 190 (void) fprintf(stderr, " year=%d", tmp->tm_year); 191 (void) fprintf(stderr, " mon=%d", tmp->tm_mon); 192 (void) fprintf(stderr, " mday=%d", tmp->tm_mday); 193 (void) fprintf(stderr, " hour=%d", tmp->tm_hour); 194 (void) fprintf(stderr, " min=%d", tmp->tm_min); 195 (void) fprintf(stderr, " sec=%d", tmp->tm_sec); 196 (void) fprintf(stderr, " isdst=%d", tmp->tm_isdst); 197 (void) fprintf(stderr, " -> "); 198 (void) fprintf(stderr, tformat(), t); 199 (void) fprintf(stderr, "\n"); 200 } 201 } 202 return tmp; 203 } 204 #endif /* !defined TYPECHECK */ 205 206 static void 207 abbrok(abbrp, zone) 208 const char * const abbrp; 209 const char * const zone; 210 { 211 register const char * cp; 212 register const char * wp; 213 214 if (warned) 215 return; 216 cp = abbrp; 217 wp = NULL; 218 while (isascii((unsigned char) *cp) && isalpha((unsigned char) *cp)) 219 ++cp; 220 if (cp - abbrp == 0) 221 wp = _("lacks alphabetic at start"); 222 else if (cp - abbrp < 3) 223 wp = _("has fewer than 3 alphabetics"); 224 else if (cp - abbrp > 6) 225 wp = _("has more than 6 alphabetics"); 226 if (wp == NULL && (*cp == '+' || *cp == '-')) { 227 ++cp; 228 if (isascii((unsigned char) *cp) && 229 isdigit((unsigned char) *cp)) 230 if (*cp++ == '1' && *cp >= '0' && *cp <= '4') 231 ++cp; 232 if (*cp != '\0') 233 wp = _("differs from POSIX standard"); 234 } 235 if (wp == NULL) 236 return; 237 (void) fflush(stdout); 238 (void) fprintf(stderr, 239 _("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"), 240 progname, zone, abbrp, wp); 241 warned = TRUE; 242 } 243 244 __dead static void 245 usage(FILE *stream, int status) 246 { 247 (void) fprintf(stream, 248 _("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -c [loyear,]hiyear ] zonename ...\n\ 249 \n\ 250 Report bugs to tz@elsie.nci.nih.gov.\n"), 251 progname, progname); 252 exit(status); 253 } 254 255 int 256 main(argc, argv) 257 int argc; 258 char * argv[]; 259 { 260 register int i; 261 register int c; 262 register int vflag; 263 register char * cutarg; 264 register long cutloyear = ZDUMP_LO_YEAR; 265 register long cuthiyear = ZDUMP_HI_YEAR; 266 register time_t cutlotime; 267 register time_t cuthitime; 268 register char ** fakeenv; 269 time_t now; 270 time_t t; 271 time_t newt; 272 struct tm tm; 273 struct tm newtm; 274 register struct tm * tmp; 275 register struct tm * newtmp; 276 277 INITIALIZE(cutlotime); 278 INITIALIZE(cuthitime); 279 #if HAVE_GETTEXT 280 (void) setlocale(LC_ALL, ""); 281 #ifdef TZ_DOMAINDIR 282 (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR); 283 #endif /* defined TEXTDOMAINDIR */ 284 (void) textdomain(TZ_DOMAIN); 285 #endif /* HAVE_GETTEXT */ 286 progname = argv[0]; 287 for (i = 1; i < argc; ++i) 288 if (strcmp(argv[i], "--version") == 0) { 289 (void) printf("%s\n", TZVERSION); 290 exit(EXIT_SUCCESS); 291 } else if (strcmp(argv[i], "--help") == 0) { 292 usage(stdout, EXIT_SUCCESS); 293 } 294 vflag = 0; 295 cutarg = NULL; 296 while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v') 297 if (c == 'v') 298 vflag = 1; 299 else cutarg = optarg; 300 if ((c != EOF && c != -1) || 301 (optind == argc - 1 && strcmp(argv[optind], "=") == 0)) { 302 usage(stderr, EXIT_FAILURE); 303 } 304 if (vflag) { 305 if (cutarg != NULL) { 306 long lo; 307 long hi; 308 char dummy; 309 310 if (sscanf(cutarg, "%ld%c", &hi, &dummy) == 1) { 311 cuthiyear = hi; 312 } else if (sscanf(cutarg, "%ld,%ld%c", 313 &lo, &hi, &dummy) == 2) { 314 cutloyear = lo; 315 cuthiyear = hi; 316 } else { 317 (void) fprintf(stderr, _("%s: wild -c argument %s\n"), 318 progname, cutarg); 319 exit(EXIT_FAILURE); 320 } 321 } 322 setabsolutes(); 323 cutlotime = yeartot(cutloyear); 324 cuthitime = yeartot(cuthiyear); 325 } 326 (void) time(&now); 327 longest = 0; 328 for (i = optind; i < argc; ++i) 329 if (strlen(argv[i]) > longest) 330 longest = strlen(argv[i]); 331 { 332 register int from; 333 register int to; 334 335 for (i = 0; environ[i] != NULL; ++i) 336 continue; 337 fakeenv = (char **) malloc((size_t) ((i + 2) * 338 sizeof *fakeenv)); 339 if (fakeenv == NULL || 340 (fakeenv[0] = (char *) malloc(longest + 4)) == NULL) { 341 err(EXIT_FAILURE, "Can't allocated %zu bytes", 342 longest + 4); 343 } 344 to = 0; 345 (void)strcpy(fakeenv[to++], "TZ="); /* XXX strcpy is safe */ 346 for (from = 0; environ[from] != NULL; ++from) 347 if (strncmp(environ[from], "TZ=", 3) != 0) 348 fakeenv[to++] = environ[from]; 349 fakeenv[to] = NULL; 350 environ = fakeenv; 351 } 352 for (i = optind; i < argc; ++i) { 353 static char buf[MAX_STRING_LENGTH]; 354 355 (void) strcpy(&fakeenv[0][3], argv[i]); /* XXX strcpy is safe */ 356 if (!vflag) { 357 show(argv[i], now, FALSE); 358 continue; 359 } 360 warned = FALSE; 361 t = absolute_min_time; 362 show(argv[i], t, TRUE); 363 t += SECSPERHOUR * HOURSPERDAY; 364 show(argv[i], t, TRUE); 365 if (t < cutlotime) 366 t = cutlotime; 367 tmp = my_localtime(&t); 368 if (tmp != NULL) { 369 tm = *tmp; 370 (void) strncpy(buf, abbr(&tm), (sizeof buf) - 1); 371 } 372 for ( ; ; ) { 373 if (t >= cuthitime || t >= cuthitime - SECSPERHOUR * 12) 374 break; 375 newt = t + SECSPERHOUR * 12; 376 newtmp = localtime(&newt); 377 if (newtmp != NULL) 378 newtm = *newtmp; 379 if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) : 380 (delta(&newtm, &tm) != (newt - t) || 381 newtm.tm_isdst != tm.tm_isdst || 382 strcmp(abbr(&newtm), buf) != 0)) { 383 newt = hunt(argv[i], t, newt); 384 newtmp = localtime(&newt); 385 if (newtmp != NULL) { 386 newtm = *newtmp; 387 (void) strncpy(buf, 388 abbr(&newtm), 389 (sizeof buf) - 1); 390 } 391 } 392 t = newt; 393 tm = newtm; 394 tmp = newtmp; 395 } 396 t = absolute_max_time; 397 t -= SECSPERHOUR * HOURSPERDAY; 398 show(argv[i], t, TRUE); 399 t += SECSPERHOUR * HOURSPERDAY; 400 show(argv[i], t, TRUE); 401 } 402 if (fflush(stdout) || ferror(stdout)) { 403 err(EXIT_FAILURE, _("Error writing standard output")); 404 } 405 exit(EXIT_SUCCESS); 406 /* If exit fails to exit... */ 407 return EXIT_FAILURE; 408 } 409 410 static time_t 411 ovfl_check(time_t t) 412 { 413 if (t < 0 && t - 1 >= 0) 414 return t; 415 else 416 return t - 1; 417 } 418 419 static void 420 setabsolutes(void) 421 { 422 if (0.5 == (time_t) 0.5) { 423 /* 424 ** time_t is floating. 425 */ 426 if (sizeof (time_t) == sizeof (float)) { 427 absolute_min_time = (time_t) -FLT_MAX; 428 absolute_max_time = (time_t) FLT_MAX; 429 } else if (sizeof (time_t) == sizeof (double)) { 430 absolute_min_time = (time_t) -DBL_MAX; 431 absolute_max_time = (time_t) DBL_MAX; 432 } else { 433 (void) fprintf(stderr, 434 _("%s: use of -v on system with floating time_t other than float or double\n"), 435 progname); 436 exit(EXIT_FAILURE); 437 } 438 } else if (0 > (time_t) -1) { 439 /* 440 ** time_t is signed. Assume overflow wraps around. 441 */ 442 time_t t = 0; 443 time_t t1 = 1; 444 445 while (t < t1) { 446 t = t1; 447 t1 = 2 * t1 + 1; 448 } 449 450 absolute_max_time = t; 451 t = -t; 452 absolute_min_time = ovfl_check(t); 453 } else { 454 /* 455 ** time_t is unsigned. 456 */ 457 absolute_min_time = 0; 458 absolute_max_time = absolute_min_time - 1; 459 } 460 } 461 462 static time_t 463 yeartot(y) 464 const long y; 465 { 466 register long myy; 467 register long seconds; 468 register time_t t; 469 470 myy = EPOCH_YEAR; 471 t = 0; 472 while (myy != y) { 473 if (myy < y) { 474 seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR; 475 ++myy; 476 if (t > absolute_max_time - seconds) { 477 t = absolute_max_time; 478 break; 479 } 480 t += seconds; 481 } else { 482 --myy; 483 seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR; 484 if (t < absolute_min_time + seconds) { 485 t = absolute_min_time; 486 break; 487 } 488 t -= seconds; 489 } 490 } 491 return t; 492 } 493 494 static time_t 495 hunt(char *name, time_t lot, time_t hit) 496 { 497 time_t t; 498 long diff; 499 struct tm lotm; 500 register struct tm * lotmp; 501 struct tm tm; 502 register struct tm * tmp; 503 char loab[MAX_STRING_LENGTH]; 504 505 lotmp = my_localtime(&lot); 506 if (lotmp != NULL) { 507 lotm = *lotmp; 508 (void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1); 509 } 510 for ( ; ; ) { 511 diff = (long) (hit - lot); 512 if (diff < 2) 513 break; 514 t = lot; 515 t += diff / 2; 516 if (t <= lot) 517 ++t; 518 else if (t >= hit) 519 --t; 520 tmp = my_localtime(&t); 521 if (tmp != NULL) 522 tm = *tmp; 523 if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) : 524 (delta(&tm, &lotm) == (t - lot) && 525 tm.tm_isdst == lotm.tm_isdst && 526 strcmp(abbr(&tm), loab) == 0)) { 527 lot = t; 528 lotm = tm; 529 lotmp = tmp; 530 } else hit = t; 531 } 532 show(name, lot, TRUE); 533 show(name, hit, TRUE); 534 return hit; 535 } 536 537 /* 538 ** Thanks to Paul Eggert for logic used in delta. 539 */ 540 541 static long 542 delta(newp, oldp) 543 struct tm * newp; 544 struct tm * oldp; 545 { 546 register long result; 547 register int tmy; 548 549 if (newp->tm_year < oldp->tm_year) 550 return -delta(oldp, newp); 551 result = 0; 552 for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy) 553 result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE); 554 result += newp->tm_yday - oldp->tm_yday; 555 result *= HOURSPERDAY; 556 result += newp->tm_hour - oldp->tm_hour; 557 result *= MINSPERHOUR; 558 result += newp->tm_min - oldp->tm_min; 559 result *= SECSPERMIN; 560 result += newp->tm_sec - oldp->tm_sec; 561 return result; 562 } 563 564 static void 565 show(char *zone, time_t t, int v) 566 { 567 register struct tm * tmp; 568 569 (void) printf("%-*s ", (int) longest, zone); 570 if (v) { 571 tmp = gmtime(&t); 572 if (tmp == NULL) { 573 (void) printf(tformat(), t); 574 } else { 575 dumptime(tmp); 576 (void) printf(" UTC"); 577 } 578 (void) printf(" = "); 579 } 580 tmp = my_localtime(&t); 581 dumptime(tmp); 582 if (tmp != NULL) { 583 if (*abbr(tmp) != '\0') 584 (void) printf(" %s", abbr(tmp)); 585 if (v) { 586 (void) printf(" isdst=%d", tmp->tm_isdst); 587 #ifdef TM_GMTOFF 588 (void) printf(" gmtoff=%ld", tmp->TM_GMTOFF); 589 #endif /* defined TM_GMTOFF */ 590 } 591 } 592 (void) printf("\n"); 593 if (tmp != NULL && *abbr(tmp) != '\0') 594 abbrok(abbr(tmp), zone); 595 } 596 597 static const char * 598 abbr(tmp) 599 struct tm * tmp; 600 { 601 register const char * result; 602 static const char nada; 603 604 if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1) 605 return &nada; 606 result = tzname[tmp->tm_isdst]; 607 return (result == NULL) ? &nada : result; 608 } 609 610 /* 611 ** The code below can fail on certain theoretical systems; 612 ** it works on all known real-world systems as of 2004-12-30. 613 */ 614 615 static const char * 616 tformat(void) 617 { 618 if (0.5 == (time_t) 0.5) { /* floating */ 619 if (sizeof (time_t) > sizeof (double)) 620 return "%Lg"; 621 return "%g"; 622 } 623 if (0 > (time_t) -1) { /* signed */ 624 if (sizeof (time_t) > sizeof (long)) 625 return "%lld"; 626 if (sizeof (time_t) > sizeof (int)) 627 return "%ld"; 628 return "%d"; 629 } 630 if (sizeof (time_t) > sizeof (unsigned long)) 631 return "%llu"; 632 if (sizeof (time_t) > sizeof (unsigned int)) 633 return "%lu"; 634 return "%u"; 635 } 636 637 static void 638 dumptime(timeptr) 639 register const struct tm * timeptr; 640 { 641 static const char wday_name[][3] = { 642 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 643 }; 644 static const char mon_name[][3] = { 645 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 646 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 647 }; 648 register const char * wn; 649 register const char * mn; 650 register int lead; 651 register int trail; 652 653 if (timeptr == NULL) { 654 (void) printf("NULL"); 655 return; 656 } 657 /* 658 ** The packaged versions of localtime and gmtime never put out-of-range 659 ** values in tm_wday or tm_mon, but since this code might be compiled 660 ** with other (perhaps experimental) versions, paranoia is in order. 661 */ 662 if (timeptr->tm_wday < 0 || timeptr->tm_wday >= 663 (int) (sizeof wday_name / sizeof wday_name[0])) 664 wn = "???"; 665 else wn = wday_name[timeptr->tm_wday]; 666 if (timeptr->tm_mon < 0 || timeptr->tm_mon >= 667 (int) (sizeof mon_name / sizeof mon_name[0])) 668 mn = "???"; 669 else mn = mon_name[timeptr->tm_mon]; 670 (void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d ", 671 wn, mn, 672 timeptr->tm_mday, timeptr->tm_hour, 673 timeptr->tm_min, timeptr->tm_sec); 674 #define DIVISOR 10 675 trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR; 676 lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR + 677 trail / DIVISOR; 678 trail %= DIVISOR; 679 if (trail < 0 && lead > 0) { 680 trail += DIVISOR; 681 --lead; 682 } else if (lead < 0 && trail > 0) { 683 trail -= DIVISOR; 684 ++lead; 685 } 686 if (lead == 0) 687 (void) printf("%d", trail); 688 else (void) printf("%d%d", lead, ((trail < 0) ? -trail : trail)); 689 } 690