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