Подтвердить что ты не робот

Математика для преобразования секунд с 1970 года в дату и наоборот

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Алгоритмы даты имеют дело только с единицами дней, а не с наносекундами. Чтобы преобразовать дни в наносекунды, умножьте на 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.

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

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

#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

Алгоритмы находятся в свободном доступе. Используйте их как хотите. В документе "Алгоритмы дат" есть несколько более полезных алгоритмов дат, если это необходимо (например, weekday_difference удивительно прост и удивительно полезен).

Эти алгоритмы, если необходимо, заключены в кроссплатформенную библиотеку дат с открытым исходным кодом с открытым исходным кодом.

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

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

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

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

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

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

Почти каждый источник времени из ОС основан на 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). До этого лучше всего были кварцевые часы, которые были недостаточно точными, чтобы беспокоиться о високосных секундах.

Ответ 2

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

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

Если год равен < 1970 или значение отрицательное, это соотношение равно undefined. Если год равен >= 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, чтобы использовать эту формулу, и это тоже должно быть сделано с учетом високосных лет. Остальное в формуле тривиально.

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

ИЗМЕНИТЬ

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

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

Ответ 3

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

Передайте секунды как первый параметр. Второй параметр должен быть истинным для локального времени, false для 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: флаг, указывающий, действует ли летнее время                в описанное время. Значение является положительным, если переход на летнее время                время действует, ноль, если это не так, и отрицательное, если                информация недоступна.

Ответ 4

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

Ответ 6

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

Использование: 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;
}

Ответ 7

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

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

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

Ответ 8

перед

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

СПУСТЯ:

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

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

Ответ 9

Мне нужно было реализовать преобразование во время Unix на младшем 8-битном MCU без множителя HW. Ниже приведен код С#, который требует только общего 8-битного умножения и деления на постоянные значения 4 и 100. Оба для 32-битного (длинного) операнда. Код С# может быть легко перенесен в финальную структуру. Он дает тот же результат, что и DateTimeOffset.ToUnixTimeSeconds() из .NET.

static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
  // Cumulative days for each previous month of the year
  int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
  // Year is to be relative to the epoch start
  year -= 1970;
  // Compensation of the non-leap years
  int minusYear = 0;
  // Detect potential lead day (February 29th) in this year?
  if ( month >= 3 )
  {
    // Then add this year into "sum of leap days" computation
    year++;
    // Compute one year less in the non-leap years sum
    minusYear = 1;
  }

  return 
    // + Seconds from computed minutes
    60 * (
      // + Minutes from computed hours
      60 * (
        // + Hours from computed days
        24 * (
          // + Day (zero index)
          day - 1
          // + days in previous months (leap day not included)
          + mdays[month - 1]
          // + days for each year divisible by 4 (starting from 1973)
          + ( ( year + 1 ) / 4 )
          // - days for each year divisible by 100 (starting from 2001)
          - ( ( year + 69 ) / 100 )
          // + days for each year divisible by 400 (starting from 2001)
          + ( ( year + 369 ) / 100 / 4 )
          // + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
          + ( 5 * 73 /*=365*/ ) * ( year - minusYear )
          // + Hours
        ) + hour
        // + Minutes
      ) + min 
      // + Seconds
    ) + sec;
}

Надеюсь, это поможет.

Отредактировано:

Ниже приведен оптимизированный код для 8-разрядного микроконтроллера PIC и компилятора CC5X.

uns32 unixTime;

...
  // Test data returning 0xFfFfFfFf UnixTime
  uns8 year = 2106 - 1970;
  uns8 month = 2;
  uns8 day = 7;
  uns8 hour = 6;
  uns8 min = 28;
  uns8 sec = 15;

  // See original C# code below

  //### Compute days
  // ( 5 * 73 /*=365*/ ) * year
  unixTime = year;
  mulUnixTime( 5 );
  mulUnixTime( 73 );

  // if ( month >= 3 ) year++;
  if ( month > 3 )
    year++;

  // if ( year > 130 ) => minus 1 total days ( year-=4 makes a result of the next division by 4 less by 1)
  if ( year > 130 )
    year -= 4;
  // + ( ( year + 1 ) / 4 )
  addUnixTime( ( year + 1 ) / 4 );
  // + mdays[month - 1]
  addUnixTime( daysInMonths( month ) );
  // + day - 1
  addUnixTime( day - 1 );
  //### Compute hours
  // Hours from computed days
  mulUnixTime( 24 );
  // + Hours
  addUnixTime( hour );
  //### Compute minutes
  // Minutes from computed hours 
  mulUnixTime( 60 );
  // + Minutes
  addUnixTime( min );
  //### Compute seconds
  // Seconds from computed minutes
  mulUnixTime( 60 );
  // + Seconds
  addUnixTime( sec );
...

void mulUnixTime( uns8 mul )
{
  unixTime *= mul;
}

void addUnixTime( uns8 add )
{
  unixTime += add;
}

uns8 daysInMonths( uns8 month @ W )
{
  skip( month );
#pragma computedGoto 1
  return 0xFF;// Dummy value for month 0
  return   0; // January
  return  31; // February
  return  59; // ...
  return  90;
  return 120;
  return 151;
  return 181;
  return 212;
  return 243;
  return 273;
  return 304; // ...
  return 334; // December
#pragma computedGoto 0
}


/*
 static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
  {
    // Cumulative days for each previous month of the year
    int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
    // Year is to be relative to the epoch start
    year -= 1970;
    // Compensation of the non-leap years
    int minusYear = 0;
    // Detect potential lead day (February 29th) in this year?
    if ( month >= 3 )
    {
      // Then add this year into "sum of leap days" computation
      year++;
      // Compute one year less in the non-leap years sum
      minusYear = 1;
    }

    return
      // + Seconds from computed minutes
      60 * (
        // + Minutes from computed hours
        60 * (
          // + Hours from computed days
          24L * (
            // + Day (zero index)
            day - 1
            // + days in previous months (leap day not included)
            + mdays[month - 1]
            // + days for each year divisible by 4 (starting from 1973)
            + ( ( year + 1 ) / 4 )
            // - days after year 2000
            - ( ( year > 130 ) ? 1 : 0 )
            // + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
            + ( 5 * 73 ) * ( year - minusYear )
          // + Hours
          ) + hour
        // + Minutes
        ) + min
      // + Seconds
      ) + sec;
  }
*/