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