time_t для ускорения преобразования даты, дающего неверный результат

У меня есть коллекция временных меток unix, которые я конвертирую в даты повышения (1.65.1), но преобразования, кажется, ломаются, когда они заходят слишком далеко в будущем. Все, что происходит вокруг 2040 года и далее, похоже, каким-то образом возвращается к периоду после 1900 года.

Учитывая следующий код...

        {
            std::time_t t = 1558220400;
            boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
            std::cout << "Date: " << date << std::endl;
        }

        {
            std::time_t t = 2145500000;
            boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
            std::cout << "Date: " << date << std::endl;
        }

        {
            std::time_t t = 2500000000;
            boost::gregorian::date date = boost::posix_time::from_time_t(t).date();
            std::cout << "Date: " << date << std::endl;
        }

... Я получаю следующий вывод...

    Date: 2019-May-18
    Date: 2037-Dec-27
    Date: 1913-Feb-13

... однако я ожидаю следующего вывода...

Expected output:
    Date: 2019-May-18
    Date: 2037-Dec-27
    Date: 2049-Mar-22

Есть ли что-то, что я делаю неправильно здесь?


person aatwo    schedule 20.05.2019    source источник
comment
Я думаю, вы обнаружите, что это 2038 год, который является пределом для 32-битных time_t..en.wikipedia.org /wiki/Year_2038_problem   -  person Ted Lyngmo    schedule 20.05.2019
comment
хммм, так как я нахожусь на платформе, где time_t 64-битный (длинный длинный), возможно, повышение падает по той же причине?   -  person aatwo    schedule 20.05.2019
comment
Это странно. Я использую boost 1.66.0, и он у меня работает, и я получаю ожидаемое 2049-Mar-22. Возможно, у них был баг.   -  person Ted Lyngmo    schedule 20.05.2019
comment
Обновите свою версию Boost, см. этот журнал изменений и строки Исправлены различные Проблемы 2038 года (32-разрядная версия) в библиотеке DateTime.   -  person rafix07    schedule 20.05.2019
comment
Странный. У меня такая же ошибка в Boost 1.69.0, и, глядя на источник, я вижу (как указал парень, который ответил на этот пост), что он по-прежнему приводит time_t к 32-битной длине.   -  person aatwo    schedule 20.05.2019
comment
@aatwo различные исправления могли быть связаны с другими различными ошибками. Рекомендую написать отчет об ошибке.   -  person eerorika    schedule 20.05.2019
comment
Спасибо, я сделаю это. Приведенный выше код не будет работать в Windows, но работает в Linux (или в любом другом месте, где long равен 64 бит), как выясняется, поскольку boost преобразует time_t в long (спасибо eeorika за указание на это)   -  person aatwo    schedule 20.05.2019
comment
Рассмотрите возможность использования библиотеки дат Говарда Хиннанта, которая не имеет этой проблемы. Достаточно просто включить "date/date.h", чтобы получить решение.   -  person Howard Hinnant    schedule 20.05.2019


Ответы (3)


Похоже, вы столкнулись с проблемой 2038 года.

Наибольшее число, которое может быть представлено 32-битным целым числом со знаком, равно 2 147 483 647. 2'147'483'647 секунд с 00:00:00 UTC 1 января 1970 года (эпоха UNIX) составляет 03:14:07 UTC 19 января 2038 года. Любое время UNIX после этого непредставимо с использованием 32-битного подписанного целое число.

Либо std::time_t в системе 32 бита, либо он преобразуется в 32 бита внутри библиотеки boost. Из источника видно, что преобразует ввод в long с помощью static_cast (и по-прежнему делает это в версии 1,70). long составляет 32 бита, например, в Windows, даже в 64-битных архитектурах. Во многих других системах, таких как 64-битный Linux, он 64-битный.

person eerorika    schedule 20.05.2019
comment
Спасибо за ответ. Я на платформе с 64-битным time_t, поэтому похоже, что сбой происходит в boost. - person aatwo; 20.05.2019
comment
Спасибо. Я никогда не думал смотреть на источник. Похоже, что даже в boost 1.69.0 переполнение все еще происходит из-за этого приведения. Чтобы обойти эту проблему, я больше не использую функцию boost::posix_time::from_time_t, и она отлично работает. - person aatwo; 20.05.2019
comment
чтобы уточнить, теперь я использую следующий код для преобразования time_t в boost::gregorian::date. если вы хотите обновить свой ответ, не стесняйтесь... auto time = boost::posix_time::ptime(boost::gregorian::date(1970,1,1)) + boost::posix_time::seconds(static_cast ‹длинный длинный›(t)); boost::gregorian::date date = time.date(); - person aatwo; 20.05.2019
comment
Спасибо. Для полноты картины я только что посмотрел исходный код boost 1.70.0 (последний на момент написания статьи), и проблема все еще присутствует. Однако проблема, по-видимому, существует в средстве отслеживания проблем с повышением (github.com/boostorg/date_time/issues /87). - person aatwo; 20.05.2019
comment
эээ, извините, я, должно быть, неправильно проверил это, потому что на самом деле это не решает проблему. У меня есть фактическое исправление, которое слишком длинно для комментария, поэтому я добавлю его в качестве дополнительного ответа. - person aatwo; 20.05.2019
comment
@aatwo Хорошо. Я удалил это из ответа. - person eerorika; 20.05.2019

В C++20 это теперь может выглядеть так:

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std::chrono;

    {
        std::time_t t = 1558220400;
        auto date = floor<days>(system_clock::from_time_t(t));
        std::cout << "Date: " << date << '\n';
    }

    {
        std::time_t t = 2145500000;
        auto date = floor<days>(system_clock::from_time_t(t));
        std::cout << "Date: " << date << '\n';
    }

    {
        std::time_t t = 2500000000;
        auto date = floor<days>(system_clock::from_time_t(t));
        std::cout << "Date: " << date << '\n';
    }
}

Выход:

Date: 2019-05-18
Date: 2037-12-27
Date: 2049-03-22

Если ваш time_t 32-битный, то вышеперечисленного недостаточно для решения проблемы. В этом случае вы должны полностью избегать C API. Это выглядит так:

{
    auto t = 1558220400;
    auto date = floor<days>(sys_seconds{seconds{t}});
    std::cout << "Date: " << date << '\n';
}

{
    auto t = 2145500000;
    auto date = floor<days>(sys_seconds{seconds{t}});
    std::cout << "Date: " << date << '\n';
}

{
    auto t = 2500000000;
    auto date = floor<days>(sys_seconds{seconds{t}});
    std::cout << "Date: " << date << '\n';
}

Если ваш поставщик еще не поставляет эту часть C++20, бесплатная предварительная версия с открытым исходным кодом, которая работает с C++11/14/17 доступен.1

Просто добавь:

#include "date/date.h"
...
using namespace date;

1 Полное раскрытие: я являюсь ведущим автором этой библиотеки. Я не преследую никакой финансовой выгоды от этих усилий. Но иногда люди раздражаются, если я не раскрываю эту информацию полностью.

person Howard Hinnant    schedule 09.06.2021

Как отмечает eeorika, это вызвано целочисленным переполнением, поскольку boost::posix_time::from_time_t преобразует 64-битное значение time_t в 32-битное (в Windows).

Если вы находитесь в затруднительном положении и оказались в том же положении, вы можете использовать следующую функцию для выполнения преобразования:

boost::gregorian::datetimet_to_date(time_t t)
{
    auto time = boost::posix_time::ptime(boost::gregorian::date(1970,1,1));

    int64_t current_t = t;
    long long_max = std::numeric_limits<long>::max();
    while(current_t > 0)
    {
        long seconds_to_add = 0;
        if(current_t >= long_max)
            seconds_to_add = long_max;
        else
            seconds_to_add = static_cast<long>(current_t);

        current_t -= seconds_to_add;
        time += boost::posix_time::seconds(seconds_to_add);
    }

    return time.date();
}

person aatwo    schedule 20.05.2019