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 * 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 * 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 * 211 prettydate( 212 l_fp *ts 213 ) 214 { 215 return common_prettydate(ts, 1); 216 } 217 218 219 char * 220 gmprettydate( 221 l_fp *ts 222 ) 223 { 224 return common_prettydate(ts, 0); 225 } 226 227 228 struct tm * 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