1 /* $NetBSD: prettydate.c,v 1.1.1.1 2009/12/13 16:55:04 kardel Exp $ */ 2 3 /* 4 * prettydate - convert a time stamp to something readable 5 */ 6 #include <stdio.h> 7 8 #include "ntp_fp.h" 9 #include "ntp_unixtime.h" /* includes <sys/time.h> */ 10 #include "lib_strbuf.h" 11 #include "ntp_stdlib.h" 12 #include "ntp_assert.h" 13 14 static char *common_prettydate(l_fp *, int); 15 16 const char *months[] = { 17 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 18 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 19 }; 20 21 static const char *days[] = { 22 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 23 }; 24 25 /* Helper function to handle possible wraparound of the ntp epoch. 26 27 Works by periodic extension of the ntp time stamp in the NTP epoch. If the 28 'time_t' is 32 bit, use solar cycle warping to get the value in a suitable 29 range. Also uses solar cycle warping to work around really buggy 30 implementations of 'gmtime()' / 'localtime()' that cannot work with a 31 negative time value, that is, times before 1970-01-01. (MSVCRT...) 32 33 Apart from that we're assuming that the localtime/gmtime library functions 34 have been updated so that they work... 35 */ 36 37 38 /* solar cycle in secs, unsigned secs and years. And the cycle limits. 39 ** 40 ** And an explanation. The julian calendar repeats ever 28 years, because it's 41 ** the LCM of 7 and 4, the week and leap year cycles. This is called a 'solar 42 ** cycle'. The gregorian calendar does the same as long as no centennial year 43 ** (divisible by 100, but not 400) goes in the way. So between 1901 and 2099 44 ** (inclusive) we can warp time stamps by 28 years to make them suitable for 45 ** localtime() and gmtime() if we have trouble. Of course this will play 46 ** hubbubb with the DST zone switches, so we should do it only if necessary; 47 ** but as we NEED a proper conversion to dates via gmtime() we should try to 48 ** cope with as many idiosyncrasies as possible. 49 */ 50 #define SOLAR_CYCLE_SECS 0x34AADC80UL /* 7*1461*86400*/ 51 #define SOLAR_CYCLE_YEARS 28 52 #define MINFOLD -3 53 #define MAXFOLD 3 54 55 struct tm * 56 ntp2unix_tm( 57 u_long ntp, int local 58 ) 59 { 60 struct tm *tm; 61 int32 folds = 0; 62 time_t t = time(NULL); 63 u_int32 dwlo = (int32)t; /* might expand for SIZEOF_TIME_T < 4 */ 64 #if ( SIZEOF_TIME_T > 4 ) 65 int32 dwhi = (int32)(t >> 16 >> 16);/* double shift: avoid warnings */ 66 #else 67 /* 68 * Get the correct sign extension in the high part. 69 * (now >> 32) may not work correctly on every 32 bit 70 * system, e.g. it yields garbage under Win32/VC6. 71 */ 72 int32 dwhi = (int32)(t >> 31); 73 #endif 74 75 /* Shift NTP to UN*X epoch, then unfold around currrent time. It's 76 * important to use a 32 bit max signed value -- LONG_MAX is 64 bit on 77 * a 64-bit system, and it will give wrong results. 78 */ 79 M_ADD(dwhi, dwlo, 0, ((1UL << 31)-1)); /* 32-bit max signed */ 80 if ((ntp -= JAN_1970) > dwlo) 81 --dwhi; 82 dwlo = ntp; 83 84 # if SIZEOF_TIME_T < 4 85 # error sizeof(time_t) < 4 -- this will not work! 86 # elif SIZEOF_TIME_T == 4 87 88 /* 89 ** If the result will not fit into a 'time_t' we have to warp solar 90 ** cycles. That's implemented by looped addition / subtraction with 91 ** M_ADD and M_SUB to avoid implicit 64 bit operations, especially 92 ** division. As he number of warps is rather limited there's no big 93 ** performance loss here. 94 ** 95 ** note: unless the high word doesn't match the sign-extended low word, 96 ** the combination will not fit into time_t. That's what we use for 97 ** loop control here... 98 */ 99 while (dwhi != ((int32)dwlo >> 31)) { 100 if (dwhi < 0 && --folds >= MINFOLD) 101 M_ADD(dwhi, dwlo, 0, SOLAR_CYCLE_SECS); 102 else if (dwhi >= 0 && ++folds <= MAXFOLD) 103 M_SUB(dwhi, dwlo, 0, SOLAR_CYCLE_SECS); 104 else 105 return NULL; 106 } 107 108 # else 109 110 /* everything fine -- no reduction needed for the next thousand years */ 111 112 # endif 113 114 /* combine hi/lo to make time stamp */ 115 t = ((time_t)dwhi << 16 << 16) | dwlo; /* double shift: avoid warnings */ 116 117 # ifdef _MSC_VER /* make this an autoconf option? */ 118 119 /* 120 ** The MSDN says that the (Microsoft) Windoze versions of 'gmtime()' 121 ** and 'localtime()' will bark on time stamps < 0. Better to fix it 122 ** immediately. 123 */ 124 while (t < 0) { 125 if (--folds < MINFOLD) 126 return NULL; 127 t += SOLAR_CYCLE_SECS; 128 } 129 130 # endif /* Microsoft specific */ 131 132 /* 't' should be a suitable value by now. Just go ahead. */ 133 while ( (tm = (*(local ? localtime : gmtime))(&t)) == 0) 134 /* seems there are some other pathological implementations of 135 ** 'gmtime()' and 'localtime()' somewhere out there. No matter 136 ** if we have 32-bit or 64-bit 'time_t', try to fix this by 137 ** solar cycle warping again... 138 */ 139 if (t < 0) { 140 if (--folds < MINFOLD) 141 return NULL; 142 t += SOLAR_CYCLE_SECS; 143 } else { 144 if ((++folds > MAXFOLD) || ((t -= SOLAR_CYCLE_SECS) < 0)) 145 return NULL; /* That's truely pathological! */ 146 } 147 /* 'tm' surely not NULL here... */ 148 NTP_INSIST(tm != NULL); 149 if (folds != 0) { 150 tm->tm_year += folds * SOLAR_CYCLE_YEARS; 151 if (tm->tm_year <= 0 || tm->tm_year >= 200) 152 return NULL; /* left warp range... can't help here! */ 153 } 154 return tm; 155 } 156 157 158 static char * 159 common_prettydate( 160 l_fp *ts, 161 int local 162 ) 163 { 164 char *bp; 165 struct tm *tm; 166 u_long sec; 167 u_long msec; 168 169 LIB_GETBUF(bp); 170 171 sec = ts->l_ui; 172 msec = ts->l_uf / 4294967; /* fract / (2 ** 32 / 1000) */ 173 174 tm = ntp2unix_tm(sec, local); 175 if (!tm) { 176 (void) sprintf(bp, "%08lx.%08lx --- --- -- ---- --:--:--", 177 (u_long)ts->l_ui, (u_long)ts->l_uf); 178 } 179 else { 180 (void) sprintf(bp, "%08lx.%08lx %s, %s %2d %4d %2d:%02d:%02d.%03lu", 181 (u_long)ts->l_ui, (u_long)ts->l_uf, days[tm->tm_wday], 182 months[tm->tm_mon], tm->tm_mday, 1900 + tm->tm_year, 183 tm->tm_hour,tm->tm_min, tm->tm_sec, msec); 184 } 185 186 return bp; 187 } 188 189 190 char * 191 prettydate( 192 l_fp *ts 193 ) 194 { 195 return common_prettydate(ts, 1); 196 } 197 198 199 char * 200 gmprettydate( 201 l_fp *ts 202 ) 203 { 204 return common_prettydate(ts, 0); 205 } 206