aboutsummaryrefslogtreecommitdiff
path: root/libntp/prettydate.c
blob: 1503a2ce87e308341eeb23e7439b8d9bcaaac024 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
 * prettydate - convert a time stamp to something readable
 */
#include <stdio.h>

#include "ntp_fp.h"
#include "ntp_unixtime.h"	/* includes <sys/time.h> */
#include "lib_strbuf.h"
#include "ntp_stdlib.h"
#include "ntp_assert.h"

static char *common_prettydate(l_fp *, int);

const char *months[] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

static const char *days[] = {
  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

/* Helper function to handle possible wraparound of the ntp epoch.

   Works by periodic extension of the ntp time stamp in the NTP epoch.  If the
   'time_t' is 32 bit, use solar cycle warping to get the value in a suitable
   range. Also uses solar cycle warping to work around really buggy
   implementations of 'gmtime()' / 'localtime()' that cannot work with a
   negative time value, that is, times before 1970-01-01. (MSVCRT...)

   Apart from that we're assuming that the localtime/gmtime library functions
   have been updated so that they work...
*/


/* solar cycle in secs, unsigned secs and years. And the cycle limits.
**
** And an explanation. The julian calendar repeats ever 28 years, because it's
** the LCM of 7 and 4, the week and leap year cycles. This is called a 'solar
** cycle'. The gregorian calendar does the same as long as no centennial year
** (divisible by 100, but not 400) goes in the way. So between 1901 and 2099
** (inclusive) we can warp time stamps by 28 years to make them suitable for
** localtime() and gmtime() if we have trouble. Of course this will play
** hubbubb with the DST zone switches, so we should do it only if necessary;
** but as we NEED a proper conversion to dates via gmtime() we should try to
** cope with as many idiosyncrasies as possible.
*/
#define SOLAR_CYCLE_SECS   0x34AADC80UL	/* 7*1461*86400*/
#define SOLAR_CYCLE_YEARS  28
#define MINFOLD -3
#define MAXFOLD  3

struct tm *
ntp2unix_tm(
	u_long ntp, int local
	)
{
	struct tm *tm;
	int32      folds = 0;
	time_t     t     = time(NULL);
	u_int32    dwlo  = (int32)t; /* might expand for SIZEOF_TIME_T < 4 */
#if ( SIZEOF_TIME_T > 4 )
	int32      dwhi  = (int32)(t >> 16 >> 16);/* double shift: avoid warnings */
#else
	/*
	 * Get the correct sign extension in the high part. 
	 * (now >> 32) may not work correctly on every 32 bit 
	 * system, e.g. it yields garbage under Win32/VC6.
	 */
    int32		dwhi = (int32)(t >> 31);
#endif
	
	/* Shift NTP to UN*X epoch, then unfold around currrent time. It's
	 * important to use a 32 bit max signed value -- LONG_MAX is 64 bit on
	 * a 64-bit system, and it will give wrong results.
	 */
	M_ADD(dwhi, dwlo, 0, ((1UL << 31)-1)); /* 32-bit max signed */
	if ((ntp -= JAN_1970) > dwlo)
		--dwhi;
	dwlo = ntp;
 
#   if SIZEOF_TIME_T < 4
#	error sizeof(time_t) < 4 -- this will not work!
#   elif SIZEOF_TIME_T == 4

	/*
	** If the result will not fit into a 'time_t' we have to warp solar
	** cycles. That's implemented by looped addition / subtraction with
	** M_ADD and M_SUB to avoid implicit 64 bit operations, especially
	** division. As he number of warps is rather limited there's no big
	** performance loss here.
	**
	** note: unless the high word doesn't match the sign-extended low word,
	** the combination will not fit into time_t. That's what we use for
	** loop control here...
	*/
	while (dwhi != ((int32)dwlo >> 31)) {
		if (dwhi < 0 && --folds >= MINFOLD)
			M_ADD(dwhi, dwlo, 0, SOLAR_CYCLE_SECS);
		else if (dwhi >= 0 && ++folds <= MAXFOLD)
			M_SUB(dwhi, dwlo, 0, SOLAR_CYCLE_SECS);
		else
			return NULL;
	}

#   else

	/* everything fine -- no reduction needed for the next thousand years */

#   endif

	/* combine hi/lo to make time stamp */
	t = ((time_t)dwhi << 16 << 16) | dwlo;	/* double shift: avoid warnings */

#   ifdef _MSC_VER	/* make this an autoconf option? */

	/*
	** The MSDN says that the (Microsoft) Windoze versions of 'gmtime()'
	** and 'localtime()' will bark on time stamps < 0. Better to fix it
	** immediately.
	*/
	while (t < 0) { 
		if (--folds < MINFOLD)
			return NULL;
		t += SOLAR_CYCLE_SECS;
	}
			
#   endif /* Microsoft specific */

	/* 't' should be a suitable value by now. Just go ahead. */
	while ( (tm = (*(local ? localtime : gmtime))(&t)) == 0)
		/* seems there are some other pathological implementations of
		** 'gmtime()' and 'localtime()' somewhere out there. No matter
		** if we have 32-bit or 64-bit 'time_t', try to fix this by
		** solar cycle warping again...
		*/
		if (t < 0) {
			if (--folds < MINFOLD)
				return NULL;
			t += SOLAR_CYCLE_SECS;
		} else {
			if ((++folds > MAXFOLD) || ((t -= SOLAR_CYCLE_SECS) < 0))
				return NULL; /* That's truely pathological! */
		}
	/* 'tm' surely not NULL here... */
	NTP_INSIST(tm != NULL);
	if (folds != 0) {
		tm->tm_year += folds * SOLAR_CYCLE_YEARS;
		if (tm->tm_year <= 0 || tm->tm_year >= 200)
			return NULL;	/* left warp range... can't help here! */
	}
	return tm;
}


static char *
common_prettydate(
	l_fp *ts,
	int local
	)
{
	char *bp;
	struct tm *tm;
	u_long sec;
	u_long msec;

	LIB_GETBUF(bp);
	
	sec = ts->l_ui;
	msec = ts->l_uf / 4294967;	/* fract / (2 ** 32 / 1000) */

	tm = ntp2unix_tm(sec, local);
	if (!tm)
		snprintf(bp, LIB_BUFLENGTH,
			 "%08lx.%08lx  --- --- -- ---- --:--:--",
			 (u_long)ts->l_ui, (u_long)ts->l_uf);
	else
		snprintf(bp, LIB_BUFLENGTH,
			 "%08lx.%08lx  %s, %s %2d %4d %2d:%02d:%02d.%03lu",
			 (u_long)ts->l_ui, (u_long)ts->l_uf,
			 days[tm->tm_wday], months[tm->tm_mon],
			 tm->tm_mday, 1900 + tm->tm_year, tm->tm_hour,
			 tm->tm_min, tm->tm_sec, msec);
	
	return bp;
}


char *
prettydate(
	l_fp *ts
	)
{
	return common_prettydate(ts, 1);
}


char *
gmprettydate(
	l_fp *ts
	)
{
	return common_prettydate(ts, 0);
}