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