6

我试图找出从纪元秒(自 NTP 纪元 1900-01-01 00:00 起)转换为日期时间字符串(MM/DD/YY,hh:mm:ss)的最佳方法,无需任何库/模块/外部功能,因为它们在嵌入式设备上不可用。

我的第一个想法是查看Python 日期时间模块源代码,但这对我来说不是很有用。

我在 Python 中的最初尝试使用从 0001-01-01 到日期的天数转换,使用从C++ 源getDateFromJulianDay适应 Python并结合模运算来获取时间。它有效,但有更好的方法吗?

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 功能。时间同步方法的讨论最好留在别处,例如这个问题

笔记:

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

2 回答 2

7

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

我想我为嵌入式设备找到了一个有效的时代算法。

谷歌搜索无果后,我发现自己又回到了 Stack Overflow,找到了Converting epoch time to “real” date/time这个问题,询问自写的 epoch time to date implementation 并提供了一个合适的算法。该问题的答案引用了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;
}

另外,分析一下为什么gmtime是这样实现的?帮助确认该gmtime功能相当有效。

使用raspberryginger.com minix Doxygen 文档站点,我能够从loc_time.h中找到 gmtime.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];

并且在misc.cextern 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.c文件参考对于查找依赖项非常有帮助。
  • gmtime函数从数字 0 开始索引月份、星期几和一年中的某一天(最大范围分别为 0-11、0-6、0-365),而月份日期从数字 1 开始, (1-31),请参阅IBMgmtime()参考资料

gmtime我为 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)

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

  • 将纪元从 1970 年的 UNIX 纪元更改为 1900 年的 NTP 纪元(NTP的主要纪元)。
  • 稍微简化了一天中的时间计算。
    • gmtime比较一天中的时间计算ntp_time_to_date
      • (dayclock % 3600) / 60和都发生在和dayclock / 3600的幕后。divmod(dayclock,3600)divmod(sec_past_hour,60)
      • 唯一真正的区别是divmod(sec_past_hour,60)避免dayclock通过 60对 (0-86399) 取dayclock % 60模,而是在sec_past_hour内对 (0-3599) 取 60 取模divmod(sec_past_hour,60)
  • 删除了我不需要的变量和代码,例如星期几。
  • 将月份的索引更改为从 1 开始,因此月份范围是 (1-12) 而不是 (0-11)
  • long值小于 65535 时立即 键入强制类型转换变量,以大大减少代码执行时间。
    • 需要长变量是:
      • ntp_time, 自 1900 年以来的秒数 (0-4294967295)
      • dayclock, 秒到一天 (0-86399)
    • 其余变量中最大的是该日期内的计算年份。

Pythonntp_time_to_date函数(及其依赖项)在 Python 1.5.2+ 的嵌入式版本以及 Python 2.7.3 上的 Telit GC-864 上成功运行,但当然可以使用 datetime 库。

于 2013-03-26T23:28:27.757 回答
0

TL;博士

如果您使用的是 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 个月计数到当前月份

试验结果

每次试验使用相同的 NTP 时间输入(每个函数输出“3/25/2013 18:40”)在实际 GC-864(注意:不是GC-864-V2)上运行的计时测试结果。使用 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变量进行类型转换的优化具有相当显着的效果。没有这个优化:intntp_time_to_date(ntp_time)

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

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

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

于 2016-04-13T14:00:21.510 回答