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