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
abbrok(const char * const abbrp,const char * const zone)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
usage(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
main(int argc,char * argv[])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
setabsolutes(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
yeartot(const long y)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
hunt(char * name,time_t lot,time_t hit)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
delta(struct tm * newp,struct tm * oldp)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
show(char * zone,time_t t,int v)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 *
abbr(struct tm * tmp)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 *
tformat(void)403 tformat(void)
404 {
405 return "%lld";
406 }
407
408 static void
dumptime(const struct tm * timeptr)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