#include "ntp_calendar.h"
#include "ntp_stdlib.h"
#include "ntp_fp.h"
+#include "ntp_unixtime.h"
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#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 <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(
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
#include "ntp_stdlib.h"
#include "ntp_assert.h"
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
static const char *months[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
/* 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;
}