The getDateFromJulianDay
function originally proposed is too computationally intensive for effective use on an embedded device, containing many multiplication and division operations on large long
variables or, as originally written in C++, longlong
variables.
I think I hunted down an efficient epoch to date algorithm for an embedded device.
After fruitless Googling, I found myself back on Stack Overflow, and found the question Converting epoch time to “real” date/time, asking about self-written epoch time to date implementation and provides a suitable algorithm. This answer to the question references the gmtime.c source code, and provided the source in C I needed to write a Python conversion algorithm:
/*
* gmtime - convert the calendar time into broken down time
*/
/* $Header: /opt/proj/minix/cvsroot/src/lib/ansi/gmtime.c,v 1.1.1.1 2005/04/21 14:56:05 beng Exp $ */
#include <time.h>
#include <limits.h>
#include "loc_time.h"
struct tm *
gmtime(register const time_t *timer)
{
static struct tm br_time;
register struct tm *timep = &br_time;
time_t time = *timer;
register unsigned long dayclock, dayno;
int year = EPOCH_YR;
dayclock = (unsigned long)time % SECS_DAY;
dayno = (unsigned long)time / SECS_DAY;
timep->tm_sec = dayclock % 60;
timep->tm_min = (dayclock % 3600) / 60;
timep->tm_hour = dayclock / 3600;
timep->tm_wday = (dayno + 4) % 7; /* day 0 was a thursday */
while (dayno >= YEARSIZE(year)) {
dayno -= YEARSIZE(year);
year++;
}
timep->tm_year = year - YEAR0;
timep->tm_yday = dayno;
timep->tm_mon = 0;
while (dayno >= _ytab[LEAPYEAR(year)][timep->tm_mon]) {
dayno -= _ytab[LEAPYEAR(year)][timep->tm_mon];
timep->tm_mon++;
}
timep->tm_mday = dayno + 1;
timep->tm_isdst = 0;
return timep;
}
Additionally, the analysis of the question Why is gmtime implemented this way? helped affirm that the gmtime
function is fairly efficient.
Using the raspberryginger.com minix Doxygen documentation site, I was able to find the C macros and constants that were included in gmtime.c from loc_time.h. The relevant code snippet:
#define YEAR0 1900 /* the first year */
#define EPOCH_YR 1970 /* EPOCH = Jan 1 1970 00:00:00 */
#define SECS_DAY (24L * 60L * 60L)
#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
#define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365)
#define FIRSTSUNDAY(timp) (((timp)->tm_yday - (timp)->tm_wday + 420) % 7)
#define FIRSTDAYOF(timp) (((timp)->tm_wday - (timp)->tm_yday + 420) % 7)
#define TIME_MAX ULONG_MAX
#define ABB_LEN 3
extern const int _ytab[2][10];
And the extern const int _ytab
was defined in misc.c:
const int _ytab[2][12] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
Some other things I found:
- The gmtime.c File Reference was very helpful for finding dependencies.
- The
gmtime
function starts indexing the Month, Day of Week, and Day of Year at the number zero, (maximal ranges of 0-11, 0-6, 0-365, respectively), whereas the Day of Month starts at the number 1, (1-31), see the IBM gmtime()
reference.
I re-wrote the gmtime
function for Python 1.5.2+:
def is_leap_year(year):
return ( not ((year) % 4) and ( ((year) % 100) or (not((year) % 400)) ) )
def year_size(year):
if is_leap_year(year):
return 366
else:
return 365
def ntp_time_to_date(ntp_time):
year = 1900 # EPOCH_YR for NTP
ytab = [ [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] ]
(dayno,dayclock) = divmod(ntp_time, 86400L)
dayno = int(dayno)
# Calculate time of day from seconds on the day's clock.
(hour, sec_past_hour) = divmod(dayclock,3600)
hour = int(hour)
(min, sec) = divmod(int(sec_past_hour),60)
while (dayno >= year_size(year)):
dayno = dayno - year_size(year)
year = year + 1
month = 1 # NOTE: month range is (1-12)
while (dayno >= ytab[is_leap_year(year)][month]):
dayno = dayno - ytab[is_leap_year(year)][month]
month = month + 1
day = dayno + 1
return (year, month, day, hour, min, sec)
Modifications I made re-factoring the C++ gmtime
function to my Python function ntp_time_to_date(ntp_time)
:
- Changed epoch from UNIX epoch of 1970 to NTP epoch of 1900 (the prime epoch for NTP).
- Slightly streamlined time of day calculation.
- Comparing time of day calculation of
gmtime
to ntp_time_to_date
:
- Both
(dayclock % 3600) / 60
and dayclock / 3600
occur behind the scenes in divmod(dayclock,3600)
and divmod(sec_past_hour,60)
.
- Only real difference is that
divmod(sec_past_hour,60)
avoids modulo of dayclock
(0-86399) by 60 via dayclock % 60
, and instead does modulo of sec_past_hour
(0-3599) by 60 within divmod(sec_past_hour,60)
.
- Removed variables and code I did not need, for example, day of week.
- Changed indexing of Month to start at 1, so Month range is (1-12) instead of (0-11)
- Type cast variables away from
long
as soon as values were less than 65535 to greatly decrease code execution time.
- The requires long variables are:
ntp_time
, seconds since 1900 (0-4294967295)
dayclock
, seconds into day (0-86399)
- The largest of the rest of the variables is the calculated year within the date.
The Python ntp_time_to_date
function (with its dependencies) runs successfully on the Telit GC-864 on an embedded version of Python 1.5.2+, as well as on Python 2.7.3, but of course use the datetime library if you can.