Преобразование секунд эпохи в дату на ограниченном встроенном устройстве

Я пытаюсь найти лучший способ преобразования секунд эпохи (начиная с эпохи NTP 1900-01-01 00:00) в строку даты и времени (ММ/ДД/ГГ, чч: мм: сс) без каких-либо библиотек/модулей/ внешние функции, так как они недоступны на встроенном устройстве.

Моей первой мыслью было взглянуть на исходный код модуля Python datetime Однако мне это не очень помогло.

Моя первая попытка в Python использует преобразование дней с 01-01-0001 до настоящего времени с использованием getDateFromJulianDay, адаптированного для Python из исходник C++ в сочетании с операциями по модулю для получения времени. Это работает, но есть ли лучший способ?

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/ ГГ, чч: мм: сс, ± zz. Я планирую реализовать настоящие возможности NTP позднее. Обсуждение методов синхронизации времени лучше оставить в другом месте, например этот вопрос.

Примечания:

  • Мое встроенное устройство представляет собой сотовый модем Telit GC-864, который работает под управлением Python 1.5.2+ и имеет только ограниченное количество операторов (в основном только операторы C), без модулей и с некоторыми ожидаемыми встроенными типами Python. Точные возможности можно найти здесь, если вам интересно. Я пишу Python для этого устройства, как будто я пишу код C — не очень Pythonic, я знаю.
  • Я понимаю, что NTP лучше всего использовать только для смещения по времени, однако с ограниченными возможностями я использую NTP в качестве источника абсолютного времени (я мог бы добавить проверку для обновления NTP в 2036 году, чтобы обеспечить еще 136 лет работы).
  • Устройство GC-864-V2 с последней версией прошивки поддерживает NTP, но GC-864, который мне нужно использовать, застрял на предыдущей версии прошивки.

person swolpert    schedule 26.03.2013    source источник
comment
Почему вы беспокоитесь об обработке дат до 1582 года?   -  person Dietrich Epp    schedule 26.03.2013
comment
Ага, не нужно - было включено в исходный код, забыли включить исходный код C++ функции getDateFromJulianDay, строка 132 здесь.   -  person swolpert    schedule 26.03.2013
comment
Итак, удалите его из своего опубликованного кода и сделайте свой вопрос более ясным.   -  person dkamins    schedule 27.03.2013


Ответы (2)


Первоначально предложенная функция getDateFromJulianDay требует слишком больших вычислительных ресурсов для эффективного использования на встроенном устройстве, поскольку содержит множество операций умножения и деления больших long переменных или, как изначально было написано на C++, longlong переменных.

Я думаю, что нашел эффективный алгоритм для встроенного устройства эпохи современности.

После бесплодного гугления я снова оказался в Stack Overflow и нашел вопрос Преобразование эпохи время до «реальной» даты/времени, задавая вопрос о самостоятельно написанной реализации времени до даты и предоставляя подходящий алгоритм. Этот ответ на вопрос ссылается на исходный код gmtime.c и предоставил исходный код в CI, необходимый для написания алгоритма преобразования 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 реализован таким образом? помог подтвердить, что функция gmtime достаточно эффективна.

Используя сайт документации raspberryginger.com minix Doxygen, я смог найти C макросы и константы, которые были включены в gmtime.c из loc_time.h. Соответствующий фрагмент кода:

#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 был определен в 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 }
        };

Некоторые другие вещи, которые я нашел:

  • справочник по файлу gmtime.c очень помог найти зависимости.
  • Функция gmtime начинает индексацию месяца, дня недели и дня года с нулевого числа (максимальные диапазоны 0-11, 0-6, 0-365 соответственно), тогда как день месяца начинается с числа 1. , (1-31), см. IBM gmtime() ссылка.

Я переписал функцию 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):

  • Эпоха UNIX 1970 года изменена на эпоху NTP 1900 года (основная эпоха для НТП).
  • 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).
      • Единственная реальная разница в том, что divmod(sec_past_hour,60) избегает модуля dayclock (0-86399) на 60 через dayclock % 60, а вместо этого делает модуль sec_past_hour (0-3599) на 60 в пределах divmod(sec_past_hour,60).
  • Удалены переменные и ненужный мне код, например день недели.
  • Изменено индексирование месяца, чтобы оно начиналось с 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.
    • The requires long variables are:
      • ntp_time, seconds since 1900 (0-4294967295)
      • dayclock, секунды в день (0-86399)
    • Самая большая из остальных переменных — это рассчитанный год в пределах даты.

Функция Python ntp_time_to_date (с ее зависимостями) успешно работает на Telit GC-864 на встроенной версии Python 1.5.2+, а также на Python 2.7.3, но, конечно, используйте библиотеку datetime, если можете.

person swolpert    schedule 26.03.2013
comment
Для меня безумие, что Python должен быть языком, который вы используете, когда платформа настолько ограничена, что первая функция была слишком медленной. Я бы поставил деньги на то, что ничего подобного никогда не произойдет в реальном мире, но я думаю, что был бы неправ! - person cwa; 29.03.2013
comment
вас волнуют високосные секунды (ошибка составляет менее 40 секунд, она постоянна до 1972 года и видна, только если вы сравниваете время с реальными физическими часами (или временем GPS), которые в то время были синхронизированы со шкалой UTC)? eecis.udel.edu/~mills/leap.html - person jfs; 03.06.2013
comment
@sebastian: В данный момент меня не волнуют високосные секунды, но это, безусловно, полезно знать. Я не видел каких-либо смещений по сравнению со временем NTP-синхронизации отдельного ящика Linux (Ubuntu). Учитывает ли синхронизация времени Linux NTP дополнительные секунды? - person swolpert; 04.06.2013
comment
NTP предоставляет текущее время UTC, т. е. точно отвечает, который сейчас час? (не во время високосной секунды), но он не запоминает високосные секунды, поэтому каждая новая високосная секунда вносит 1-секундную ошибку в ответ: сколько секунд прошло?. - person jfs; 04.06.2013

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 для каждого испытания (каждая функция выводит «25.03.2013 18:40»). "). Синхронизация выполнялась с помощью отладки оператора 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 секунды

Делать что-либо связанное с вычислениями на Telit GC-864, работающем с файлами .pyo в Python 1.5.2+, — не лучшая идея. Использование GC-864-V2 со встроенной функцией NTP является возможным решением для тех, кто сталкивается с этой проблемой. Более того, более новые модемы сотовых телефонов для межмашинного взаимодействия (M2M), также известные как Интернет вещей (IoT), обладают гораздо большими возможностями.

Если у вас есть аналогичная проблема с GC-864, рассмотрите возможность использования более нового и современного модема сотового телефона.

person swolpert    schedule 13.04.2016