xref: /freebsd-13.1/contrib/tzcode/zic/zdump.c (revision a35c698d)
1 /*
2 ** This file is in the public domain, so clarified as of
3 ** 2009-05-17 by Arthur David Olson.
4 */
5 
6 #ifndef lint
7 static const char rcsid[] =
8   "$FreeBSD$";
9 static char	elsieid[] = "@(#)zdump.c	8.10";
10 #endif /* not 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 */
17 
18 #include <err.h>
19 #include <stdio.h>	/* for stdout, stderr */
20 #include <stdlib.h>	/* for exit, malloc, atoi */
21 #include <string.h>	/* for strcpy */
22 #include <sys/types.h>	/* for time_t */
23 #include <time.h>	/* for struct tm */
24 #include <unistd.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 char *	tzname[2];
148 
149 static time_t	absolute_min_time;
150 static time_t	absolute_max_time;
151 static size_t	longest;
152 static char *	progname;
153 static int	warned;
154 
155 static void	usage(FILE *stream, int status);
156 static char *	abbr(struct tm * tmp);
157 static void	abbrok(const char * abbrp, const char * zone);
158 static long	delta(struct tm * newp, struct tm * oldp);
159 static void	dumptime(const struct tm * tmp);
160 static time_t	hunt(char * name, time_t lot, time_t	hit);
161 static void	setabsolutes(void);
162 static void	show(char * zone, time_t t, int v);
163 static const char *	tformat(void);
164 static time_t	yeartot(long y);
165 
166 #ifndef TYPECHECK
167 #define my_localtime	localtime
168 #else /* !defined TYPECHECK */
169 static struct tm *
my_localtime(tp)170 my_localtime(tp)
171 time_t *	tp;
172 {
173 	register struct tm *	tmp;
174 
175 	tmp = localtime(tp);
176 	if (tp != NULL && tmp != NULL) {
177 		struct tm	tm;
178 		register time_t	t;
179 
180 		tm = *tmp;
181 		t = mktime(&tm);
182 		if (t - *tp >= 1 || *tp - t >= 1) {
183 			(void) fflush(stdout);
184 			(void) fprintf(stderr, "\n%s: ", progname);
185 			(void) fprintf(stderr, tformat(), *tp);
186 			(void) fprintf(stderr, " ->");
187 			(void) fprintf(stderr, " year=%d", tmp->tm_year);
188 			(void) fprintf(stderr, " mon=%d", tmp->tm_mon);
189 			(void) fprintf(stderr, " mday=%d", tmp->tm_mday);
190 			(void) fprintf(stderr, " hour=%d", tmp->tm_hour);
191 			(void) fprintf(stderr, " min=%d", tmp->tm_min);
192 			(void) fprintf(stderr, " sec=%d", tmp->tm_sec);
193 			(void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
194 			(void) fprintf(stderr, " -> ");
195 			(void) fprintf(stderr, tformat(), t);
196 			(void) fprintf(stderr, "\n");
197 		}
198 	}
199 	return tmp;
200 }
201 #endif /* !defined TYPECHECK */
202 
203 static void
abbrok(abbrp,zone)204 abbrok(abbrp, zone)
205 const char * const	abbrp;
206 const char * const	zone;
207 {
208 	register const char *	cp;
209 	register char *		wp;
210 
211 	if (warned)
212 		return;
213 	cp = abbrp;
214 	wp = NULL;
215 	while (isascii((unsigned char) *cp) &&
216 		(isalnum((unsigned char)*cp) || *cp == '-' || *cp == '+'))
217 		++cp;
218 	if (cp - abbrp < 3)
219 		wp = _("has fewer than 3 characters");
220 	else if (cp - abbrp > 6)
221 		wp = _("has more than 6 characters");
222 	else if (*cp)
223 		wp = "has characters other than ASCII alphanumerics, '-' or '+'";
224 	else
225 		return;
226 	(void) fflush(stdout);
227 	(void) fprintf(stderr,
228 		_("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
229 		progname, zone, abbrp, wp);
230 	warned = TRUE;
231 }
232 
233 int
main(argc,argv)234 main(argc, argv)
235 int	argc;
236 char *	argv[];
237 {
238 	register int		i;
239 	register int		c;
240 	register int		vflag;
241 	register char *		cutarg;
242 	register long		cutloyear = ZDUMP_LO_YEAR;
243 	register long		cuthiyear = ZDUMP_HI_YEAR;
244 	register time_t		cutlotime;
245 	register time_t		cuthitime;
246 	register char **	fakeenv;
247 	time_t			now;
248 	time_t			t;
249 	time_t			newt;
250 	struct tm		tm;
251 	struct tm		newtm;
252 	register struct tm *	tmp;
253 	register struct tm *	newtmp;
254 
255 	progname=argv[0];
256 	INITIALIZE(cutlotime);
257 	INITIALIZE(cuthitime);
258 #if HAVE_GETTEXT
259 	(void) setlocale(LC_MESSAGES, "");
260 #ifdef TZ_DOMAINDIR
261 	(void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
262 #endif /* TEXTDOMAINDIR */
263 	(void) textdomain(TZ_DOMAIN);
264 #endif /* HAVE_GETTEXT */
265 	for (i = 1; i < argc; ++i)
266 		if (strcmp(argv[i], "--version") == 0) {
267 			errx(EXIT_SUCCESS, "%s", elsieid);
268 		} else if (strcmp(argv[i], "--help") == 0) {
269 			usage(stdout, EXIT_SUCCESS);
270 		}
271 	vflag = 0;
272 	cutarg = NULL;
273 	while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v')
274 		if (c == 'v')
275 			vflag = 1;
276 		else	cutarg = optarg;
277 	if ((c != -1) ||
278 		(optind == argc - 1 && strcmp(argv[optind], "=") == 0)) {
279 			usage(stderr, EXIT_FAILURE);
280 	}
281 	if (vflag) {
282 		if (cutarg != NULL) {
283 			long	lo;
284 			long	hi;
285 			char	dummy;
286 
287 			if (sscanf(cutarg, "%ld%c", &hi, &dummy) == 1) {
288 				cuthiyear = hi;
289 			} else if (sscanf(cutarg, "%ld,%ld%c",
290 				&lo, &hi, &dummy) == 2) {
291 					cutloyear = lo;
292 					cuthiyear = hi;
293 			} else {
294 (void) fprintf(stderr, _("%s: wild -c argument %s\n"),
295 					progname, cutarg);
296 				exit(EXIT_FAILURE);
297 			}
298 		}
299 		setabsolutes();
300 		cutlotime = yeartot(cutloyear);
301 		cuthitime = yeartot(cuthiyear);
302 	}
303 	(void) time(&now);
304 	longest = 0;
305 	for (i = optind; i < argc; ++i)
306 		if (strlen(argv[i]) > longest)
307 			longest = strlen(argv[i]);
308 	{
309 		register int	from;
310 		register int	to;
311 
312 		for (i = 0;  environ[i] != NULL; ++i)
313 			continue;
314 		fakeenv = (char **) malloc((size_t) ((i + 2) *
315 			sizeof *fakeenv));
316 		if (fakeenv == NULL ||
317 			(fakeenv[0] = (char *) malloc((size_t) (longest +
318 				4))) == NULL)
319 					errx(EXIT_FAILURE,
320 					     _("malloc() failed"));
321 		to = 0;
322 		(void) strcpy(fakeenv[to++], "TZ=");
323 		for (from = 0; environ[from] != NULL; ++from)
324 			if (strncmp(environ[from], "TZ=", 3) != 0)
325 				fakeenv[to++] = environ[from];
326 		fakeenv[to] = NULL;
327 		environ = fakeenv;
328 	}
329 	for (i = optind; i < argc; ++i) {
330 		static char	buf[MAX_STRING_LENGTH];
331 
332 		(void) strcpy(&fakeenv[0][3], argv[i]);
333 		if (!vflag) {
334 			show(argv[i], now, FALSE);
335 			continue;
336 		}
337 		warned = FALSE;
338 		t = absolute_min_time;
339 		show(argv[i], t, TRUE);
340 		t += SECSPERHOUR * HOURSPERDAY;
341 		show(argv[i], t, TRUE);
342 		if (t < cutlotime)
343 			t = cutlotime;
344 		tmp = my_localtime(&t);
345 		if (tmp != NULL) {
346 			tm = *tmp;
347 			(void) strncpy(buf, abbr(&tm), (sizeof buf) - 1);
348 		}
349 		for ( ; ; ) {
350 			if (t >= cuthitime || t >= cuthitime - SECSPERHOUR * 12)
351 				break;
352 			newt = t + SECSPERHOUR * 12;
353 			newtmp = localtime(&newt);
354 			if (newtmp != NULL)
355 				newtm = *newtmp;
356 			if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
357 				(delta(&newtm, &tm) != (newt - t) ||
358 				newtm.tm_isdst != tm.tm_isdst ||
359 				strcmp(abbr(&newtm), buf) != 0)) {
360 					newt = hunt(argv[i], t, newt);
361 					newtmp = localtime(&newt);
362 					if (newtmp != NULL) {
363 						newtm = *newtmp;
364 						(void) strncpy(buf,
365 							abbr(&newtm),
366 							(sizeof buf) - 1);
367 					}
368 			}
369 			t = newt;
370 			tm = newtm;
371 			tmp = newtmp;
372 		}
373 		t = absolute_max_time;
374 		t -= SECSPERHOUR * HOURSPERDAY;
375 		show(argv[i], t, TRUE);
376 		t += SECSPERHOUR * HOURSPERDAY;
377 		show(argv[i], t, TRUE);
378 	}
379 	if (fflush(stdout) || ferror(stdout))
380 		errx(EXIT_FAILURE, _("error writing standard output"));
381 	exit(EXIT_SUCCESS);
382 	/* If exit fails to exit... */
383 	return(EXIT_FAILURE);
384 }
385 
386 static void
setabsolutes(void)387 setabsolutes(void)
388 {
389 	if (0.5 == (time_t) 0.5) {
390 		/*
391 		** time_t is floating.
392 		*/
393 		if (sizeof (time_t) == sizeof (float)) {
394 			absolute_min_time = (time_t) -FLT_MAX;
395 			absolute_max_time = (time_t) FLT_MAX;
396 		} else if (sizeof (time_t) == sizeof (double)) {
397 			absolute_min_time = (time_t) -DBL_MAX;
398 			absolute_max_time = (time_t) DBL_MAX;
399 		} else {
400 			(void) fprintf(stderr,
401 _("%s: use of -v on system with floating time_t other than float or double\n"),
402 				progname);
403 			exit(EXIT_FAILURE);
404 		}
405 	} else if (0 > (time_t) -1) {
406 		/*
407 		** time_t is signed.  Assume overflow wraps around.
408 		*/
409 		time_t t = 0;
410 		time_t t1 = 1;
411 
412 		while (t < t1) {
413 			t = t1;
414 			t1 = 2 * t1 + 1;
415 		}
416 
417 		absolute_max_time = t;
418 		t = -t;
419 		absolute_min_time = t - 1;
420 		if (t < absolute_min_time)
421 			absolute_min_time = t;
422 	} else {
423 		/*
424 		** time_t is unsigned.
425 		*/
426 		absolute_min_time = 0;
427 		absolute_max_time = absolute_min_time - 1;
428 	}
429 }
430 
431 static time_t
yeartot(y)432 yeartot(y)
433 const long	y;
434 {
435 	register long	myy;
436 	register long	seconds;
437 	register time_t	t;
438 
439 	myy = EPOCH_YEAR;
440 	t = 0;
441 	while (myy != y) {
442 		if (myy < y) {
443 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
444 			++myy;
445 			if (t > absolute_max_time - seconds) {
446 				t = absolute_max_time;
447 				break;
448 			}
449 			t += seconds;
450 		} else {
451 			--myy;
452 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
453 			if (t < absolute_min_time + seconds) {
454 				t = absolute_min_time;
455 				break;
456 			}
457 			t -= seconds;
458 		}
459 	}
460 	return t;
461 }
462 
463 static void
usage(FILE * stream,int status)464 usage(FILE *stream, int status)
465 {
466 	fprintf(stream,
467 _("usage: %s [--version] [-v] [--help] [-c [loyear,]hiyear] zonename ...\n\
468 \n\
469 Report bugs to [email protected].\n"), progname);
470 	exit(status);
471 }
472 
473 static time_t
hunt(char * name,time_t lot,time_t hit)474 hunt(char *name, time_t lot, time_t hit)
475 {
476 	time_t			t;
477 	long			diff;
478 	struct tm		lotm;
479 	register struct tm *	lotmp;
480 	struct tm		tm;
481 	register struct tm *	tmp;
482 	char			loab[MAX_STRING_LENGTH];
483 
484 	lotmp = my_localtime(&lot);
485 	if (lotmp != NULL) {
486 		lotm = *lotmp;
487 		(void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1);
488 	}
489 	for ( ; ; ) {
490 		diff = (long) (hit - lot);
491 		if (diff < 2)
492 			break;
493 		t = lot;
494 		t += diff / 2;
495 		if (t <= lot)
496 			++t;
497 		else if (t >= hit)
498 			--t;
499 		tmp = my_localtime(&t);
500 		if (tmp != NULL)
501 			tm = *tmp;
502 		if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
503 			(delta(&tm, &lotm) == (t - lot) &&
504 			tm.tm_isdst == lotm.tm_isdst &&
505 			strcmp(abbr(&tm), loab) == 0)) {
506 				lot = t;
507 				lotm = tm;
508 				lotmp = tmp;
509 		} else	hit = t;
510 	}
511 	show(name, lot, TRUE);
512 	show(name, hit, TRUE);
513 	return hit;
514 }
515 
516 /*
517 ** Thanks to Paul Eggert for logic used in delta.
518 */
519 
520 static long
delta(newp,oldp)521 delta(newp, oldp)
522 struct tm *	newp;
523 struct tm *	oldp;
524 {
525 	register long	result;
526 	register int	tmy;
527 
528 	if (newp->tm_year < oldp->tm_year)
529 		return -delta(oldp, newp);
530 	result = 0;
531 	for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
532 		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
533 	result += newp->tm_yday - oldp->tm_yday;
534 	result *= HOURSPERDAY;
535 	result += newp->tm_hour - oldp->tm_hour;
536 	result *= MINSPERHOUR;
537 	result += newp->tm_min - oldp->tm_min;
538 	result *= SECSPERMIN;
539 	result += newp->tm_sec - oldp->tm_sec;
540 	return result;
541 }
542 
543 static void
show(char * zone,time_t t,int v)544 show(char *zone, time_t t, int v)
545 {
546 	register struct tm *	tmp;
547 
548 	(void) printf("%-*s  ", (int) longest, zone);
549 	if (v) {
550 		tmp = gmtime(&t);
551 		if (tmp == NULL) {
552 			(void) printf(tformat(), t);
553 		} else {
554 			dumptime(tmp);
555 			(void) printf(" UTC");
556 		}
557 		(void) printf(" = ");
558 	}
559 	tmp = my_localtime(&t);
560 	dumptime(tmp);
561 	if (tmp != NULL) {
562 		if (*abbr(tmp) != '\0')
563 			(void) printf(" %s", abbr(tmp));
564 		if (v) {
565 			(void) printf(" isdst=%d", tmp->tm_isdst);
566 #ifdef TM_GMTOFF
567 			(void) printf(" gmtoff=%ld", tmp->TM_GMTOFF);
568 #endif /* defined TM_GMTOFF */
569 		}
570 	}
571 	(void) printf("\n");
572 	if (tmp != NULL && *abbr(tmp) != '\0')
573 		abbrok(abbr(tmp), zone);
574 }
575 
576 static char *
abbr(tmp)577 abbr(tmp)
578 struct tm *	tmp;
579 {
580 	register char *	result;
581 	static char	nada;
582 
583 	if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)
584 		return &nada;
585 	result = tzname[tmp->tm_isdst];
586 	return (result == NULL) ? &nada : result;
587 }
588 
589 /*
590 ** The code below can fail on certain theoretical systems;
591 ** it works on all known real-world systems as of 2004-12-30.
592 */
593 
594 static const char *
tformat(void)595 tformat(void)
596 {
597 	if (0.5 == (time_t) 0.5) {	/* floating */
598 		if (sizeof (time_t) > sizeof (double))
599 			return "%Lg";
600 		return "%g";
601 	}
602 	if (0 > (time_t) -1) {		/* signed */
603 		if (sizeof (time_t) > sizeof (long))
604 			return "%lld";
605 		if (sizeof (time_t) > sizeof (int))
606 			return "%ld";
607 		return "%d";
608 	}
609 	if (sizeof (time_t) > sizeof (unsigned long))
610 		return "%llu";
611 	if (sizeof (time_t) > sizeof (unsigned int))
612 		return "%lu";
613 	return "%u";
614 }
615 
616 static void
dumptime(timeptr)617 dumptime(timeptr)
618 register const struct tm *	timeptr;
619 {
620 	static const char	wday_name[][3] = {
621 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
622 	};
623 	static const char	mon_name[][3] = {
624 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
625 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
626 	};
627 	register const char *	wn;
628 	register const char *	mn;
629 	register int		lead;
630 	register int		trail;
631 
632 	if (timeptr == NULL) {
633 		(void) printf("NULL");
634 		return;
635 	}
636 	/*
637 	** The packaged versions of localtime and gmtime never put out-of-range
638 	** values in tm_wday or tm_mon, but since this code might be compiled
639 	** with other (perhaps experimental) versions, paranoia is in order.
640 	*/
641 	if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
642 		(int) (sizeof wday_name / sizeof wday_name[0]))
643 			wn = "???";
644 	else		wn = wday_name[timeptr->tm_wday];
645 	if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
646 		(int) (sizeof mon_name / sizeof mon_name[0]))
647 			mn = "???";
648 	else		mn = mon_name[timeptr->tm_mon];
649 	(void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
650 		wn, mn,
651 		timeptr->tm_mday, timeptr->tm_hour,
652 		timeptr->tm_min, timeptr->tm_sec);
653 #define DIVISOR	10
654 	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
655 	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
656 		trail / DIVISOR;
657 	trail %= DIVISOR;
658 	if (trail < 0 && lead > 0) {
659 		trail += DIVISOR;
660 		--lead;
661 	} else if (lead < 0 && trail > 0) {
662 		trail -= DIVISOR;
663 		++lead;
664 	}
665 	if (lead == 0)
666 		(void) printf("%d", trail);
667 	else	(void) printf("%d%d", lead, ((trail < 0) ? -trail : trail));
668 }
669