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