有限嵌入式设备上的Epoch秒到期转换

时间:2013-03-26 00:30:51

标签: python datetime embedded epoch ntp

我正在尝试找出从纪元秒(从NTP纪元1900-01-01 00:00开始)转换为日期时间字符串(MM / DD / YY,hh:mm:ss)而没有任何库的最佳方法/ modules / external functions,因为它们在嵌入式设备上不可用。

我的第一个想法是查看Python datetime module source code,但这对我来说并不是很有用。

我在Python中的初步尝试使用从C++ source开始使用适用于Python的getDateFromJulianDay转换自0001-01-01以来的天数,并结合模运算来获取时间。它有效,但有更好的方法吗?

def getDateFromJulianDay(julianDay):
    # Gregorian calendar starting from October 15, 1582
    # This algorithm is from:
    # Henry F. Fliegel and Thomas C. van Flandern. 1968.
    # Letters to the editor:
    #     a machine algorithm for processing calendar dates.
    # Commun. ACM 11, 10 (October 1968), 657-. DOI=10.1145/364096.364097
    # http://doi.acm.org/10.1145/364096.364097
    ell = julianDay + 68569;
    n = (4 * ell) / 146097;
    ell = ell - (146097 * n + 3) / 4;
    i = (4000 * (ell + 1)) / 1461001;
    ell = ell - (1461 * i) / 4 + 31;
    j = (80 * ell) / 2447;
    d = ell - (2447 * j) / 80;
    ell = j / 11;
    m = j + 2 - (12 * ell);
    y = 100 * (n - 49) + i + ell;
    return y,m,d

# NTP response (integer portion) for Monday, March 25, 2013 at 6:40:43 PM
sec_since_1900 = 3573225643

# 2415021 is the number of days between 0001-01-01 and 1900-01-01,
#     the start of the NTP epoch
(year,month,day) =  getDateFromJulianDay(2415021 + sec_since_1900/60/60/24)

seconds_into_day = sec_since_1900 % 86400
(hour, sec_past_hour) = divmod(seconds_into_day,3600)
(min, sec) = divmod(sec_past_hour,60)
print 'year:',year,'month:',month,'day:',day
print 'hour:',hour,'min:',min,'sec:',sec

为什么我这样做: 我从NTP服务器获取当前时间,并将此时间用于更新仅接受日期,时间和时区的硬件实时时钟(RTC)的面值:MM / DD / YY,hh:mm:ss ,±ZZ。我计划在以后实现真正的NTP功能。关于时间同步方法的讨论最好留在其他地方,例如this question

注意:

  • 我的嵌入式设备是Telit GC-864蜂窝调制解调器,运行Python 1.5.2+并且只有有限的运算符(大多数只是C运算符),没有模块,以及一些预期的内置Python类型。如果您有兴趣,确切的功能是here。我为这个设备编写Python,好像我正在编写C代码 - 我知道不是Pythonic。
  • 我意识到NTP最好只用于时间偏移,但是选项有限,我使用NTP作为绝对时间源(我可以在2036年添加NTP翻转检查以启用另外136年的操作)
  • 具有最新固件的GC-864-V2设备确实具有NTP功能,但我需要使用的GC-864固定在以前版本的固件上。

2 个答案:

答案 0 :(得分:6)

最初提出的getDateFromJulianDay函数计算量太大,无法在嵌入式设备上有效使用,在大型long变量上包含许多乘法和除法运算,或者最初用C ++编写,{{3} }。

我想我已经找到了嵌入式设备的有效时代算法。

在没有结果的谷歌搜索之后,我发现自己回到了Stack Overflow,找到了问题longlong variables,询问自写时间的最新实现,并提供了一个合适的算法。问题的Converting epoch time to “real” date/time引用了answer,并提供了C中的源代码,我需要编写Python转换算法:

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

此外,问题gmtime.c source codeanalysis有助于确认gmtime功能相当有效。

使用Why is gmtime implemented this way?,我能够找到raspberryginger.com minix Doxygen documentation sitegmtime.c中包含的C宏和常量。相关代码段:

#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];

extern const int _ytab定义了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 } };

gmtime

我找到的其他一些事情:

  • loc_time.h对查找依赖项非常有帮助。
  • gmtime函数开始将月份,星期几和日期编号为零,(最大范围分别为0-11,0-6,0-365),而日期月份从数字1开始,(1-31),请参阅misc.c

我重写了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) 函数:

gmtime

修改我将C ++ ntp_time_to_date(ntp_time)函数重新分解为我的Python函数gmtime

  • 将1970年的UNIX纪元改为1900年的NTP纪元(gmtime.c File Reference)。
  • 稍微简化的时间计算。
    • 比较ntp_time_to_date(dayclock % 3600) / 60的时间计算:
      • dayclock / 3600divmod(dayclock,3600)幕后的divmod(sec_past_hour,60)divmod(sec_past_hour,60)都会出现dayclockdayclock % 60
      • 唯一真正的区别是sec_past_hour通过divmod(sec_past_hour,60)避免模数为long(0-86399)60,而是ntp_time模数(0-3599)在dayclock内的60。
  • 删除了我不需要的变量和代码,例如,星期几。
  • 将月份的索引更改为从1开始,因此月份范围为(1-12)而不是(0-11)
  • 一旦值小于65535,就从ntp_time_to_date输出变量,以大大减少代码执行时间。
    • 需要长变量是:
      • {{1}},自1900年以来的秒数(0-4294967295)
      • {{1}},秒进入白天(0-86399)
    • 其余变量中最大的一个是日期内的计算年份。

Python {{1}}函数(及其依赖项)在嵌入式版本的Python 1.5.2+以及Python 2.7.3上的Telit GC-864上成功运行,但当然使用日期时间图书馆,如果可以。

答案 1 :(得分:0)

TL; DR

如果您使用的是Telit GC-864,Python解释器似乎会在每行代码执行之间插入某种延迟。

对于Telit GC-864,我的问题getDateFromJulianDay(julianDay)中的函数比我的答案ntp_time_to_date(ntp_time)中的函数更快。

更多详情

代码的数量超过了GC-864上的执行时间而不是代码的复杂度 - 我知道这很奇怪。我的问题中的函数getDateFromJulianDay(julianDay)有一些复杂的操作,可能是15行代码。我的回答ntp_time_to_date(ntp_time)中的函数具有更简单的计算复杂性,但while循环导致100多行代码执行:

  • 一个循环计数从1900到当年
  • 另一个循环从第1个月开始计算到当前月份

测试结果

在实际的GC-864上运行测试结果(注意:一个GC-864-V2),每个试验使用相同的NTP时间输入(每个功能输出&#34; 3 / 25/2013 18:40&#34;)。使用printf语句调试执行时序,计算机上的串行终端会为GC-864发送的每一行添加时间戳。

getDateFromJulianDay(julianDay)试验:

  • 0.3802秒
  • 0.3370秒
  • 0.3370秒
  • 平均值:0.3514秒

ntp_time_to_date(ntp_time)试验:

  • 0.8899秒
  • 0.9072秒
  • 0.8986秒
  • 平均值:0.8986秒

可变性部分源于定期为蜂窝网络任务提供服务的GC-864小区调制解调器。

为了完整性,在long中尽快将int变量强制转换为ntp_time_to_date(ntp_time)具有相当显着的效果。没有这个优化:

  • 2.3155秒
  • 1.5034秒
  • 1.5293秒
  • 2.0995秒
  • 2.0909秒
  • 平均值:1.9255秒

在Python 1.5.2+中运行Telit GC-864运行.pyo文件的任何计算任务都不是一个好主意。对于遇到此问题的人来说,使用具有内置NTP功能的GC-864-V2是一种可行的解决方案。此外,更新的机器对机器(M2M)又称物联网(IoT)手机调制解调器功能更强大。

如果您遇到与GC-864类似的问题,请考虑使用更新更现代的手机调制解调器