1/*
2** This file is in the public domain, so clarified as of
3** 2009-05-17 by Arthur David Olson.
4*/
5
6#include "version.h"
7
8/*
9** This code has been made independent of the rest of the time
10** conversion package to increase confidence in the verification it provides.
11** You can use this code to help in verifying other implementations.
12**
13** However, include private.h when debugging, so that it overrides
14** time_t consistently with the rest of the package.
15*/
16
17#ifdef time_tz
18# include "private.h"
19#endif
20
21#include "stdio.h"	/* for stdout, stderr, perror */
22#include "string.h"	/* for strcpy */
23#include "sys/types.h"	/* for time_t */
24#include "time.h"	/* for struct tm */
25#include "stdlib.h"	/* for exit, malloc, atoi */
26#include "limits.h"	/* for CHAR_BIT, LLONG_MAX */
27#include "ctype.h"	/* for isalpha et al. */
28
29/* Enable extensions and modifications for ICU. */
30#define ICU
31
32#ifdef ICU
33#include "dirent.h"
34#endif
35
36#ifndef isascii
37#define isascii(x) 1
38#endif /* !defined isascii */
39
40/*
41** Substitutes for pre-C99 compilers.
42** Much of this section of code is stolen from private.h.
43*/
44
45#ifndef HAVE_STDINT_H
46# define HAVE_STDINT_H \
47    (199901 <= __STDC_VERSION__ || 2 < (__GLIBC__ + (0 < __GLIBC_MINOR__)))
48#endif
49#if HAVE_STDINT_H
50# include "stdint.h"
51#endif
52#ifndef HAVE_INTTYPES_H
53# define HAVE_INTTYPES_H HAVE_STDINT_H
54#endif
55#if HAVE_INTTYPES_H
56# include <inttypes.h>
57#endif
58
59#ifndef INT_FAST32_MAX
60# if INT_MAX >> 31 == 0
61typedef long int_fast32_t;
62# else
63typedef int int_fast32_t;
64# endif
65#endif
66
67#ifndef INTMAX_MAX
68# if defined LLONG_MAX || defined __LONG_LONG_MAX__
69typedef long long intmax_t;
70#  define strtoimax strtoll
71#  define PRIdMAX "lld"
72#  ifdef LLONG_MAX
73#   define INTMAX_MAX LLONG_MAX
74#  else
75#   define INTMAX_MAX __LONG_LONG_MAX__
76#  endif
77# else
78typedef long intmax_t;
79#  define strtoimax strtol
80#  define PRIdMAX "ld"
81#  define INTMAX_MAX LONG_MAX
82# endif
83#endif
84
85
86#ifndef ZDUMP_LO_YEAR
87#define ZDUMP_LO_YEAR	(-500)
88#endif /* !defined ZDUMP_LO_YEAR */
89
90#ifndef ZDUMP_HI_YEAR
91#define ZDUMP_HI_YEAR	2500
92#endif /* !defined ZDUMP_HI_YEAR */
93
94#ifndef MAX_STRING_LENGTH
95#define MAX_STRING_LENGTH	1024
96#endif /* !defined MAX_STRING_LENGTH */
97
98#ifndef TRUE
99#define TRUE		1
100#endif /* !defined TRUE */
101
102#ifndef FALSE
103#define FALSE		0
104#endif /* !defined FALSE */
105
106#ifndef EXIT_SUCCESS
107#define EXIT_SUCCESS	0
108#endif /* !defined EXIT_SUCCESS */
109
110#ifndef EXIT_FAILURE
111#define EXIT_FAILURE	1
112#endif /* !defined EXIT_FAILURE */
113
114#ifndef SECSPERMIN
115#define SECSPERMIN	60
116#endif /* !defined SECSPERMIN */
117
118#ifndef MINSPERHOUR
119#define MINSPERHOUR	60
120#endif /* !defined MINSPERHOUR */
121
122#ifndef SECSPERHOUR
123#define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
124#endif /* !defined SECSPERHOUR */
125
126#ifndef HOURSPERDAY
127#define HOURSPERDAY	24
128#endif /* !defined HOURSPERDAY */
129
130#ifndef EPOCH_YEAR
131#define EPOCH_YEAR	1970
132#endif /* !defined EPOCH_YEAR */
133
134#ifndef TM_YEAR_BASE
135#define TM_YEAR_BASE	1900
136#endif /* !defined TM_YEAR_BASE */
137
138#ifndef DAYSPERNYEAR
139#define DAYSPERNYEAR	365
140#endif /* !defined DAYSPERNYEAR */
141
142#ifndef isleap
143#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
144#endif /* !defined isleap */
145
146#ifndef isleap_sum
147/*
148** See tzfile.h for details on isleap_sum.
149*/
150#define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
151#endif /* !defined isleap_sum */
152
153#define SECSPERDAY	((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
154#define SECSPERNYEAR	(SECSPERDAY * DAYSPERNYEAR)
155#define SECSPERLYEAR	(SECSPERNYEAR + SECSPERDAY)
156#define SECSPER400YEARS	(SECSPERNYEAR * (intmax_t) (300 + 3)	\
157			 + SECSPERLYEAR * (intmax_t) (100 - 3))
158
159/*
160** True if SECSPER400YEARS is known to be representable as an
161** intmax_t.  It's OK that SECSPER400YEARS_FITS can in theory be false
162** even if SECSPER400YEARS is representable, because when that happens
163** the code merely runs a bit more slowly, and this slowness doesn't
164** occur on any practical platform.
165*/
166enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
167
168#ifndef HAVE_GETTEXT
169#define HAVE_GETTEXT 0
170#endif
171#if HAVE_GETTEXT
172#include "locale.h"	/* for setlocale */
173#include "libintl.h"
174#endif /* HAVE_GETTEXT */
175
176#ifndef GNUC_or_lint
177#ifdef lint
178#define GNUC_or_lint
179#else /* !defined lint */
180#ifdef __GNUC__
181#define GNUC_or_lint
182#endif /* defined __GNUC__ */
183#endif /* !defined lint */
184#endif /* !defined GNUC_or_lint */
185
186#if 2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__)
187# define ATTRIBUTE_PURE __attribute__ ((__pure__))
188#else
189# define ATTRIBUTE_PURE /* empty */
190#endif
191
192/*
193** For the benefit of GNU folk...
194** `_(MSGID)' uses the current locale's message library string for MSGID.
195** The default is to use gettext if available, and use MSGID otherwise.
196*/
197
198#ifndef _
199#if HAVE_GETTEXT
200#define _(msgid) gettext(msgid)
201#else /* !HAVE_GETTEXT */
202#define _(msgid) msgid
203#endif /* !HAVE_GETTEXT */
204#endif /* !defined _ */
205
206#ifndef TZ_DOMAIN
207#define TZ_DOMAIN "tz"
208#endif /* !defined TZ_DOMAIN */
209
210extern char **	environ;
211extern int	getopt(int argc, char * const argv[],
212			const char * options);
213extern char *	optarg;
214extern int	optind;
215extern char *	tzname[2];
216
217/* The minimum and maximum finite time values.  */
218static time_t const absolute_min_time =
219  ((time_t) -1 < 0
220   ? (time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1)
221   : 0);
222static time_t const absolute_max_time =
223  ((time_t) -1 < 0
224   ? - (~ 0 < 0) - ((time_t) -1 << (CHAR_BIT * sizeof (time_t) - 1))
225   : -1);
226static size_t	longest;
227static char *	progname;
228static int	warned;
229
230static char *	abbr(struct tm * tmp);
231static void	abbrok(const char * abbrp, const char * zone);
232static intmax_t	delta(struct tm * newp, struct tm * oldp) ATTRIBUTE_PURE;
233static void	dumptime(const struct tm * tmp);
234static time_t	hunt(char * name, time_t lot, time_t	hit);
235static void	show(char * zone, time_t t, int v);
236static const char *	tformat(void);
237static time_t	yeartot(intmax_t y) ATTRIBUTE_PURE;
238#ifdef ICU
239typedef struct listentry {
240	char *		name;
241	struct listentry *	next;
242} listentry;
243
244static time_t	huntICU(char * name, time_t lot, time_t	hit, FILE *fp);
245static void	dumptimeICU(FILE * fp, time_t t);
246static void	showICU(FILE * fp, char * zone, time_t t1, time_t t2);
247static int	getall(struct listentry ** namelist);
248static void getzones(char * basedir, char * subdir, struct listentry ** last, int * count);
249#endif
250
251#ifndef TYPECHECK
252#define my_localtime	localtime
253#else /* !defined TYPECHECK */
254static struct tm *
255my_localtime(time_t *tp)
256{
257	register struct tm *	tmp;
258
259	tmp = localtime(tp);
260	if (tp != NULL && tmp != NULL) {
261		struct tm	tm;
262		register time_t	t;
263
264		tm = *tmp;
265		t = mktime(&tm);
266		if (t != *tp) {
267			(void) fflush(stdout);
268			(void) fprintf(stderr, "\n%s: ", progname);
269			(void) fprintf(stderr, tformat(), *tp);
270			(void) fprintf(stderr, " ->");
271			(void) fprintf(stderr, " year=%d", tmp->tm_year);
272			(void) fprintf(stderr, " mon=%d", tmp->tm_mon);
273			(void) fprintf(stderr, " mday=%d", tmp->tm_mday);
274			(void) fprintf(stderr, " hour=%d", tmp->tm_hour);
275			(void) fprintf(stderr, " min=%d", tmp->tm_min);
276			(void) fprintf(stderr, " sec=%d", tmp->tm_sec);
277			(void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
278			(void) fprintf(stderr, " -> ");
279			(void) fprintf(stderr, tformat(), t);
280			(void) fprintf(stderr, "\n");
281		}
282	}
283	return tmp;
284}
285#endif /* !defined TYPECHECK */
286
287static void
288abbrok(const char *const abbrp, const char *const zone)
289{
290	register const char *	cp;
291	register const char *	wp;
292
293	if (warned)
294		return;
295	cp = abbrp;
296	wp = NULL;
297	while (isascii((unsigned char) *cp) && isalpha((unsigned char) *cp))
298		++cp;
299	if (cp - abbrp == 0)
300		wp = _("lacks alphabetic at start");
301	else if (cp - abbrp < 3)
302		wp = _("has fewer than 3 alphabetics");
303	else if (cp - abbrp > 6)
304		wp = _("has more than 6 alphabetics");
305	if (wp == NULL && (*cp == '+' || *cp == '-')) {
306		++cp;
307		if (isascii((unsigned char) *cp) &&
308			isdigit((unsigned char) *cp))
309				if (*cp++ == '1' && *cp >= '0' && *cp <= '4')
310					++cp;
311		if (*cp != '\0')
312			wp = _("differs from POSIX standard");
313	}
314	if (wp == NULL)
315		return;
316	(void) fflush(stdout);
317	(void) fprintf(stderr,
318		_("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
319		progname, zone, abbrp, wp);
320	warned = TRUE;
321}
322
323static void
324usage(FILE * const stream, const int status)
325{
326	(void) fprintf(stream,
327_("%s: usage: %s [--version] [--help] [-{vV}] [-{ct} [lo,]hi] zonename ...\n"
328  "\n"
329  "Report bugs to %s.\n"),
330		       progname, progname, REPORT_BUGS_TO);
331	exit(status);
332}
333
334int
335main(int argc, char *argv[])
336{
337	register int		i;
338	register int		vflag;
339	register int		Vflag;
340	register char *		cutarg;
341	register char *		cuttimes;
342	register time_t		cutlotime;
343	register time_t		cuthitime;
344	register char **	fakeenv;
345	time_t			now;
346	time_t			t;
347	time_t			newt;
348	struct tm		tm;
349	struct tm		newtm;
350	register struct tm *	tmp;
351	register struct tm *	newtmp;
352#ifdef ICU
353	int		nextopt;
354	char *	dirarg;
355	int		aflag;
356	int		iflag;
357	listentry *	namelist = NULL;
358	FILE *	fp = stdout;
359#endif
360
361	cutlotime = absolute_min_time;
362	cuthitime = absolute_max_time;
363#if HAVE_GETTEXT
364	(void) setlocale(LC_ALL, "");
365#ifdef TZ_DOMAINDIR
366	(void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
367#endif /* defined TEXTDOMAINDIR */
368	(void) textdomain(TZ_DOMAIN);
369#endif /* HAVE_GETTEXT */
370	progname = argv[0];
371	for (i = 1; i < argc; ++i)
372		if (strcmp(argv[i], "--version") == 0) {
373			(void) printf("zdump %s%s\n", PKGVERSION, TZVERSION);
374			exit(EXIT_SUCCESS);
375		} else if (strcmp(argv[i], "--help") == 0) {
376			usage(stdout, EXIT_SUCCESS);
377		}
378	vflag = Vflag = 0;
379	cutarg = cuttimes = NULL;
380#ifdef ICU
381	aflag = 0;
382	iflag = 0;
383	dirarg = NULL;
384	for (;;)
385		switch(getopt(argc, argv, "ac:d:it:vV")) {
386		case 'a': aflag = 1; break;
387		case 'c': cutarg = optarg; break;
388		case 'd': dirarg = optarg; break;
389		case 'i': iflag = 1; break;
390		case 't': cuttimes = optarg; break;
391		case 'v': vflag = 1; break;
392		case 'V': Vflag = 1; break;
393		case -1:
394			if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
395				goto arg_processing_done;
396		/* Fall through.  */
397		default:
398			(void) fprintf(stderr,
399				_("%s: usage is %s [ --version ] [ -a ] [ -v ] [ -V ] [ -i ] [ -c [loyear,]hiyear ] [ -t [lotime,]hitime] ][ -d dir ] [ zonename ... ]\n"),
400				progname, progname);
401			exit(EXIT_FAILURE);
402		}
403#else
404	for (;;)
405	  switch (getopt(argc, argv, "c:t:vV")) {
406	  case 'c': cutarg = optarg; break;
407	  case 't': cuttimes = optarg; break;
408	  case 'v': vflag = 1; break;
409	  case 'V': Vflag = 1; break;
410	  case -1:
411	    if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
412	      goto arg_processing_done;
413	    /* Fall through.  */
414	  default:
415	    usage(stderr, EXIT_FAILURE);
416	  }
417#endif
418 arg_processing_done:;
419
420#ifdef ICU
421	if (dirarg != NULL) {
422		DIR *	dp;
423		/* create the output directory */
424		mkdir(dirarg, 0777);
425		if ((dp = opendir(dirarg)) == NULL) {
426			fprintf(stderr, "cannot create the target directory");
427			exit(EXIT_FAILURE);
428		}
429		closedir(dp);
430	}
431#endif ICU
432
433	if (vflag | Vflag) {
434		intmax_t	lo;
435		intmax_t	hi;
436		char *loend, *hiend;
437		register intmax_t cutloyear = ZDUMP_LO_YEAR;
438		register intmax_t cuthiyear = ZDUMP_HI_YEAR;
439		if (cutarg != NULL) {
440			lo = strtoimax(cutarg, &loend, 10);
441			if (cutarg != loend && !*loend) {
442				hi = lo;
443				cuthiyear = hi;
444			} else if (cutarg != loend && *loend == ','
445				   && (hi = strtoimax(loend + 1, &hiend, 10),
446				       loend + 1 != hiend && !*hiend)) {
447				cutloyear = lo;
448				cuthiyear = hi;
449			} else {
450(void) fprintf(stderr, _("%s: wild -c argument %s\n"),
451					progname, cutarg);
452				exit(EXIT_FAILURE);
453			}
454		}
455		if (cutarg != NULL || cuttimes == NULL) {
456			cutlotime = yeartot(cutloyear);
457			cuthitime = yeartot(cuthiyear);
458		}
459		if (cuttimes != NULL) {
460			lo = strtoimax(cuttimes, &loend, 10);
461			if (cuttimes != loend && !*loend) {
462				hi = lo;
463				if (hi < cuthitime) {
464					if (hi < absolute_min_time)
465						hi = absolute_min_time;
466					cuthitime = hi;
467				}
468			} else if (cuttimes != loend && *loend == ','
469				   && (hi = strtoimax(loend + 1, &hiend, 10),
470				       loend + 1 != hiend && !*hiend)) {
471				if (cutlotime < lo) {
472					if (absolute_max_time < lo)
473						lo = absolute_max_time;
474					cutlotime = lo;
475				}
476				if (hi < cuthitime) {
477					if (hi < absolute_min_time)
478						hi = absolute_min_time;
479					cuthitime = hi;
480				}
481			} else {
482				(void) fprintf(stderr,
483					_("%s: wild -t argument %s\n"),
484					progname, cuttimes);
485				exit(EXIT_FAILURE);
486			}
487		}
488	}
489
490#ifdef ICU
491	if (aflag) {
492		/* get all available zones */
493		char ** fakeargv;
494		int i;
495		int count;
496
497		count = getall(&namelist);
498
499		fakeargv = (char **) malloc((size_t) (argc + count) * sizeof *argv);
500		/*
501		if ((fakeargv = (char **) malloc((size_t) (argc + count) * sizeof *argv)) == NULL) {
502			exit(EXIT_FAILURE);
503		}
504		*/
505		for (i = 0; i < argc; i++) {
506			fakeargv[i] = argv[i];
507		}
508		for (i = 0; i < count; i++) {
509			fakeargv[i + argc] = namelist->name;
510			namelist = namelist->next;
511		}
512		argv = fakeargv;
513		argc += count;
514	}
515#endif
516	(void) time(&now);
517	longest = 0;
518	for (i = optind; i < argc; ++i)
519		if (strlen(argv[i]) > longest)
520			longest = strlen(argv[i]);
521	{
522		register int	from;
523		register int	to;
524
525		for (i = 0; environ[i] != NULL; ++i)
526			continue;
527		fakeenv = malloc((i + 2) * sizeof *fakeenv);
528		if (fakeenv == NULL
529		    || (fakeenv[0] = malloc(longest + 4)) == NULL) {
530					(void) perror(progname);
531					exit(EXIT_FAILURE);
532		}
533		to = 0;
534		(void) strcpy(fakeenv[to++], "TZ=");
535		for (from = 0; environ[from] != NULL; ++from)
536			if (strncmp(environ[from], "TZ=", 3) != 0)
537				fakeenv[to++] = environ[from];
538		fakeenv[to] = NULL;
539		environ = fakeenv;
540	}
541	for (i = optind; i < argc; ++i) {
542		static char	buf[MAX_STRING_LENGTH];
543
544		(void) strcpy(&fakeenv[0][3], argv[i]);
545		if (! (vflag | Vflag)) {
546			show(argv[i], now, FALSE);
547			continue;
548		}
549#ifdef ICU
550		fp = NULL;
551		if (iflag) {
552			if (dirarg == NULL) {
553				/* we want to display a zone name here */
554				if (i != optind) {
555					printf("\n");
556				}
557				printf("ZONE: %s\n", argv[i]);
558			} else {
559				int		zstart;
560				char	path[FILENAME_MAX + 1];
561				strcpy(path, dirarg);
562				strcat(path, "/");
563				zstart = strlen(path);
564				strcat(path, argv[i]);
565				/* replace '/' with '-' */
566				while(path[++zstart] != 0) {
567					if (path[zstart] == '/') {
568						path[zstart] = '-';
569					}
570				}
571				if ((fp = fopen(path, "w")) == NULL) {
572					fprintf(stderr, "cannot create output file %s\n", path);
573					exit(EXIT_FAILURE);
574				}
575			}
576		}
577#endif
578		warned = FALSE;
579		t = absolute_min_time;
580#ifdef ICU
581		/* skip displaying info for the lowest time, which is actually not
582		 * a transition when -i option is set */
583		if (!iflag) {
584#endif
585		if (!Vflag) {
586			show(argv[i], t, TRUE);
587			t += SECSPERDAY;
588			show(argv[i], t, TRUE);
589		}
590#ifdef ICU
591		}
592#endif
593		if (t < cutlotime)
594			t = cutlotime;
595		tmp = my_localtime(&t);
596		if (tmp != NULL) {
597			tm = *tmp;
598			(void) strncpy(buf, abbr(&tm), (sizeof buf) - 1);
599		}
600		for ( ; ; ) {
601			newt = (t < absolute_max_time - SECSPERDAY / 2
602				? t + SECSPERDAY / 2
603				: absolute_max_time);
604			if (cuthitime <= newt)
605				break;
606			newtmp = localtime(&newt);
607			if (newtmp != NULL)
608				newtm = *newtmp;
609#ifdef ICU
610			if (iflag) {
611				/* We  do not want to capture transitions just for
612				 * abbreviated zone name changes */
613				if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
614					(delta(&newtm, &tm) != (newt - t) ||
615					newtm.tm_isdst != tm.tm_isdst)) {
616						newt = huntICU(argv[i], t, newt, fp);
617						newtmp = localtime(&newt);
618						if (newtmp != NULL) {
619							newtm = *newtmp;
620							(void) strncpy(buf,
621								abbr(&newtm),
622								(sizeof buf) - 1);
623						}
624				}
625			} else {
626#endif
627			if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
628				(delta(&newtm, &tm) != (newt - t) ||
629				newtm.tm_isdst != tm.tm_isdst ||
630				strcmp(abbr(&newtm), buf) != 0)) {
631					newt = hunt(argv[i], t, newt);
632					newtmp = localtime(&newt);
633					if (newtmp != NULL) {
634						newtm = *newtmp;
635						(void) strncpy(buf,
636							abbr(&newtm),
637							(sizeof buf) - 1);
638					}
639			}
640#ifdef ICU
641			}
642#endif
643			t = newt;
644			tm = newtm;
645			tmp = newtmp;
646		}
647#ifdef ICU
648		if (!iflag) {
649		/* skip displaying info for the highest time, which is actually not
650		 * a transition when -i option is used*/
651#endif
652		if (!Vflag) {
653			t = absolute_max_time;
654			t -= SECSPERDAY;
655			show(argv[i], t, TRUE);
656			t += SECSPERDAY;
657			show(argv[i], t, TRUE);
658		}
659#ifdef ICU
660		}
661		/* close file */
662		if (fp != NULL) {
663			fclose(fp);
664		}
665#endif
666	}
667	if (fflush(stdout) || ferror(stdout)) {
668		(void) fprintf(stderr, "%s: ", progname);
669		(void) perror(_("Error writing to standard output"));
670		exit(EXIT_FAILURE);
671	}
672#ifdef ICU
673	if (aflag) {
674		struct listentry *	entry = namelist;
675		struct listentry *	next;
676		while (entry != NULL) {
677			free(entry->name);
678			next = entry->next;
679			free(entry);
680			entry = next;
681		}
682	}
683#endif
684	exit(EXIT_SUCCESS);
685	/* If exit fails to exit... */
686	return EXIT_FAILURE;
687}
688
689static time_t
690yeartot(const intmax_t y)
691{
692	register intmax_t	myy, seconds, years;
693	register time_t		t;
694
695	myy = EPOCH_YEAR;
696	t = 0;
697	while (myy < y) {
698		if (SECSPER400YEARS_FITS && 400 <= y - myy) {
699			intmax_t diff400 = (y - myy) / 400;
700			if (INTMAX_MAX / SECSPER400YEARS < diff400)
701				return absolute_max_time;
702			seconds = diff400 * SECSPER400YEARS;
703			years = diff400 * 400;
704                } else {
705			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
706			years = 1;
707		}
708		myy += years;
709		if (t > absolute_max_time - seconds)
710			return absolute_max_time;
711		t += seconds;
712	}
713	while (y < myy) {
714		if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
715			intmax_t diff400 = (myy - y) / 400;
716			if (INTMAX_MAX / SECSPER400YEARS < diff400)
717				return absolute_min_time;
718			seconds = diff400 * SECSPER400YEARS;
719			years = diff400 * 400;
720		} else {
721			seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
722			years = 1;
723		}
724		myy -= years;
725		if (t < absolute_min_time + seconds)
726			return absolute_min_time;
727		t -= seconds;
728	}
729	return t;
730}
731
732static time_t
733hunt(char *name, time_t lot, time_t hit)
734{
735	time_t			t;
736	struct tm		lotm;
737	register struct tm *	lotmp;
738	struct tm		tm;
739	register struct tm *	tmp;
740	char			loab[MAX_STRING_LENGTH];
741
742	lotmp = my_localtime(&lot);
743	if (lotmp != NULL) {
744		lotm = *lotmp;
745		(void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1);
746	}
747	for ( ; ; ) {
748		time_t diff = hit - lot;
749		if (diff < 2)
750			break;
751		t = lot;
752		t += diff / 2;
753		if (t <= lot)
754			++t;
755		else if (t >= hit)
756			--t;
757		tmp = my_localtime(&t);
758		if (tmp != NULL)
759			tm = *tmp;
760		if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
761			(delta(&tm, &lotm) == (t - lot) &&
762			tm.tm_isdst == lotm.tm_isdst &&
763			strcmp(abbr(&tm), loab) == 0)) {
764				lot = t;
765				lotm = tm;
766				lotmp = tmp;
767		} else	hit = t;
768	}
769	show(name, lot, TRUE);
770	show(name, hit, TRUE);
771	return hit;
772}
773
774/*
775** Thanks to Paul Eggert for logic used in delta.
776*/
777
778static intmax_t
779delta(struct tm * newp, struct tm *oldp)
780{
781	register intmax_t	result;
782	register int		tmy;
783
784	if (newp->tm_year < oldp->tm_year)
785		return -delta(oldp, newp);
786	result = 0;
787	for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
788		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
789	result += newp->tm_yday - oldp->tm_yday;
790	result *= HOURSPERDAY;
791	result += newp->tm_hour - oldp->tm_hour;
792	result *= MINSPERHOUR;
793	result += newp->tm_min - oldp->tm_min;
794	result *= SECSPERMIN;
795	result += newp->tm_sec - oldp->tm_sec;
796	return result;
797}
798
799static void
800show(char *zone, time_t t, int v)
801{
802	register struct tm *	tmp;
803
804	(void) printf("%-*s  ", (int) longest, zone);
805	if (v) {
806		tmp = gmtime(&t);
807		if (tmp == NULL) {
808			(void) printf(tformat(), t);
809		} else {
810			dumptime(tmp);
811			(void) printf(" UT");
812		}
813		(void) printf(" = ");
814	}
815	tmp = my_localtime(&t);
816	dumptime(tmp);
817	if (tmp != NULL) {
818		if (*abbr(tmp) != '\0')
819			(void) printf(" %s", abbr(tmp));
820		if (v) {
821			(void) printf(" isdst=%d", tmp->tm_isdst);
822#ifdef TM_GMTOFF
823			(void) printf(" gmtoff=%ld", tmp->TM_GMTOFF);
824#endif /* defined TM_GMTOFF */
825		}
826	}
827	(void) printf("\n");
828	if (tmp != NULL && *abbr(tmp) != '\0')
829		abbrok(abbr(tmp), zone);
830}
831
832static char *
833abbr(struct tm *tmp)
834{
835	register char *	result;
836	static char	nada;
837
838	if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)
839		return &nada;
840	result = tzname[tmp->tm_isdst];
841	return (result == NULL) ? &nada : result;
842}
843
844/*
845** The code below can fail on certain theoretical systems;
846** it works on all known real-world systems as of 2004-12-30.
847*/
848
849static const char *
850tformat(void)
851{
852	if (0 > (time_t) -1) {		/* signed */
853		if (sizeof (time_t) == sizeof (intmax_t))
854			return "%"PRIdMAX;
855		if (sizeof (time_t) > sizeof (long))
856			return "%lld";
857		if (sizeof (time_t) > sizeof (int))
858			return "%ld";
859		return "%d";
860	}
861#ifdef PRIuMAX
862	if (sizeof (time_t) == sizeof (uintmax_t))
863		return "%"PRIuMAX;
864#endif
865	if (sizeof (time_t) > sizeof (unsigned long))
866		return "%llu";
867	if (sizeof (time_t) > sizeof (unsigned int))
868		return "%lu";
869	return "%u";
870}
871
872static void
873dumptime(register const struct tm *timeptr)
874{
875	static const char	wday_name[][3] = {
876		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
877	};
878	static const char	mon_name[][3] = {
879		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
880		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
881	};
882	register const char *	wn;
883	register const char *	mn;
884	register int		lead;
885	register int		trail;
886
887	if (timeptr == NULL) {
888		(void) printf("NULL");
889		return;
890	}
891	/*
892	** The packaged versions of localtime and gmtime never put out-of-range
893	** values in tm_wday or tm_mon, but since this code might be compiled
894	** with other (perhaps experimental) versions, paranoia is in order.
895	*/
896	if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
897		(int) (sizeof wday_name / sizeof wday_name[0]))
898			wn = "???";
899	else		wn = wday_name[timeptr->tm_wday];
900	if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
901		(int) (sizeof mon_name / sizeof mon_name[0]))
902			mn = "???";
903	else		mn = mon_name[timeptr->tm_mon];
904	(void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
905		wn, mn,
906		timeptr->tm_mday, timeptr->tm_hour,
907		timeptr->tm_min, timeptr->tm_sec);
908#define DIVISOR	10
909	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
910	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
911		trail / DIVISOR;
912	trail %= DIVISOR;
913	if (trail < 0 && lead > 0) {
914		trail += DIVISOR;
915		--lead;
916	} else if (lead < 0 && trail > 0) {
917		trail -= DIVISOR;
918		++lead;
919	}
920	if (lead == 0)
921		(void) printf("%d", trail);
922	else	(void) printf("%d%d", lead, ((trail < 0) ? -trail : trail));
923}
924
925#ifdef ICU
926static time_t
927huntICU(char *name, time_t lot, time_t hit, FILE * fp)
928{
929	time_t			t;
930	long			diff;
931	struct tm		lotm;
932	register struct tm *	lotmp;
933	struct tm		tm;
934	register struct tm *	tmp;
935	char			loab[MAX_STRING_LENGTH];
936
937	lotmp = my_localtime(&lot);
938	if (lotmp != NULL) {
939		lotm = *lotmp;
940		(void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1);
941	}
942	for ( ; ; ) {
943		diff = (long) (hit - lot);
944		if (diff < 2)
945			break;
946		t = lot;
947		t += diff / 2;
948		if (t <= lot)
949			++t;
950		else if (t >= hit)
951			--t;
952		tmp = my_localtime(&t);
953		if (tmp != NULL)
954			tm = *tmp;
955		/* We  do not want to capture transitions just for
956		 * abbreviated zone name changes */
957		if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
958			(delta(&tm, &lotm) == (t - lot) &&
959			tm.tm_isdst == lotm.tm_isdst)) {
960				lot = t;
961				lotm = tm;
962				lotmp = tmp;
963		} else	hit = t;
964	}
965	showICU(fp, name, lot, hit);
966	return hit;
967}
968
969static void showICU(FILE * fp, char *zone, time_t t1, time_t t2)
970{
971	if (fp == NULL) {
972		fp = stdout;
973	}
974	dumptimeICU(fp, t1);
975	fprintf(fp, " > ");
976	dumptimeICU(fp, t2);
977	fprintf(fp, "\n");
978}
979
980static void dumptimeICU(FILE * fp, time_t t)
981{
982	static const char	wday_name[][3] = {
983		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
984	};
985	struct tm gmt;
986	struct tm loc;
987	register int		lead;
988	register int		trail;
989	long	offset;
990	long	hour, min, sec;
991
992	loc = *my_localtime(&t);
993
994	trail = loc.tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
995	lead = loc.tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR + trail / DIVISOR;
996	trail %= DIVISOR;
997	if (trail < 0 && lead > 0) {
998		trail += DIVISOR;
999		--lead;
1000	} else if (lead < 0 && trail > 0) {
1001		trail -= DIVISOR;
1002		++lead;
1003	}
1004
1005	fprintf(fp, "%04d-%02d-%02d", lead * DIVISOR + trail, loc.tm_mon + 1, loc.tm_mday);
1006	fprintf(fp, " %.3s ", wday_name[loc.tm_wday]);
1007	fprintf(fp, "%02d:%02d:%02d", loc.tm_hour, loc.tm_min, loc.tm_sec);
1008
1009	gmt = *gmtime(&t);
1010	offset = delta(&loc, &gmt);
1011	if (offset < 0) {
1012		offset = -offset;
1013		fprintf(fp, "-");
1014	} else {
1015		fprintf(fp, "+");
1016	}
1017
1018	sec = offset % 60;
1019	offset = (offset - sec) / 60;
1020	min = offset % 60;
1021	hour = offset / 60;
1022
1023	fprintf(fp, "%02d", hour);
1024	fprintf(fp, "%02d", min);
1025	fprintf(fp, "%02d", sec);
1026	fprintf(fp, "[DST=%d]", loc.tm_isdst);
1027}
1028
1029static int getall(struct listentry ** namelist) {
1030	int count = 0;
1031	struct listentry dummyentry;
1032	struct listentry *	last = &dummyentry;
1033
1034	getzones(TZDIR, NULL, &last, &count);
1035	if (count > 0) {
1036		*namelist = dummyentry.next;
1037	}
1038
1039	return count;
1040}
1041
1042static void getzones(char * basedir, char * relpath, struct listentry ** last, int * count) {
1043	char	path[FILENAME_MAX + 1];
1044	struct	dirent *	dir;
1045	DIR *	dp;
1046
1047	strcpy(path, basedir);
1048	if (relpath != NULL) {
1049		strcat(path, "/");
1050		strcat(path, relpath);
1051	}
1052
1053	if ((dp = opendir(path)) == NULL) {
1054		/* file */
1055		if (strstr(relpath, ".tab") == NULL && strcmp(relpath, "Etc/Unknown") != 0) {
1056			char *		pzonename;
1057			listentry *	pentry;
1058
1059			if ((pzonename = malloc(strlen(relpath) + 1)) == NULL) {
1060				exit(EXIT_FAILURE);
1061			}
1062			strcpy(pzonename, relpath);
1063
1064			if ((pentry = malloc(sizeof(listentry))) == NULL) {
1065				exit(EXIT_FAILURE);
1066			}
1067
1068			pentry->name = pzonename;
1069			pentry->next = NULL;
1070			(*last)->next = pentry;
1071			*last = pentry;
1072			(*count)++;
1073		}
1074	} else {
1075		/* directory */
1076		while ((dir = readdir(dp)) != NULL) {
1077			char	subpath[FILENAME_MAX + 1];
1078
1079			if (strcmp(dir->d_name, ".") == 0
1080				|| strcmp(dir->d_name, "..") == 0) {
1081				continue;
1082			}
1083			if (relpath != NULL) {
1084				strcpy(subpath, relpath);
1085				strcat(subpath, "/");
1086				strcat(subpath, dir->d_name);
1087			} else {
1088				strcpy(subpath, dir->d_name);
1089			}
1090			getzones(basedir, subpath, last, count);
1091		}
1092		closedir(dp);
1093	}
1094}
1095#endif
1096