diff options
Diffstat (limited to 'libntp/caljulian.c')
-rw-r--r-- | libntp/caljulian.c | 213 |
1 files changed, 132 insertions, 81 deletions
diff --git a/libntp/caljulian.c b/libntp/caljulian.c index 71123dfe20d3..7673061b57ce 100644 --- a/libntp/caljulian.c +++ b/libntp/caljulian.c @@ -7,24 +7,25 @@ #include "ntp_calendar.h" #include "ntp_stdlib.h" #include "ntp_fp.h" +#include "ntp_unixtime.h" -#if 0 -/* - * calmonthtab - days-in-the-month table +#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 1 + +/* Updated 2008-11-10 Juergen Perlinger <juergen.perlinger@t-online.de> + * + * 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( @@ -32,89 +33,137 @@ 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... + * + * But'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. */ - u_long acez_day; - - 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 */ + now = time(NULL); + tmplo = (u_int32)now; +#if ( SIZEOF_TIME_T > 4 ) + tmphi = (int32)(now >> 16 >> 16); +#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. + */ + tmphi = (int32)(now >> 31); +#endif + + M_ADD(tmphi, tmplo, 0, ((1UL << 31)-1)); /* 32-bit max signed */ + M_ADD(tmphi, tmplo, 0, 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. (This could be streamlined for 64 bit machines, but + * is worth the trouble?) + */ + ntptime = tmplo & 127; /* save remainder bits */ + tmplo = (tmplo >> 7) | (tmphi << 25); + ntp_day = (u_int32)tmplo / 675; + ntptime += ((u_int32)tmplo % 675) << 7; + + /* some checks for the algorithm + * There's some 64-bit trouble out there: the original NTP time stamp + * had only 32 bits, so our calculation invariant only holds in 32 bits! + */ + NTP_ENSURE(ntptime < SECSPERDAY); + NTP_INVARIANT((u_int32)(ntptime + ntp_day * SECSPERDAY) == (u_int32)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 >= (u_long)(JAN + FEB + leaps)) + sclday += (2 - leaps) * 7; + ++jt->year; + jt->month = (u_char)(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 @@ -132,8 +181,10 @@ caljulian( ) { struct tm *tm; + NTP_REQUIRE(jt != NULL); tm = ntp2unix_tm(ntptime, 0); + NTP_INSIST(tm != NULL); jt->hour = (u_char) tm->tm_hour; jt->minute = (u_char) tm->tm_min; |