From: Harlan Stenn Date: Tue, 11 Nov 2008 21:49:27 +0000 (-0500) Subject: [Bug 1011] gmtime() returns NULL on windows where it would not under Unix X-Git-Tag: NTP_4_2_5P140~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=493d6de408873f21d8224d7abe39f0314d75e604;p=thirdparty%2Fntp.git [Bug 1011] gmtime() returns NULL on windows where it would not under Unix bk: 4919fde7iijILbN9Ll0RjlpKZjniGA --- diff --git a/ChangeLog b/ChangeLog index 4c979d2d0..1623be496 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,5 @@ +* [Bug 1011] gmtime() returns NULL on windows where it would not under Unix. +* Updated caljulian.c and prettydate.c from Juergen Perlinger. (4.2.5p139) 2008/11/11 Released by Harlan Stenn * Typo fix to driver20.html. (4.2.5p138) 2008/11/10 Released by Harlan Stenn diff --git a/libntp/caljulian.c b/libntp/caljulian.c index 8d5c6e187..158038de5 100644 --- a/libntp/caljulian.c +++ b/libntp/caljulian.c @@ -7,25 +7,29 @@ #include "ntp_calendar.h" #include "ntp_stdlib.h" #include "ntp_fp.h" +#include "ntp_unixtime.h" + +#ifdef HAVE_LIMITS_H +# include +#endif + +#if !(defined(ISC_CHECK_ALL) || defined(ISC_CHECK_NONE) || \ + defined(ISC_CHECK_ENSURE) || defined(ISC_CHECK_INSIST) || \ + defined(ISC_CHECK_INVARIANT)) +# define ISC_CHECK_ALL +#endif + #include "ntp_assert.h" -#if 0 -/* - * calmonthtab - days-in-the-month table +#if 1 + +/* Updated 2008-11-10 Juergen Perlinger + * + * Make the conversion 2038-proof with proper NTP epoch unfolding and extended + * precision calculations. Though we should really get a 'time_t' with more + * than 32 bits at least until 2037, because the unfolding cannot work after + * the wrap of the 32-bit 'time_t'. */ -static u_short calmonthtab[11] = { - JAN, - FEB, - MAR, - APR, - MAY, - JUN, - JUL, - AUG, - SEP, - OCT, - NOV -}; void caljulian( @@ -33,89 +37,120 @@ caljulian( register struct calendar *jt ) { - u_long ntp_day; - u_long minutes; + u_long saved_time = ntptime; + u_long ntp_day; /* days (since christian era or in year) */ + u_long n400; /* # of Gregorian cycles */ + u_long n100; /* # of normal centuries */ + u_long n4; /* # of 4-year cycles */ + u_long n1; /* # of years into a leap year cycle */ + u_long sclday; /* scaled days for month conversion */ + int leaps; /* # of leaps days in year */ + time_t now; /* current system time */ + u_int32 tmplo; /* double precision tmp value / lo part */ + int32 tmphi; /* double precision tmp value / hi part */ + + NTP_INSIST(NULL != jt); + /* - * Absolute, zero-adjusted Christian era day, starting from the - * mythical day 12/1/1 BC + * First we have to unfold the ntp time stamp around the current time + * to make sure we are in the right epoch. Also we we do *NOT* fold + * before the begin of the first NTP epoch, so we WILL have a + * non-negative time stamp afterwards. Though at the time of this + * writing (2008 A.D.) it would be really strange to have systems + * running with clock set to he 1960's or before... */ - u_long acez_day; + now = time(NULL); + tmplo = (u_int32)now; + tmphi = (int32)(now >> 16 >> 16); + + M_ADD(tmphi, tmplo, 1, LONG_MAX + JAN_1970); + if ((ntptime > tmplo) && (tmphi > 0)) + --tmphi; + tmplo = ntptime; + + /* + * Now split into days and seconds-of-day, using the fact that + * SECSPERDAY (86400) == 675 * 128; we can get roughly 17000 years of + * time scale, using only 32-bit calculations. Some magic numbers here, + * sorry for that. + */ + ntptime = tmplo & 127; /* save remainder bits */ + tmplo = (tmplo >> 7) | (tmphi << 25); + ntp_day = (u_long)tmplo / 675; + ntptime += ((u_long)tmplo % 675) << 7; - u_long d400; /* Days into a Gregorian cycle */ - u_long d100; /* Days into a normal century */ - u_long d4; /* Days into a 4-year cycle */ - u_long n400; /* # of Gregorian cycles */ - u_long n100; /* # of normal centuries */ - u_long n4; /* # of 4-year cycles */ - u_long n1; /* # of years into a leap year */ - /* cycle */ + /* some checks for the algorithm */ + NTP_ENSURE(ntptime < SECSPERDAY); + NTP_INVARIANT((u_long)(ntptime + ntp_day * SECSPERDAY) == saved_time); /* * Do the easy stuff first: take care of hh:mm:ss, ignoring leap * seconds */ jt->second = (u_char)(ntptime % SECSPERMIN); - minutes = ntptime / SECSPERMIN; - jt->minute = (u_char)(minutes % MINSPERHR); - jt->hour = (u_char)((minutes / MINSPERHR) % HRSPERDAY); + ntptime /= SECSPERMIN; + jt->minute = (u_char)(ntptime % MINSPERHR); + ntptime /= MINSPERHR; + jt->hour = (u_char)(ntptime); - /* - * Find the day past 1900/01/01 00:00 UTC - */ - ntp_day = ntptime / SECSPERDAY; - acez_day = DAY_NTP_STARTS + ntp_day - 1; - n400 = acez_day/GREGORIAN_CYCLE_DAYS; - d400 = acez_day%GREGORIAN_CYCLE_DAYS; - n100 = d400 / GREGORIAN_NORMAL_CENTURY_DAYS; - d100 = d400 % GREGORIAN_NORMAL_CENTURY_DAYS; - n4 = d100 / GREGORIAN_NORMAL_LEAP_CYCLE_DAYS; - d4 = d100 % GREGORIAN_NORMAL_LEAP_CYCLE_DAYS; - n1 = d4 / DAYSPERYEAR; + /* check time invariants */ + NTP_ENSURE(jt->second < SECSPERMIN); + NTP_ENSURE(jt->minute < MINSPERHR); + NTP_ENSURE(jt->hour < HRSPERDAY); /* - * Calculate the year and year-of-day + * Find the day past 1900/01/01 00:00 UTC */ - jt->yearday = (u_short)(1 + d4%DAYSPERYEAR); - jt->year = (u_short)(400*n400 + 100*n100 + n4*4 + n1); + ntp_day += DAY_NTP_STARTS - 1; /* convert to days in CE */ + n400 = ntp_day / GREGORIAN_CYCLE_DAYS; /* split off cycles */ + ntp_day %= GREGORIAN_CYCLE_DAYS; + n100 = ntp_day / GREGORIAN_NORMAL_CENTURY_DAYS; + ntp_day %= GREGORIAN_NORMAL_CENTURY_DAYS; + n4 = ntp_day / GREGORIAN_NORMAL_LEAP_CYCLE_DAYS; + ntp_day %= GREGORIAN_NORMAL_LEAP_CYCLE_DAYS; + n1 = ntp_day / DAYSPERYEAR; + ntp_day %= DAYSPERYEAR; /* now zero-based day-of-year */ + + NTP_ENSURE(ntp_day < 366); - if (n100 == 4 || n1 == 4) - { /* - * If the cycle year ever comes out to 4, it must be December 31st - * of a leap year. + * Calculate the year and day-of-year */ - jt->month = 12; - jt->monthday = 31; - jt->yearday = 366; + jt->year = (u_short)(400*n400 + 100*n100 + 4*n4 + n1); + + if ((n100 | n1) > 3) { + /* + * If the cycle year ever comes out to 4, it must be December + * 31st of a leap year. + */ + jt->month = 12; + jt->monthday = 31; + jt->yearday = 366; + } else { + /* + * The following code is according to the excellent book + * 'Calendrical Calculations' by Nachum Dershowitz and Edward + * Reingold. It converts the day-of-year into month and + * day-of-month, using a linear transformation with integer + * truncation. Magic numbers again, but they will not be used + * anywhere else. + */ + sclday = ntp_day * 7 + 217; + leaps = ((n1 == 3) && ((n4 != 24) || (n100 == 3))) ? 1 : 0; + if (ntp_day >= JAN + FEB + leaps) + sclday += (2 - leaps) * 7; + ++jt->year; + jt->month = sclday / 214; + jt->monthday = (u_char)((sclday % 214) / 7 + 1); + jt->yearday = (u_short)(1 + ntp_day); } - else - { - /* - * Else, search forwards through the months to get the right month - * and date. - */ - int monthday; - - jt->year++; - monthday = jt->yearday; - for (jt->month=0;jt->month<11; jt->month++) - { - int t; - - t = monthday - calmonthtab[jt->month]; - if (jt->month == 1 && is_leapyear(jt->year)) - t--; - - if (t > 0) - monthday = t; - else - break; - } - jt->month++; - jt->monthday = (u_char) monthday; - } + /* check date invariants */ + NTP_ENSURE(1 <= jt->month && jt->month <= 12); + NTP_ENSURE(1 <= jt->monthday && jt->monthday <= 31); + NTP_ENSURE(1 <= jt->yearday && jt->yearday <= 366); } + #else /* Updated 2003-12-30 TMa diff --git a/libntp/prettydate.c b/libntp/prettydate.c index e9aa7d84d..eba71a901 100644 --- a/libntp/prettydate.c +++ b/libntp/prettydate.c @@ -9,6 +9,10 @@ #include "ntp_stdlib.h" #include "ntp_assert.h" +#ifdef HAVE_LIMITS_H +# include +#endif + static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" @@ -20,55 +24,119 @@ static const char *days[] = { /* Helper function to handle possible wraparound of the ntp epoch. - Works by assuming that the localtime/gmtime library functions - have been updated so that they work + 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... */ -#define MAX_EPOCH_NR 1000 + +/* 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 ) { - time_t t, curr; struct tm *tm; - int curr_year, epoch_nr; - - /* First get the current year: */ - curr = time(NULL); - tm = local ? localtime(&curr) : gmtime(&curr); - if (!tm) return NULL; - - curr_year = 1900 + tm->tm_year; + int32 folds = 0; + time_t t = time(NULL); + u_int32 dwlo = (int32)t; /* might expand for SIZEOF_TIME_T < 4 */ + int32 dwhi = (int32)(t >> 16 >> 16);/* double shift: avoid warnings */ + + /* Shift NTP to UN*X epoch, then unfold around currrent time */ + M_ADD(dwhi, dwlo, 0, LONG_MAX); + 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; + } - /* Convert the ntp timestamp to a unix utc seconds count: */ - t = (time_t) ntp - JAN_1970; +# else - /* Check that the ntp timestamp is not before a 136 year window centered - around the current year: + /* everything fine -- no reduction needed for the next thousand years */ - Failsafe in case of an infinite loop: - Allow up to 1000 epochs of 136 years each! - */ - for (epoch_nr = 0; epoch_nr < MAX_EPOCH_NR; epoch_nr++) { - tm = local ? localtime(&t) : gmtime(&t); - NTP_INSIST(tm != NULL); +# endif -#if SIZEOF_TIME_T < 4 -# include "Bletch: sizeof(time_t) < 4!" -#endif + /* combine hi/lo to make time stamp */ + t = ((time_t)dwhi << 16 << 16) | dwlo; /* double shift: avoid warnings */ -#if SIZEOF_TIME_T == 4 - /* If 32 bits, then year is 1970-2038, so no sense looking */ - epoch_nr = MAX_EPOCH_NR; -#else /* SIZEOF_TIME_T > 4 */ - /* Check that the resulting year is in the correct epoch: */ - if (1900 + tm->tm_year > curr_year - 68) break; +# ifdef _MSC_VER /* make this an autoconf option? */ - /* Epoch wraparound: Add 2^32 seconds! */ - t += (time_t) 65536 << 16; -#endif /* SIZEOF_TIME_T > 4 */ + /* + ** 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))) + /* 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... */ + 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; }