prettydate.c revision 1.8
1/*	$NetBSD: prettydate.c,v 1.8 2016/01/08 21:35:38 christos Exp $	*/
2
3/*
4 * prettydate - convert a time stamp to something readable
5 */
6#include <config.h>
7#include <stdio.h>
8
9#include "ntp_fp.h"
10#include "ntp_unixtime.h"	/* includes <sys/time.h> */
11#include "lib_strbuf.h"
12#include "ntp_stdlib.h"
13#include "ntp_assert.h"
14#include "ntp_calendar.h"
15
16#if SIZEOF_TIME_T < 4
17# error sizeof(time_t) < 4 -- this will not work!
18#endif
19
20static char *common_prettydate(l_fp *, int);
21
22const char * const months[12] = {
23  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
24  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
25};
26
27const char * const daynames[7] = {
28  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
29};
30
31/* Helper function to handle possible wraparound of the ntp epoch.
32 *
33 * Works by periodic extension of the ntp time stamp in the UN*X epoch.
34 * If the 'time_t' is 32 bit, use solar cycle warping to get the value
35 * in a suitable range. Also uses solar cycle warping to work around
36 * really buggy implementations of 'gmtime()' / 'localtime()' that
37 * cannot work with a negative time value, that is, times before
38 * 1970-01-01. (MSVCRT...)
39 *
40 * Apart from that we're assuming that the localtime/gmtime library
41 * functions have been updated so that they work...
42 *
43 * An explanation: The julian calendar repeats ever 28 years, because
44 * it's the LCM of 7 and 1461, the week and leap year cycles. This is
45 * called a 'solar cycle'. The gregorian calendar does the same as
46 * long as no centennial year (divisible by 100, but not 400) goes in
47 * the way. So between 1901 and 2099 (inclusive) we can warp time
48 * stamps by 28 years to make them suitable for localtime() and
49 * gmtime() if we have trouble. Of course this will play hubbubb with
50 * the DST zone switches, so we should do it only if necessary; but as
51 * we NEED a proper conversion to dates via gmtime() we should try to
52 * cope with as many idiosyncrasies as possible.
53 *
54 */
55
56/*
57 * solar cycle in unsigned secs and years, and the cycle limits.
58 */
59#define SOLAR_CYCLE_SECS   0x34AADC80UL	/* 7*1461*86400*/
60#define SOLAR_CYCLE_YEARS  28
61#define MINFOLD -3
62#define MAXFOLD	 3
63
64static struct tm *
65get_struct_tm(
66	const vint64 *stamp,
67	int	      local)
68{
69	struct tm *tm	 = NULL;
70	int32	   folds = 0;
71	time_t	   ts;
72
73#ifdef HAVE_INT64
74
75	int64 tl;
76	ts = tl = stamp->q_s;
77
78	/*
79	 * If there is chance of truncation, try to fix it. Let the
80	 * compiler find out if this can happen at all.
81	 */
82	while (ts != tl) { /* truncation? */
83		if (tl < 0) {
84			if (--folds < MINFOLD)
85				return NULL;
86			tl += SOLAR_CYCLE_SECS;
87		} else {
88			if (++folds > MAXFOLD)
89				return NULL;
90			tl -= SOLAR_CYCLE_SECS;
91		}
92		ts = tl; /* next try... */
93	}
94#else
95
96	/*
97	 * since we do not have 64-bit scalars, it's not likely we have
98	 * 64-bit time_t. Assume 32 bits and properly reduce the value.
99	 */
100	u_int32 hi, lo;
101
102	hi = stamp->D_s.hi;
103	lo = stamp->D_s.lo;
104
105	while ((hi && ~hi) || ((hi ^ lo) & 0x80000000u)) {
106		if (M_ISNEG(hi, lo)) {
107			if (--folds < MINFOLD)
108				return NULL;
109			M_ADD(hi, lo, 0, SOLAR_CYCLE_SECS);
110		} else {
111			if (++folds > MAXFOLD)
112				return NULL;
113			M_SUB(hi, lo, 0, SOLAR_CYCLE_SECS);
114		}
115	}
116	ts = (int32)lo;
117
118#endif
119
120	/*
121	 * 'ts' should be a suitable value by now. Just go ahead, but
122	 * with care:
123	 *
124	 * There are some pathological implementations of 'gmtime()'
125	 * and 'localtime()' out there. No matter if we have 32-bit or
126	 * 64-bit 'time_t', try to fix this by solar cycle warping
127	 * again...
128	 *
129	 * At least the MSDN says that the (Microsoft) Windoze
130	 * versions of 'gmtime()' and 'localtime()' will bark on time
131	 * stamps < 0.
132	 */
133	while ((tm = (*(local ? localtime : gmtime))(&ts)) == NULL)
134		if (ts < 0) {
135			if (--folds < MINFOLD)
136				return NULL;
137			ts += SOLAR_CYCLE_SECS;
138		} else if (ts >= (time_t)SOLAR_CYCLE_SECS) {
139			if (++folds > MAXFOLD)
140				return NULL;
141			ts -= SOLAR_CYCLE_SECS;
142		} else
143			return NULL; /* That's truly pathological! */
144
145	/* 'tm' surely not NULL here! */
146	INSIST(tm != NULL);
147	if (folds != 0) {
148		tm->tm_year += folds * SOLAR_CYCLE_YEARS;
149		if (tm->tm_year <= 0 || tm->tm_year >= 200)
150			return NULL;	/* left warp range... can't help here! */
151	}
152
153	return tm;
154}
155
156static char *
157common_prettydate(
158	l_fp *ts,
159	int local
160	)
161{
162	static const char pfmt0[] =
163	    "%08lx.%08lx  %s, %s %2d %4d %2d:%02d:%02d.%03u";
164	static const char pfmt1[] =
165	    "%08lx.%08lx [%s, %s %2d %4d %2d:%02d:%02d.%03u UTC]";
166
167	char	    *bp;
168	struct tm   *tm;
169	u_int	     msec;
170	u_int32	     ntps;
171	vint64	     sec;
172
173	LIB_GETBUF(bp);
174
175	/* get & fix milliseconds */
176	ntps = ts->l_ui;
177	msec = ts->l_uf / 4294967;	/* fract / (2 ** 32 / 1000) */
178	if (msec >= 1000u) {
179		msec -= 1000u;
180		ntps++;
181	}
182	sec = ntpcal_ntp_to_time(ntps, NULL);
183	tm  = get_struct_tm(&sec, local);
184	if (!tm) {
185		/*
186		 * get a replacement, but always in UTC, using
187		 * ntpcal_time_to_date()
188		 */
189		struct calendar jd;
190		ntpcal_time_to_date(&jd, &sec);
191		snprintf(bp, LIB_BUFLENGTH, local ? pfmt1 : pfmt0,
192			 (u_long)ts->l_ui, (u_long)ts->l_uf,
193			 daynames[jd.weekday], months[jd.month-1],
194			 jd.monthday, jd.year, jd.hour,
195			 jd.minute, jd.second, msec);
196	} else
197		snprintf(bp, LIB_BUFLENGTH, pfmt0,
198			 (u_long)ts->l_ui, (u_long)ts->l_uf,
199			 daynames[tm->tm_wday], months[tm->tm_mon],
200			 tm->tm_mday, 1900 + tm->tm_year, tm->tm_hour,
201			 tm->tm_min, tm->tm_sec, msec);
202	return bp;
203}
204
205
206char *
207prettydate(
208	l_fp *ts
209	)
210{
211	return common_prettydate(ts, 1);
212}
213
214
215char *
216gmprettydate(
217	l_fp *ts
218	)
219{
220	return common_prettydate(ts, 0);
221}
222
223
224struct tm *
225ntp2unix_tm(
226	u_int32 ntp, int local
227	)
228{
229	vint64 vl;
230	vl = ntpcal_ntp_to_time(ntp, NULL);
231	return get_struct_tm(&vl, local);
232}
233
234