caljulian.c   [plain text]


/*
 * caljulian - determine the Julian date from an NTP time.
 */
#include <sys/types.h>

#include "ntp_types.h"
#include "ntp_calendar.h"
#include "ntp_stdlib.h"
#include "ntp_fp.h"
#include "ntp_unixtime.h"

#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'.
 */

void
caljulian(
	u_long		  		ntptime,
	register struct calendar	*jt
	)
{
	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);

	/*
	 * 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.
	 */
	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);
	}
	
	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);
	ntptime   /= SECSPERMIN;
	jt->minute = (u_char)(ntptime % MINSPERHR);
	ntptime   /= MINSPERHR;
	jt->hour   = (u_char)(ntptime);

	/* check time invariants */
	NTP_ENSURE(jt->second < SECSPERMIN);
	NTP_ENSURE(jt->minute < MINSPERHR);
	NTP_ENSURE(jt->hour   < HRSPERDAY);

	/*
	 * Find the day past 1900/01/01 00:00 UTC
	 */
	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);

	/*
	 * Calculate the year and day-of-year
	 */
	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);
	}

	/* 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

   Uses common code with the *prettydate functions to convert an ntp
   seconds count into a calendar date.
   Will handle ntp epoch wraparound as long as the underlying os/library 
   does so for the unix epoch, i.e. works after 2038.
*/

void
caljulian(
	u_long		  		ntptime,
	register struct calendar	*jt
	)
{
	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;
	jt->month = (u_char) (tm->tm_mon + 1);
	jt->monthday = (u_char) tm->tm_mday;
	jt->second = (u_char) tm->tm_sec;
	jt->year = (u_short) (tm->tm_year + 1900);
	jt->yearday = (u_short) (tm->tm_yday + 1);  /* Assumes tm_yday starts with day 0! */
}
#endif