[ntp:questions] Converting from Y-m-d h:m:s

Terje Mathisen "terje.mathisen at tmsw.no" at ntp.org
Sat May 15 15:25:16 UTC 2010


unruh wrote:
> On 2010-05-14, Marc Leclerc<marc-leclerc at signaturealpha.com>  wrote:
>> Hi,
>>
>> is there any consideration when converting GPS/UTC DateTime YYYY MM DD
>> HH mm ss to a timespec value to set the system clock, I though I could
>> find a function to do this easily. Do the leap seconds in UTC have to be
>> considered when setting the system wall time
>
> AFAIK leapseconds are our of sight as far as UTC is concerned. Ie the
> UTC seconds since 1970 are 13 fewer than the actual number of seconds
> that someone living from 1970 to now has experienced.
> 86400*((Y-1970)*365+#leapyears+dayofyear-1)+3600*h+60*m+s
> is the number of UTC ( and ntp) seconds since 1970.
>
> Note that ntp has a subroutine to do the conversion from seconds to/from
> dates as does gettimeofday.

Yes: Use the Source!

I believe I was involved with at least one of the rewrites of the ntp 
internal to/from date/time functions.

In short:

Going from YMDHMS to unix (or ntp) seconds is really quite trivial, 
while the opposite direction is much harder!

My most efficient algorithm for the reverse function needs somewhere 
between 30 and 50 clock cycles (compare with 40 cycles for a single DIV 
opcode...), while the forward function is about twice as fast:

(This is the YMD to day# part only, the rest is trivial, right?)

/* ymd.c Terje Mathisen 2000-01-20. Code for calendar operations
  * Modified 2001-11-28, using ideas from peter at horizon.com
  * Updated 2005-04 with alternative ideas from IBM (Not used!)
  *
  * Assumes a two's complement binary machine, using arithmetic
  * right shift for signed ints, but is otherwise
  * portable to any machine with an ANSI C compiler.
  *
  * if USE_UNSIGNED_MASK is defined, then the current code will run on 
any machine
  * using at least 29 (with division) or 26 bits (MUL only) for an 
unsigned int.
  *
  * The table-less IBM approach requires at least 31 bits
  */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* #define USE_UNSIGNED_MASK */

#ifdef USE_UNSIGNED_SHIFT
typedef unsigned mask_t;

# define INC_IF_MASK(x,mask) ((x) += (mask) & 1)
# define DEC_IF_MASK(x,mask) ((x) -= (mask) & 1)
#else
typedef int mask_t;

# define INC_IF_MASK(x,mask) ((x) -= (mask))
# define DEC_IF_MASK(x,mask) ((x) += (mask))
#endif

/* These operations are valid for any date after 1584,
  * according to the Gregorian (modern) calendar
  */

#define STARTYEAR 0	/* Must be divisible by 400! */
/* Day zero is STARTYEAR-03-01

/* Table to simplify conversions between mm-dd and day_in_year */
static short daysToMonth[13] =
	{-1,30,60,91,121,152,183,213,244,274,305,336,366};

unsigned ymd2days(unsigned y, unsigned m, unsigned d)
{
     unsigned days, y100;

	/* Start by setting March = month zero: */
     mask_t mask = (mask_t) (m -= 3);
	/* Mask will now be in the (mask_t) -2 to 9 range, a shift by 4 is 
enough to make it -1 or 0: */
	mask >>= 4;

     DEC_IF_MASK(y, mask);			/* Decrement year if jan/feb */
     m += (mask & 12);               /* and increment the month: The 
year starts in March! */
     y -= STARTYEAR;
     y100 = y / 100;                 /* Centuries for leap year calc */
     days = y * 365 + (y >> 2) - y100 + (y100 >> 2);
     days += (unsigned) daysToMonth[m] + d;
     return days;
}

void days2ymd(unsigned days, unsigned *yy, unsigned *mm, unsigned *dd)
{
     unsigned y400, y100, y, m, d, gd;
	mask_t mask;

     /* Start by subtracting any full 400-year cycles: */
     y400 = days / 146097;
     days -= y400 * 146097;

#if 0
	/* Very good approximation to the number of years, will be wrong
	 * (too high) 257 times
	 * in a 400-year cycle, i.e. in 0.18% of all calls.
	 */

	/* A good compiler will replace this with a scaled reciprocal MUL! */
     y = (days + 1) * 400 / 146096;
#else
	/* Useful and faster approximation to the number of years, will be wrong
	 * (one too high) 910 times
	 * in a 400-year cycle, i.e. in 0.62% of all calls.
	 *
	 * Requires unsigned values with up to 29 significant bits!
	 */
     y = (days * 2871 + 1983) >> 20;
/* peter (at) horizon.com suggested this
  * formula which is slightly more accurate but
  * needs a full 32-bit unsigned temporary:
/*    y = (days * 22967 + 59235) >> 23; */
#endif

	/* Calculate # of centuries:
	 * Since y will be in the 0 to 400 range, the following
	 * approximation can be used instead of a division by 100:
	 * y100 = y / 100;  ~ (y * 41) >> 12;
	 */
     y100 = (y * 41) >> 12;

      /* # of days in those full years */
     gd = y * 365		/* Normal years */
		+ (y >> 2)		/* + leap years every 4 years */
		- y100			/* - missing century leap years */
		+ (y100 >> 2);  /* + every 400 years */

	/* test for the small chance of a wrong year: */
	if (gd > days) {
		y--;	/* y will be in the [0-399] range! */
		y100 = (y * 41) >> 12;
		/* The 400-year correction can be skipped! */
		gd = y * 365 + (y >> 2) - y100 /* + (y100 >> 2) */;
	}

     /* Calculate the offset into the current year: */
     days -= gd;
     /* Correct for starting date and 400-year cycles: */
     y += STARTYEAR + y400 * 400;

#if 1
     /* Make a FAST guess at the current month, can be too low: */
     m = days >> 5;

     mask = (mask_t) daysToMonth[m+1] - (mask_t) days;
	/* mask will be in the -18 to 18 range, use shift by 5: */
     mask >>= 5;
     /* If the guess was wrong then the mask will be -1, otherwise 0: */

     /* Increment month if needed */
     INC_IF_MASK(m, mask);

     /* The remainder is the day of the month */
     d = days - (unsigned) daysToMonth[m];

     /* Correct for the March 1 starting point: */
     mask = (mask_t) (9 - m) >> 4;
     m += 3;
     INC_IF_MASK(y, mask);
     m -= (unsigned) (mask & 12);

#else
	/* Based on code from an IBM report: Slightly slower, but
	 * uses no table lookups!
	 * Needs 31-bit unsigned ints:
	 */
	m = ((days + 31) * 1071);
	d = (((m & 0x7fff) * 62669) >> 26) + 1;
	m >>= 15;

	mask = (mask_t) (10 - m) >> 4;
	m += 2;
	INC_IF_MASK(y, mask);
	m -= (unsigned) (mask & 12);
#endif

     *yy = y; *mm = m; *dd = d;
}

int baddate(unsigned y, unsigned m, unsigned d)
{
	static unsigned char daysInMonth[12] = 
{31,29,31,30,31,30,31,31,30,31,30,31};
	if (m < 1 || m > 12) return 1;
	if (d < 1 || d > daysInMonth[m-1]) return 1;
	if (m == 2 && d == 29) {
		if (y & 3) return 1;
		if (y % 400 != 0 && y % 100 == 0) return 1;
	}
	return 0;
}

int main(int argc, char* argv[])
{
     unsigned days, y, m, d, dd, startday;
     clock_t clocks;
	int mhz = 0;

     static double ms_per_clock = 1000.0 / (double) CLOCKS_PER_SEC;

	if (argc > 1) {
		mhz = atoi(argv[1]);
		if (mhz <= 0) {
			printf("Usage: %s [mhz]\n", argv[0]);
			return 1;
		}
	}

	startday = ymd2days(1200, 3, 1);
     days2ymd(startday, &y, &m, &d);
     printf("Starting %u-%02u-%02u\n", y, m, d);
     clocks = clock();
     for (days = startday; days < startday + 146097 * 2000; days++) {
         days2ymd(days, &y, &m, &d);
#if 1
         dd = ymd2days(y, m, d);
         if (dd != days || baddate(y, m, d)) {
             printf("Error: %u -> %u-%02u-%02u -> %u!\n", days, y, m, d, 
dd);
             days2ymd(days, &y, &m, &d);
             dd = ymd2days(y, m, d);
         }
#endif
     }
     clocks = clock() - clocks;
     printf("Ending %u-%02u-%02u\nUsed %1.0fms (%1.1fns/iteration)\n",
            y, m, d,
            clocks * ms_per_clock,
		   clocks * ms_per_clock * 1e6 / (days - startday));
	if (mhz > 0) {
		printf("%1.1f clock cycles/iteration\n",
			clocks * ms_per_clock * (1e-3 * 1e6) * mhz /
			(days - startday));
	}

     return 0;
}




Terje
-- 
- <Terje.Mathisen at tmsw.no>
"almost all programming can be viewed as an exercise in caching"




More information about the questions mailing list