Home | History | Annotate | Line # | Download | only in libntp
      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