xref: /openbsd/usr.sbin/zdump/zdump.c (revision d89ec533)
1 /*	$OpenBSD: zdump.c,v 1.14 2016/03/15 19:50:48 millert 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 /*
8 ** This code has been made independent of the rest of the time
9 ** conversion package to increase confidence in the verification it provides.
10 ** You can use this code to help in verifying other implementations.
11 */
12 
13 #include <ctype.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <time.h>
19 
20 #define ZDUMP_LO_YEAR	(-500)
21 #define ZDUMP_HI_YEAR	2500
22 
23 #define MAX_STRING_LENGTH	1024
24 
25 #define TRUE		1
26 #define FALSE		0
27 
28 #define SECSPERMIN	60
29 #define MINSPERHOUR	60
30 #define SECSPERHOUR	(SECSPERMIN * MINSPERHOUR)
31 #define HOURSPERDAY	24
32 #define EPOCH_YEAR	1970
33 #define TM_YEAR_BASE	1900
34 #define DAYSPERNYEAR	365
35 
36 #define SECSPERDAY	((long) SECSPERHOUR * HOURSPERDAY)
37 #define SECSPERNYEAR	(SECSPERDAY * DAYSPERNYEAR)
38 #define SECSPERLYEAR	(SECSPERNYEAR + SECSPERDAY)
39 
40 #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
41 
42 #ifndef isleap_sum
43 /*
44 ** See tzfile.h for details on isleap_sum.
45 */
46 #define isleap_sum(a, b)	isleap((a) % 400 + (b) % 400)
47 #endif /* !defined isleap_sum */
48 
49 extern char	**environ;
50 extern char	*tzname[2];
51 extern char 	*__progname;
52 
53 time_t		absolute_min_time;
54 time_t		absolute_max_time;
55 size_t		longest;
56 int		warned;
57 
58 static char 		*abbr(struct tm *tmp);
59 static void		abbrok(const char *abbrp, const char *zone);
60 static long		delta(struct tm *newp, struct tm *oldp);
61 static void		dumptime(const struct tm *tmp);
62 static time_t		hunt(char *name, time_t lot, time_t	hit);
63 static void		setabsolutes(void);
64 static void		show(char *zone, time_t t, int v);
65 static const char 	*tformat(void);
66 static time_t		yeartot(long y);
67 static void		usage(void);
68 
69 static void
70 abbrok(const char * const abbrp, const char * const zone)
71 {
72 	const char 	*cp;
73 	char 		*wp;
74 
75 	if (warned)
76 		return;
77 	cp = abbrp;
78 	wp = NULL;
79 	while (isascii((unsigned char)*cp) &&
80 	    (isalnum((unsigned char)*cp) || *cp == '-' || *cp == '+'))
81 		++cp;
82 	if (cp - abbrp < 3)
83 		wp = "has fewer than 3 characters";
84 	else if (cp - abbrp > 6)
85 		wp = "has more than 6 characters";
86 	else if (*cp)
87 		wp = "has characters other than ASCII alphanumerics, '-' or '+'";
88 	else
89 		return;
90 	fflush(stdout);
91 	fprintf(stderr, "%s: warning: zone \"%s\" abbreviation \"%s\" %s\n",
92 		__progname, zone, abbrp, wp);
93 	warned = TRUE;
94 }
95 
96 static void
97 usage(void)
98 {
99 	fprintf(stderr, "usage: %s [-v] [-c [loyear,]hiyear] zonename ...\n",
100 	    __progname);
101 	exit(EXIT_FAILURE);
102 }
103 
104 int
105 main(int argc, char *argv[])
106 {
107 	int		i, c, vflag = 0;
108 	char		*cutarg = NULL;
109 	long		cutloyear = ZDUMP_LO_YEAR;
110 	long		cuthiyear = ZDUMP_HI_YEAR;
111 	time_t		cutlotime = 0, cuthitime = 0;
112 	time_t		now, t, newt;
113 	struct tm	tm, newtm, *tmp, *newtmp;
114 	char		**fakeenv;
115 
116 	if (pledge("stdio rpath", NULL) == -1) {
117 		perror("pledge");
118 		exit(1);
119 	}
120 
121 	while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v') {
122 		switch (c) {
123 		case 'v':
124 			vflag = 1;
125 			break;
126 		case 'c':
127 			cutarg = optarg;
128 			break;
129 		default:
130 			usage();
131 			break;
132 		}
133 	}
134 	if (c != -1 ||
135 	    (optind == argc - 1 && strcmp(argv[optind], "=") == 0)) {
136 		usage();
137 	}
138 	if (vflag) {
139 		if (cutarg != NULL) {
140 			long	lo, hi;
141 			char	dummy;
142 
143 			if (sscanf(cutarg, "%ld%c", &hi, &dummy) == 1) {
144 				cuthiyear = hi;
145 			} else if (sscanf(cutarg, "%ld,%ld%c",
146 			    &lo, &hi, &dummy) == 2) {
147 				cutloyear = lo;
148 				cuthiyear = hi;
149 			} else {
150 				fprintf(stderr, "%s: wild -c argument %s\n",
151 				    __progname, cutarg);
152 				exit(EXIT_FAILURE);
153 			}
154 		}
155 		setabsolutes();
156 		cutlotime = yeartot(cutloyear);
157 		cuthitime = yeartot(cuthiyear);
158 	}
159 	time(&now);
160 	longest = 0;
161 	for (i = optind; i < argc; ++i)
162 		if (strlen(argv[i]) > longest)
163 			longest = strlen(argv[i]);
164 
165 	{
166 		int	from, to;
167 
168 		for (i = 0; environ[i] != NULL; ++i)
169 			continue;
170 		fakeenv = reallocarray(NULL, i + 2, sizeof *fakeenv);
171 		if (fakeenv == NULL ||
172 		    (fakeenv[0] = malloc(longest + 4)) == NULL) {
173 			perror(__progname);
174 			exit(EXIT_FAILURE);
175 		}
176 		to = 0;
177 		strlcpy(fakeenv[to++], "TZ=", longest + 4);
178 		for (from = 0; environ[from] != NULL; ++from)
179 			if (strncmp(environ[from], "TZ=", 3) != 0)
180 				fakeenv[to++] = environ[from];
181 		fakeenv[to] = NULL;
182 		environ = fakeenv;
183 	}
184 	for (i = optind; i < argc; ++i) {
185 		char	buf[MAX_STRING_LENGTH];
186 
187 		strlcpy(&fakeenv[0][3], argv[i], longest + 1);
188 		if (!vflag) {
189 			show(argv[i], now, FALSE);
190 			continue;
191 		}
192 		warned = FALSE;
193 		t = absolute_min_time;
194 		show(argv[i], t, TRUE);
195 		t += SECSPERHOUR * HOURSPERDAY;
196 		show(argv[i], t, TRUE);
197 		if (t < cutlotime)
198 			t = cutlotime;
199 		tmp = localtime(&t);
200 		if (tmp != NULL) {
201 			tm = *tmp;
202 			strlcpy(buf, abbr(&tm), sizeof buf);
203 		}
204 		for ( ; ; ) {
205 			if (t >= cuthitime || t >= cuthitime - SECSPERHOUR * 12)
206 				break;
207 			newt = t + SECSPERHOUR * 12;
208 			newtmp = localtime(&newt);
209 			if (newtmp != NULL)
210 				newtm = *newtmp;
211 			if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
212 			    (delta(&newtm, &tm) != (newt - t) ||
213 			    newtm.tm_isdst != tm.tm_isdst ||
214 			    strcmp(abbr(&newtm), buf) != 0)) {
215 				newt = hunt(argv[i], t, newt);
216 				newtmp = localtime(&newt);
217 				if (newtmp != NULL) {
218 					newtm = *newtmp;
219 					strlcpy(buf, abbr(&newtm), sizeof buf);
220 				}
221 			}
222 			t = newt;
223 			tm = newtm;
224 			tmp = newtmp;
225 		}
226 		t = absolute_max_time;
227 		t -= SECSPERHOUR * HOURSPERDAY;
228 		show(argv[i], t, TRUE);
229 		t += SECSPERHOUR * HOURSPERDAY;
230 		show(argv[i], t, TRUE);
231 	}
232 	if (fflush(stdout) || ferror(stdout)) {
233 		fprintf(stderr, "%s: ", __progname);
234 		perror("Error writing to standard output");
235 		exit(EXIT_FAILURE);
236 	}
237 	return 0;
238 }
239 
240 static void
241 setabsolutes(void)
242 {
243 	time_t t = 0, t1 = 1;
244 
245 	while (t < t1) {
246 		t = t1;
247 		t1 = 2 * t1 + 1;
248 	}
249 
250 	absolute_max_time = t;
251 	t = -t;
252 	absolute_min_time = t - 1;
253 	if (t < absolute_min_time)
254 		absolute_min_time = t;
255 }
256 
257 static time_t
258 yeartot(const long y)
259 {
260 	long	myy = EPOCH_YEAR, seconds;
261 	time_t	t = 0;
262 
263 	while (myy != y) {
264 		if (myy < y) {
265 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
266 			++myy;
267 			if (t > absolute_max_time - seconds) {
268 				t = absolute_max_time;
269 				break;
270 			}
271 			t += seconds;
272 		} else {
273 			--myy;
274 			seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
275 			if (t < absolute_min_time + seconds) {
276 				t = absolute_min_time;
277 				break;
278 			}
279 			t -= seconds;
280 		}
281 	}
282 	return t;
283 }
284 
285 static time_t
286 hunt(char *name, time_t lot, time_t hit)
287 {
288 	time_t			t;
289 	long			diff;
290 	struct tm		lotm, *lotmp;
291 	struct tm		tm, *tmp;
292 	char			loab[MAX_STRING_LENGTH];
293 
294 	lotmp = localtime(&lot);
295 	if (lotmp != NULL) {
296 		lotm = *lotmp;
297 		strlcpy(loab, abbr(&lotm), sizeof loab);
298 	}
299 	for ( ; ; ) {
300 		diff = (long) (hit - lot);
301 		if (diff < 2)
302 			break;
303 		t = lot;
304 		t += diff / 2;
305 		if (t <= lot)
306 			++t;
307 		else if (t >= hit)
308 			--t;
309 		tmp = localtime(&t);
310 		if (tmp != NULL)
311 			tm = *tmp;
312 		if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
313 		    (delta(&tm, &lotm) == (t - lot) &&
314 		    tm.tm_isdst == lotm.tm_isdst &&
315 		    strcmp(abbr(&tm), loab) == 0)) {
316 			lot = t;
317 			lotm = tm;
318 			lotmp = tmp;
319 		} else
320 			hit = t;
321 	}
322 	show(name, lot, TRUE);
323 	show(name, hit, TRUE);
324 	return hit;
325 }
326 
327 /*
328 ** Thanks to Paul Eggert for logic used in delta.
329 */
330 
331 static long
332 delta(struct tm *newp, struct tm *oldp)
333 {
334 	long	result;
335 	int	tmy;
336 
337 	if (newp->tm_year < oldp->tm_year)
338 		return -delta(oldp, newp);
339 	result = 0;
340 	for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
341 		result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
342 	result += newp->tm_yday - oldp->tm_yday;
343 	result *= HOURSPERDAY;
344 	result += newp->tm_hour - oldp->tm_hour;
345 	result *= MINSPERHOUR;
346 	result += newp->tm_min - oldp->tm_min;
347 	result *= SECSPERMIN;
348 	result += newp->tm_sec - oldp->tm_sec;
349 	return result;
350 }
351 
352 static void
353 show(char *zone, time_t t, int v)
354 {
355 	struct tm 	*tmp;
356 
357 	printf("%-*s  ", (int) longest, zone);
358 	if (v) {
359 		tmp = gmtime(&t);
360 		if (tmp == NULL) {
361 			printf(tformat(), t);
362 		} else {
363 			dumptime(tmp);
364 			printf(" UTC");
365 		}
366 		printf(" = ");
367 	}
368 	tmp = localtime(&t);
369 	dumptime(tmp);
370 	if (tmp != NULL) {
371 		if (*abbr(tmp) != '\0')
372 			printf(" %s", abbr(tmp));
373 		if (v) {
374 			printf(" isdst=%d", tmp->tm_isdst);
375 #ifdef TM_GMTOFF
376 			printf(" gmtoff=%ld", tmp->TM_GMTOFF);
377 #endif /* defined TM_GMTOFF */
378 		}
379 	}
380 	printf("\n");
381 	if (tmp != NULL && *abbr(tmp) != '\0')
382 		abbrok(abbr(tmp), zone);
383 }
384 
385 static char *
386 abbr(struct tm *tmp)
387 {
388 	char 		*result;
389 	static char	nada;
390 
391 	if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)
392 		return &nada;
393 	result = tzname[tmp->tm_isdst];
394 	return (result == NULL) ? &nada : result;
395 }
396 
397 /*
398 ** The code below can fail on certain theoretical systems;
399 ** it works on all known real-world systems as of 2004-12-30.
400 */
401 
402 static const char *
403 tformat(void)
404 {
405 	return "%lld";
406 }
407 
408 static void
409 dumptime(const struct tm *timeptr)
410 {
411 	static const char wday_name[][3] = {
412 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
413 	};
414 	static const char mon_name[][3] = {
415 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
416 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
417 	};
418 	const char	*wn, *mn;
419 	int		lead, trail;
420 
421 	if (timeptr == NULL) {
422 		printf("NULL");
423 		return;
424 	}
425 	/*
426 	** The packaged versions of localtime and gmtime never put out-of-range
427 	** values in tm_wday or tm_mon, but since this code might be compiled
428 	** with other (perhaps experimental) versions, paranoia is in order.
429 	*/
430 	if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
431 	    (int) (sizeof wday_name / sizeof wday_name[0]))
432 		wn = "???";
433 	else
434 		wn = wday_name[timeptr->tm_wday];
435 	if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
436 	    (int) (sizeof mon_name / sizeof mon_name[0]))
437 		mn = "???";
438 	else
439 		mn = mon_name[timeptr->tm_mon];
440 	printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
441 	    wn, mn,
442 	    timeptr->tm_mday, timeptr->tm_hour,
443 	    timeptr->tm_min, timeptr->tm_sec);
444 #define DIVISOR	10
445 	trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
446 	lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
447 		trail / DIVISOR;
448 	trail %= DIVISOR;
449 	if (trail < 0 && lead > 0) {
450 		trail += DIVISOR;
451 		--lead;
452 	} else if (lead < 0 && trail > 0) {
453 		trail -= DIVISOR;
454 		++lead;
455 	}
456 	if (lead == 0)
457 		printf("%d", trail);
458 	else
459 		printf("%d%d", lead, ((trail < 0) ? -trail : trail));
460 }
461