1 /* $NetBSD: prettydate.c,v 1.10 2020/05/25 20:47:24 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
20 static char *common_prettydate(l_fp *, int);
21
22 const char * const months[12] = {
23 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
24 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
25 };
26
27 const 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
64 static struct tm *
get_struct_tm(const vint64 * stamp,int local)65 get_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
156 static char *
common_prettydate(l_fp * ts,int local)157 common_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 if (ts->l_ui == 0 && ts->l_uf == 0) {
176 strlcpy (bp, "(no time)", LIB_BUFLENGTH);
177 return (bp);
178 }
179
180 /* get & fix milliseconds */
181 ntps = ts->l_ui;
182 msec = ts->l_uf / 4294967; /* fract / (2 ** 32 / 1000) */
183 if (msec >= 1000u) {
184 msec -= 1000u;
185 ntps++;
186 }
187 sec = ntpcal_ntp_to_time(ntps, NULL);
188 tm = get_struct_tm(&sec, local);
189 if (!tm) {
190 /*
191 * get a replacement, but always in UTC, using
192 * ntpcal_time_to_date()
193 */
194 struct calendar jd;
195 ntpcal_time_to_date(&jd, &sec);
196 snprintf(bp, LIB_BUFLENGTH, local ? pfmt1 : pfmt0,
197 (u_long)ts->l_ui, (u_long)ts->l_uf,
198 daynames[jd.weekday], months[jd.month-1],
199 jd.monthday, jd.year, jd.hour,
200 jd.minute, jd.second, msec);
201 } else
202 snprintf(bp, LIB_BUFLENGTH, pfmt0,
203 (u_long)ts->l_ui, (u_long)ts->l_uf,
204 daynames[tm->tm_wday], months[tm->tm_mon],
205 tm->tm_mday, 1900 + tm->tm_year, tm->tm_hour,
206 tm->tm_min, tm->tm_sec, msec);
207 return bp;
208 }
209
210
211 char *
prettydate(l_fp * ts)212 prettydate(
213 l_fp *ts
214 )
215 {
216 return common_prettydate(ts, 1);
217 }
218
219
220 char *
gmprettydate(l_fp * ts)221 gmprettydate(
222 l_fp *ts
223 )
224 {
225 return common_prettydate(ts, 0);
226 }
227
228
229 struct tm *
ntp2unix_tm(u_int32 ntp,int local)230 ntp2unix_tm(
231 u_int32 ntp, int local
232 )
233 {
234 vint64 vl;
235 vl = ntpcal_ntp_to_time(ntp, NULL);
236 return get_struct_tm(&vl, local);
237 }
238
239