Математика для перевода секунд с 1970 в дату и наоборот - PullRequest
26 голосов
/ 01 ноября 2011

У меня есть секунды с 1 января 1970 г. 00:00 как int64 в наносекундах, и я пытаюсь преобразовать их в месяц / день / год / день недели.

Это легко сделать итеративно,У меня это работает, но я хочу сделать это формально.Я ищу фактическую математику.

Ответы [ 8 ]

28 голосов
/ 22 августа 2015

Новый ответ на старый вопрос:

Обоснование этого нового ответа: Существующие ответы либо не показывают алгоритмы для преобразования из наносекунд в год / месяц / день (например, они используют библиотеки со скрытым источником), или они используют итерацию в алгоритмах, которые они показывают.

Этот ответ не имеет итерации вообще.

Алгоритмы здесь и объяснены в мучительных деталях.Они также тестируются на корректность в течение +/- миллиона лет (намного больше, чем нужно).

Алгоритмы не учитывают високосные секунды.Если вам это нужно, это может быть сделано, но требуется поиск в таблице, и эта таблица растет со временем.

Алгоритмы дат работают только с единицами дней, а не с наносекундами.Чтобы преобразовать дни в наносекунды, умножьте на 86400*1000000000 (следя за тем, чтобы использовать 64-битную арифметику).Чтобы преобразовать наносекунды в дни, разделите на ту же сумму.Или, что еще лучше, используйте библиотеку C ++ 11 <chrono>.

Есть три алгоритма даты из этой статьи, которые необходимы для ответа на этот вопрос.

1. days_from_civil:

// Returns number of days since civil 1970-01-01.  Negative values indicate
//    days prior to 1970-01-01.
// Preconditions:  y-m-d represents a date in the civil (Gregorian) calendar
//                 m is in [1, 12]
//                 d is in [1, last_day_of_month(y, m)]
//                 y is "approximately" in
//                   [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
//                 Exact range of validity is:
//                 [civil_from_days(numeric_limits<Int>::min()),
//                  civil_from_days(numeric_limits<Int>::max()-719468)]
template <class Int>
constexpr
Int
days_from_civil(Int y, unsigned m, unsigned d) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    y -= m <= 2;
    const Int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return era * 146097 + static_cast<Int>(doe) - 719468;
}

2. civil_from_days:

// Returns year/month/day triple in civil calendar
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468].
template <class Int>
constexpr
std::tuple<Int, unsigned, unsigned>
civil_from_days(Int z) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468;
    const Int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    const Int y = static_cast<Int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}

3. weekday_from_days:

// Returns day of week in civil calendar [0, 6] -> [Sun, Sat]
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-4].
template <class Int>
constexpr
unsigned
weekday_from_days(Int z) noexcept
{
    return static_cast<unsigned>(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6);
}

Эти алгоритмы написаны для C ++ 14,Если у вас есть C ++ 11, удалите constexpr.Если у вас есть C ++ 98/03, удалите constexpr, noexcept и static_assert s.

Обратите внимание на отсутствие итерации в любом из этих трех алгоритмов.

Их можно использовать так:

#include <iostream>

int
main()
{
    int64_t z = days_from_civil(2015LL, 8, 22);
    int64_t ns = z*86400*1000000000;
    std::cout << ns << '\n';
    const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    unsigned wd = weekday_from_days(z);
    int64_t y;
    unsigned m, d;
    std::tie(y, m, d) = civil_from_days(ns/86400/1000000000);
    std::cout << y << '-' << m << '-' << d << ' ' << weekdays[wd] << '\n';
}

, который выводит:

1440201600000000000
2015-8-22 Sat

Алгоритмы находятся в свободном доступе.Используйте их как хотите.* * * * * * * * * * * * * * * * * * * * * * * * * * [10] * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * [кроссплатформенная, безопасная библиотека типов , если необходимо.

Если требуется поддержка часового пояса или високосной секунды, то существует библиотека часовых поясов , построенная поверх даты библиотека .

Обновление: различные локальные зоны в одном приложении

Узнайте, как преобразовать различные часовые пояса .

Обновление: Есть ли какие-либо ошибки в игнорировании високосных секунд при выполнении вычислений даты таким способом?

Это хороший вопрос из комментариев ниже.

Ответ: Есть некоторые подводные камни.И есть некоторые преимущества.Хорошо знать, что они оба.

Почти каждый источник времени ОС основан на Unix Time . Unix Time - это отсчет времени с 1970-01-01 без учета високосных секунд.Это включает в себя такие функции, как C time(nullptr) и C ++ std::chrono::system_clock::now(), а также POSIX gettimeofday и clock_gettime.Это не факт, указанный в стандарте (за исключением того, что определено в POSIX), но это де-факто стандарт.

Так что, если ваш источник секунд (наносекунды, что угодно) пренебрегает високосными секундами, это точноисправлено, чтобы игнорировать дополнительные секунды при преобразовании в типы полей, такие как {year, month, day, hours, minutes, seconds, nanoseconds}.Фактически, чтобы учитывать високосные секунды в таком контексте, на самом деле вводит ошибок.

Так что хорошо знать ваш источник времени и особенно знать, если он также пренебрегает високосными секундами.как Unix Time делает.

Если ваш источник времени не пренебрегает високосными секундами, вы можете все же получить правильный ответ довторой.Вам просто нужно знать набор високосных секунд, которые были вставлены. Вот текущий список .

Например, если вы получили количество секунд с 1970-01-01 00:00:00 UTC, которое включает високосных секунд ивы знаете, что это представляет «сейчас» (который в настоящее время 2016-09-26), текущее количество високосных секунд, вставленных между текущим и 1970-01-01, равно 26. Таким образом, вы можете вычесть 26 из своего количества и затем следуйте этим алгоритмам, получая точный результат.

Эта библиотека может автоматизировать вычисления с учетом високосных секунд.Например, чтобы получить количество секунд между 2016-09-26 00:00:00 UTC и 1970-01-01 00:00:00 UTC , включая високосных секунд, вы можете сделать это:

#include "date/tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    auto now  = clock_cast<utc_clock>(sys_days{2016_y/September/26});
    auto then = clock_cast<utc_clock>(sys_days{1970_y/January/1});
    std::cout << now - then << '\n';
}

, который выдает:

1474848026s

Пренебрежение високосными секундами ( Unix Time ) выглядит так:

#include "date/date.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    auto now  = sys_days{2016_y/September/26} + 0s;
    auto then = sys_days{1970_y/January/1};
    std::cout << now - then << '\n';
}

, который выдает:

1474848000s

Для разницы 26s.

В предстоящие новогодние праздники (2017-01-01) мы добавим 27 th високосную секунду.

Между 1958-01-01 и 1970-01-01 было вставлено 10 «високосных секунд», но в единицах, меньших секунды, а не только в конце декабря или июня. Документация о том, как именнобыло затрачено много времени, и именно тогда, когда оно было отрывочным, и я не смог отыскать надежный источник.

Службы атомного отсчета времени начали экспериментально в 1955 году, и первый основанный на атомах международный стандарт времени TAI имеетЭпоха 1958-01-01 00:00:00 по Гринвичу (что сейчас UTC).До этого лучшими были кварцевые часы, которые не были достаточно точными, чтобы беспокоиться о високосных секундах.

10 голосов
/ 05 ноября 2011

Спецификация Single Unix дает формулу для секунд с начала эпохи :

Значение, которое приблизительно соответствует количеству прошедших секунд. с эпохи. Название всемирного координированного времени (указывается в терминах секунд (tm_sec), минут (tm_min), часов (tm_hour), дней с 1 января года (tm_yday) и календарный год минус 1900 (tm_year)) относится ко времени, представленному в секундах с Эпоха, согласно выражению ниже.

Если год <1970 или значение отрицательное, соотношение не определено. Если год> = 1970 и значение неотрицательное, значение связано с именем всемирного координированного времени в соответствии с Выражение на языке C, где tm_sec, tm_min, tm_hour, tm_yday и tm_year - все целочисленные типы:

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400

Соотношение между фактическим временем дня и текущим значением. в течение нескольких секунд, так как эпоха не определена.

Как любые изменения в значении секунд с начала эпохи настроить на желаемое отношение с текущим фактическим временем реализации. Как представлено в секундах с эпохи, каждый день должен составлять ровно 86400 секунд.

Примечание: Последние три условия выражения добавляют в день для каждого года, следующего за високосным, начиная с первого високосного года с Эпоха. Первый член добавляет день каждые 4 года, начиная с 1973 года, второй вычитает день обратно каждые 100 лет, начиная с 2001 года, и третий добавляет день назад каждые 400 лет, начиная с 2001 года. деления в формуле являются целочисленными делениями; то есть остаток отбрасывается, оставляя только целое число.

Вам нужно будет преобразовать месяц и день месяца в tm_yday, чтобы использовать эту формулу, и это также должно быть сделано с учетом високосных лет. Остальное в формуле тривиально.

Попробуйте из этого выяснить, как вернуть дату и время из секунд.

EDIT

Я реализовал конвертер в целочисленной арифметике в этот ответ .

См. Тестовый прогон на ideone .

2 голосов
/ 01 ноября 2011

Зависит от того, в какое время вы хотите gmtime или localtime , затем просто прочитайте struct_tm

2 голосов
/ 01 ноября 2011
bool FloatToTime(float seconds_since_epoch, bool local_time, struct tm *timest)
{
   struct tm *ret;
   time_t t=(time_t) seconds_since_epoch;
   if (local_time) ret=localtime(&t);
      else ret=gmtime(&t);
   if(ret==NULL) return false;
   memcpy(timest, ret, sizeof(struct tm));
   return true;
}

Передайте ему секунды в качестве первого параметра.Второй параметр должен быть истинным для местного времени, ложным для GMT.Третий параметр - это указатель на структуру для хранения ответа.

Возвращаемые структуры (со страницы руководства):

tm_sec: количество секунд после минуты,обычно в диапазоне от 0 до 59, но может быть до 60 с учетом високосных секунд.

tm_min: количество минут после часа, в диапазоне от 0 до 59.

tm_hour: Количество часов после полуночи в диапазоне от 0 до 23.

tm_mday: день месяца в диапазоне от 1 до 31.

tm_mon: количество месяцев с январяв диапазоне от 0 до 11.

tm_year: количество лет с 1900 года.

tm_wday: количество дней с воскресенья в диапазоне от 0 до 6.

tm_yday: число дней с 1 января в диапазоне от 0 до 365.

tm_isdst: флаг, указывающий, действует ли переход на летнее время в указанное время.Значение является положительным, если действует летнее время, нулевым, если оно отсутствует, и отрицательным, если информация недоступна.

1 голос
/ 13 июня 2014

Этот код работает ...

Использование: uint32_t getSecsSinceEpoch (1970, месяц, день, years_since_epoch, час, минута, секунда);

Пример: timestamp = getSecsSinceEpoch (1970, 6, 12, (2014 - 1970), 15, 29, 0)

Возвраты: 1402586940

Вы можете проверить на www.epochconverter.com.

Потребовалось около 20 минутчтобы написать это, и большая часть этого была потрачена, споря с другом относительно того, должен ли я включать високосные секунды, нано секунды, и т.д. Blech.

Веселитесь ...

Доктор.Брайан Уилкатт

#define DAYSPERWEEK (7)
#define DAYSPERNORMYEAR (365U)
#define DAYSPERLEAPYEAR (366U)

#define SECSPERDAY (86400UL) /* == ( 24 * 60 * 60) */
#define SECSPERHOUR (3600UL) /* == ( 60 * 60) */
#define SECSPERMIN (60UL) /* == ( 60) */

#define LEAPYEAR(year)          (!((year) % 4) && (((year) % 100) || !((year) % 400)))

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

/****************************************************
* Class:Function    : getSecsSomceEpoch
* Input     : uint16_t epoch date (ie, 1970)
* Input     : uint8 ptr to returned month
* Input     : uint8 ptr to returned day
* Input     : uint8 ptr to returned years since Epoch
* Input     : uint8 ptr to returned hour
* Input     : uint8 ptr to returned minute
* Input     : uint8 ptr to returned seconds
* Output        : uint32_t Seconds between Epoch year and timestamp
* Behavior      :
*
* Converts MM/DD/YY HH:MM:SS to actual seconds since epoch.
* Epoch year is assumed at Jan 1, 00:00:01am.
****************************************************/
uint32_t getSecsSinceEpoch(uint16_t epoch, uint8_t month, uint8_t day, uint8_t years, uint8_t hour, uint8_t minute, uint8_t second)
{
unsigned long secs = 0;
int countleap = 0;
int i;
int dayspermonth;

secs = years * (SECSPERDAY * 365);
for (i = 0; i < (years - 1); i++)
{   
    if (LEAPYEAR((epoch + i)))
      countleap++;
}
secs += (countleap * SECSPERDAY);

secs += second;
secs += (hour * SECSPERHOUR);
secs += (minute * SECSPERMIN);
secs += ((day - 1) * SECSPERDAY);

if (month > 1)
{
    dayspermonth = 0;

    if (LEAPYEAR((epoch + years))) // Only counts when we're on leap day or past it
    {
        if (month > 2)
        {
            dayspermonth = 1;
        } else if (month == 2 && day >= 29) {
            dayspermonth = 1;
        }
    }

    for (i = 0; i < month - 1; i++)
    {   
        secs += (_ytab[dayspermonth][i] * SECSPERDAY);
    }
}

return secs;
}
1 голос
/ 01 ноября 2011

Для этого существует множество функций, см. http://www.cplusplus.com/reference/clibrary/ctime/,, а именно strftime.

0 голосов
/ 30 марта 2017

до

    for (i = 0; i < (years - 1); i++)
    {   
        if (LEAPYEAR((epoch + i)))
        countleap++;
    }

СПУСТЯ:

    for (i = 0; i < years; i++)
 {   
   if (LEAPYEAR((epoch + i)))
    countleap++;
 }

После исправления у меня сработал код.

0 голосов
/ 02 ноября 2011

Прежде всего, не храните свои секунды в виде числа с плавающей запятой. Если вам нужны микро / наносекунды, храните их отдельно. Вам понадобятся целые числа, чтобы сделать эти вычисления.

Это зависит от вашего часового пояса (правила перехода на летнее время, високосные годы, високосные секунды), но я бы сказал, сначала получим число дней целочисленным делением на 86400. Затем выясним, что осталось, по модулю деления на 86400. Теперь Вы можете выяснить, сколько лет прошло с помощью первого целого числа, разделив количество дней на 365, а затем вычтя количество високосных дней из оставшихся дней (рассчитывается по модулю деления количества дней на 365). Вы также захотите вычесть количество високосных секунд из количества оставшихся секунд (уже рассчитано). Если это вычитание приводит эти числа ниже нуля, то вычтите из следующего наибольшего номинала. Затем вы можете рассчитать день месяца, используя явную логику для вашего календаря. Обязательно добавьте час (или любое другое смещение DST), если вы приземлились в DST.

Лично я бы просто использовал Boost.Date_Time , поскольку он делает все это и больше (вероятно, с меньшим количеством ошибок, чем вы или я бы сделал в первые несколько итераций), но я решил, что сделайте снимок по вашему вопросу ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...