简体   繁体   English

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

[英]Epoch Seconds to Date Conversion on a Limited Embedded Device

I am trying to figure out the best way to convert from epoch seconds (since NTP epoch 1900-01-01 00:00) to a datetime string (MM/DD/YY,hh:mm:ss) without any libraries/modules/external functions, as they are not available on an embedded device. 我试图找出从纪元秒(自NTP纪元1900-01-01 00:00)转换为日期时间字符串(MM / DD / YY,hh:mm:ss)的最佳方法,没有任何库/模块/外部功能,因为它们在嵌入式设备上不可用。

My first thought was to look at the Python datetime module source code , however that was not very useful to me. 我的第一个想法是查看Python datetime模块源代码 ,但这对我来说并不是很有用。

My initial attempt in Python uses a conversion of days since 0001-01-01 to date using getDateFromJulianDay adapted to Python from C++ source , combined with modulo operations to obtain time. 我在Python中的初步尝试使用从0001-01-01开始的日期转换,使用适用于C ++源代码的 Python的getDateFromJulianDay ,结合模运算来获取时间。 It works, but is there a better way? 它有效,但有更好的方法吗?

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

Why I'm doing this: I am getting the current time from an NTP server, and taking this time at face value for updating a hardware real time clock (RTC) that only accepts the date, time and time zone: MM/DD/YY,hh:mm:ss,±zz. 为什么我这样做:我从NTP服务器获取当前时间,并将此时间用于更新仅接受日期,时间和时区的硬件实时时钟(RTC)的面值:MM / DD / YY,HH:MM:SS,±ZZ。 I plan to implement true NTP capabilities at a later date. 我计划在以后实现真正的NTP功能。 A discussion of time synchronization methods is best left elsewhere, such as this question . 关于时间同步方法的讨论最好留在其他地方,例如这个问题

Notes: 笔记:

  • My embedded device is a Telit GC-864 cellular modem that runs Python 1.5.2+ and only has limited operators (mostly just C operators), no modules, and some of the expected built-in Python types. 我的嵌入式设备是Telit GC-864蜂窝调制解调器,运行Python 1.5.2+并且只有有限的运算符(主要是C运算符),没有模块,以及一些预期的内置Python类型。 The exact capabilities are here , if you're interested. 确切的能力都在这里 ,如果你有兴趣。 I write Python for this device as if I'm writing C code - not very Pythonic, I know. 我为这个设备编写Python,好像我正在编写C代码 - 不是很Pythonic,我知道。
  • I realize NTP is best used only for a time offset, however with limited options, I'm using NTP as an absolute time source (I could add the check for the NTP rollover in 2036 to enable another 136 years of operation). 我意识到NTP最好只用于时间偏移,但是由于选项有限,我使用NTP作为绝对时间源(我可以在2036年添加对NTP翻转的检查以启用另外136年的操作)。
  • The GC-864-V2 device with up-to-date firmware does have NTP capability, but the GC-864 I need to use is stuck on a previous release of firmware. 具有最新固件的GC-864-V2设备具有NTP功能,但我需要使用的GC-864卡在以前版本的固件上。

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 . 最初提出的getDateFromJulianDay函数计算量太大,无法在嵌入式设备上有效使用,包含对大型long变量的许多乘法和除法运算,或者最初用C ++编写的long longlong变量

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. 在没有结果的谷歌搜索之后,我发现自己回到了Stack Overflow,发现了将纪元时间转换为“真实”日期/时间的问题 ,询问自编时间实现的时间,并提供了一个合适的算法。 This answer to the question references the gmtime.c source code , and provided the source in CI needed to write a Python conversion algorithm: 这个问题的答案引用了gmtime.c源代码 ,并提供了编写Python转换算法所需的CI 源代码

/*
 * 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? 另外,对问题的分析 为什么gmtime以这种方式实现? helped affirm that the gmtime function is fairly efficient. 帮助确认gmtime功能相当有效。

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 . 使用raspberryginger.com MINIX Doxygen文档的网站 ,我能找到被列入了C宏和常量gmtime.cloc_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 : extern const int _ytabmisc.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. gmtime.c文件参考对于查找依赖项非常有用。
  • 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 . gmtime函数开始将月份,星期几和日期编号为零,(最大范围分别为0-11,0-6,0-365),而月份日期从数字1开始,(1-31),请参阅IBM gmtime()参考

I re-wrote the gmtime function for Python 1.5.2+: 我重写了Python 1.5.2+的gmtime函数:

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) : 修改我将C ++ gmtime函数重新分解为我的Python函数ntp_time_to_date(ntp_time)

  • Changed epoch from UNIX epoch of 1970 to NTP epoch of 1900 ( the prime epoch for NTP ). 将时代从1970年的UNIX时代改为1900年的NTP时代( NTP的主要时代 )。
  • Slightly streamlined time of day calculation. 略微精简的时间计算。
    • Comparing time of day calculation of gmtime to ntp_time_to_date : gmtime时间计算与ntp_time_to_date进行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) . 两个(dayclock % 3600) / 60dayclock / 3600发生在divmod(dayclock,3600)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) . 只有真正的区别在于divmod(sec_past_hour,60)避免了模dayclock通过(0-86399)60 dayclock % 60 ,而是确实的模sec_past_hour 60(0-3599)内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) 将月份的索引更改为从1开始,因此月份范围是(1-12)而不是(0-11)
  • Type cast variables away from long as soon as values were less than 65535 to greatly decrease code execution time. long值小于65535,就将long变量从long键入,以大大减少代码执行时间。
    • The requires long variables are: 需要长变量是:
      • ntp_time , seconds since 1900 (0-4294967295) ntp_time ,自1900年以来的秒数(0-4294967295)
      • dayclock , seconds into day (0-86399) dayclock ,秒到天(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. Python ntp_time_to_date函数(及其依赖项)在嵌入式版本的Python 1.5.2+以及Python 2.7.3上的Telit GC-864上成功运行,但如果可以,当然可以使用datetime库。

TL;DR TL; DR

If you are using a Telit GC-864, the Python interpreter seemingly inserts some sort of delay in-between each line of code execution. 如果您使用的是Telit GC-864,Python解释器似乎会在每行代码执行之间插入某种延迟。

For a Telit GC-864, the function in my question getDateFromJulianDay(julianDay) is faster than the function in my answer, ntp_time_to_date(ntp_time) . 对于泰利特GC-864,在我的问题的功能getDateFromJulianDay(julianDay)比我的回答,函数更快ntp_time_to_date(ntp_time)

More Detail 更多详情

The number of code lines dominates execution time on a GC-864 more than the complexity of the code - weird, I know. 代码的数量主导着GC-864上的执行时间,而不是代码的复杂性 - 我知道这很奇怪。 The function getDateFromJulianDay(julianDay) in my question has a handful of complex operations, maybe 15 lines of code. 我的问题中的函数getDateFromJulianDay(julianDay)有一些复杂的操作,可能是15行代码。 The function in my answer ntp_time_to_date(ntp_time) has simpler computation complexity, but the while loops cause 100+ lines of code execution: 我的答案ntp_time_to_date(ntp_time)的函数具有更简单的计算复杂性,但while循环导致100多行代码执行:

  • One loop counts from 1900 to the current year 一个循环计数从1900到当前年份
  • Another loop counts from month 1 to the current month 另一个循环从第1个月到当前月份计数

Test Results 检测结果

Timing test results running on an actual GC-864 (note: not a GC-864-V2) using the same NTP time input for each trial (each function outputs "3/25/2013 18:40"). 在实际GC-864上运行的定时测试结果(注意: 不是 GC-864-V2),每次试验使用相同的NTP时间输入(每个功能输出“3/25/2013 18:40”)。 Timing was performed using printf statement debugging, and a serial terminal on a computer would time stamp each line sent by the GC-864. 使用printf语句调试执行时序,计算机上的串行终端将为GC-864发送的每一行添加时间戳。

getDateFromJulianDay(julianDay) Trials: getDateFromJulianDay(julianDay)试验:

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

ntp_time_to_date(ntp_time) Trials: ntp_time_to_date(ntp_time)试验:

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

The variability partially stems from the GC-864 cell modem servicing cellular network tasks periodically. 可变性部分源于定期为蜂窝网络任务提供服务的GC-864小区调制解调器。

For completeness, the optimization of typecasting the long variables to int as quickly as possible in ntp_time_to_date(ntp_time) has a fairly significant effect. 为了完整性,在ntp_time_to_date(ntp_time)尽可能快地将long变量类型转换为int的优化具有相当显着的效果。 Without this optimization: 没有这个优化:

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

Doing anything computationally involved on a Telit GC-864 running .pyo files in Python 1.5.2+ is not a great idea. 在Python 1.5.2+中运行Telit GC-864运行.pyo文件的任何计算任务都不是一个好主意。 Using the GC-864-V2, which has a built-in NTP capability is a possible solution for someone who comes across this issue. 对于遇到此问题的人来说,使用具有内置NTP功能的GC-864-V2是一种可行的解决方案。 Moreover, newer machine-to-machine (M2M) aka internet of things (IoT) cell phone modems are much more capable. 此外,更新的机器对机器(M2M)又称物联网(IoT)手机调制解调器功能更强大。

If you have a similar issue with the GC-864, consider using a newer and more modern cell phone modem . 如果您遇到与GC-864类似的问题,请考虑使用更新,更现代的手机调制解调器

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM