1/*
2 * prettydate - convert a time stamp to something readable
3 */
4#include <stdio.h>
5
6#include "ntp_fp.h"
7#include "ntp_unixtime.h"	/* includes <sys/time.h> */
8#include "lib_strbuf.h"
9#include "ntp_stdlib.h"
10#include "ntp_assert.h"
11
12static char *common_prettydate(l_fp *, int);
13
14const char *months[] = {
15  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
16  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
17};
18
19static const char *days[] = {
20  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
21};
22
23/* Helper function to handle possible wraparound of the ntp epoch.
24
25   Works by periodic extension of the ntp time stamp in the NTP epoch.  If the
26   'time_t' is 32 bit, use solar cycle warping to get the value in a suitable
27   range. Also uses solar cycle warping to work around really buggy
28   implementations of 'gmtime()' / 'localtime()' that cannot work with a
29   negative time value, that is, times before 1970-01-01. (MSVCRT...)
30
31   Apart from that we're assuming that the localtime/gmtime library functions
32   have been updated so that they work...
33*/
34
35
36/* solar cycle in secs, unsigned secs and years. And the cycle limits.
37**
38** And an explanation. The julian calendar repeats ever 28 years, because it's
39** the LCM of 7 and 4, the week and leap year cycles. This is called a 'solar
40** cycle'. The gregorian calendar does the same as long as no centennial year
41** (divisible by 100, but not 400) goes in the way. So between 1901 and 2099
42** (inclusive) we can warp time stamps by 28 years to make them suitable for
43** localtime() and gmtime() if we have trouble. Of course this will play
44** hubbubb with the DST zone switches, so we should do it only if necessary;
45** but as we NEED a proper conversion to dates via gmtime() we should try to
46** cope with as many idiosyncrasies as possible.
47*/
48#define SOLAR_CYCLE_SECS   0x34AADC80UL	/* 7*1461*86400*/
49#define SOLAR_CYCLE_YEARS  28
50#define MINFOLD -3
51#define MAXFOLD  3
52
53struct tm *
54ntp2unix_tm(
55	u_long ntp, int local
56	)
57{
58	struct tm *tm;
59	int32      folds = 0;
60	time_t     t     = time(NULL);
61	u_int32    dwlo  = (int32)t; /* might expand for SIZEOF_TIME_T < 4 */
62	int32      dwhi  = (SIZEOF_TIME_T > 4) ? (int32)(t >> 16 >> 16) : (int32)(t >> 31); /* double shift: avoid warnings */
63
64
65	/* Shift NTP to UN*X epoch, then unfold around currrent time. It's
66	 * important to use a 32 bit max signed value -- LONG_MAX is 64 bit on
67	 * a 64-bit system, and it will give wrong results.
68	 */
69	M_ADD(dwhi, dwlo, 0, ((1UL << 31)-1)); /* 32-bit max signed */
70	if ((ntp -= JAN_1970) > dwlo)
71		--dwhi;
72	dwlo = ntp;
73
74	if (SIZEOF_TIME_T == 4) {
75	/*
76	** If the result will not fit into a 'time_t' we have to warp solar
77	** cycles. That's implemented by looped addition / subtraction with
78	** M_ADD and M_SUB to avoid implicit 64 bit operations, especially
79	** division. As he number of warps is rather limited there's no big
80	** performance loss here.
81	**
82	** note: unless the high word doesn't match the sign-extended low word,
83	** the combination will not fit into time_t. That's what we use for
84	** loop control here...
85	*/
86	while (dwhi != ((int32)dwlo >> 31)) {
87		if (dwhi < 0 && --folds >= MINFOLD)
88			M_ADD(dwhi, dwlo, 0, SOLAR_CYCLE_SECS);
89		else if (dwhi >= 0 && ++folds <= MAXFOLD)
90			M_SUB(dwhi, dwlo, 0, SOLAR_CYCLE_SECS);
91		else
92			return NULL;
93	}
94
95	}
96	/* combine hi/lo to make time stamp */
97	t = ((time_t)dwhi << 16 << 16) | dwlo;	/* double shift: avoid warnings */
98
99#   ifdef _MSC_VER	/* make this an autoconf option? */
100
101	/*
102	** The MSDN says that the (Microsoft) Windoze versions of 'gmtime()'
103	** and 'localtime()' will bark on time stamps < 0. Better to fix it
104	** immediately.
105	*/
106	while (t < 0) {
107		if (--folds < MINFOLD)
108			return NULL;
109		t += SOLAR_CYCLE_SECS;
110	}
111
112#   endif /* Microsoft specific */
113
114	/* 't' should be a suitable value by now. Just go ahead. */
115	while ( (tm = (*(local ? localtime : gmtime))(&t)) == 0)
116		/* seems there are some other pathological implementations of
117		** 'gmtime()' and 'localtime()' somewhere out there. No matter
118		** if we have 32-bit or 64-bit 'time_t', try to fix this by
119		** solar cycle warping again...
120		*/
121		if (t < 0) {
122			if (--folds < MINFOLD)
123				return NULL;
124			t += SOLAR_CYCLE_SECS;
125		} else {
126			if ((++folds > MAXFOLD) || ((t -= SOLAR_CYCLE_SECS) < 0))
127				return NULL; /* That's truely pathological! */
128		}
129	/* 'tm' surely not NULL here... */
130	NTP_INSIST(tm != NULL);
131	if (folds != 0) {
132		tm->tm_year += folds * SOLAR_CYCLE_YEARS;
133		if (tm->tm_year <= 0 || tm->tm_year >= 200)
134			return NULL;	/* left warp range... can't help here! */
135	}
136	return tm;
137}
138
139
140static char *
141common_prettydate(
142	l_fp *ts,
143	int local
144	)
145{
146	char *bp;
147	struct tm *tm;
148	u_long sec;
149	u_long msec;
150
151	LIB_GETBUF(bp);
152
153	sec = ts->l_ui;
154	msec = ts->l_uf / 4294967;	/* fract / (2 ** 32 / 1000) */
155
156	tm = ntp2unix_tm(sec, local);
157	if (!tm) {
158		(void) sprintf(bp, "%08lx.%08lx  --- --- -- ---- --:--:--",
159		       (u_long)ts->l_ui, (u_long)ts->l_uf);
160	}
161	else {
162		(void) sprintf(bp, "%08lx.%08lx  %s, %s %2d %4d %2d:%02d:%02d.%03lu",
163		       (u_long)ts->l_ui, (u_long)ts->l_uf, days[tm->tm_wday],
164		       months[tm->tm_mon], tm->tm_mday, 1900 + tm->tm_year,
165		       tm->tm_hour,tm->tm_min, tm->tm_sec, msec);
166	}
167
168	return bp;
169}
170
171
172char *
173prettydate(
174	l_fp *ts
175	)
176{
177	return common_prettydate(ts, 1);
178}
179
180
181char *
182gmprettydate(
183	l_fp *ts
184	)
185{
186	return common_prettydate(ts, 0);
187}
188