6

I have a collection of unix timestamps I am converting to boost (1.65.1) dates but the conversions seem to break down when they get too far in the future. Anything around 2040 and beyond seems to be wrapping in some way back to post 1900.

Given the following code...

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

... I get the following output...

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

... however I am expecting the following output...

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

Is there something I am doing wrong here?

phuclv
  • 37,963
  • 15
  • 156
  • 475
aatwo
  • 948
  • 6
  • 12
  • 3
    I think you'll find that it's 2038 that is the limit for 32 bit `time_t`..https://en.wikipedia.org/wiki/Year_2038_problem – Ted Lyngmo May 20 '19 at 14:52
  • hmmm since I am on a platform where time_t is 64 bit (long long) perhaps boost is falling over for a similar reason? – aatwo May 20 '19 at 14:57
  • That's odd. I use boost 1.66.0 and it's working for me and I get the expected `2049-Mar-22`. Perhaps they had a bug. – Ted Lyngmo May 20 '19 at 15:01
  • 1
    Upgrade your Boost version, See this [change log](https://www.boost.org/users/history/version_1_67_0.html) and lines **Fixed various year 2038 (32-bit) issues** in DateTime library. – rafix07 May 20 '19 at 15:04
  • Weird. I have the same bug in Boost 1.69.0 and looking at the source I can see (as pointed out by the guy who answered this post) that it is still casting the time_t to a 32 bit long. – aatwo May 20 '19 at 15:14
  • @aatwo the various fixes might have been to other various bugs. I recommend writing a bug report. – eerorika May 20 '19 at 15:15
  • Thanks I'll do that. The code above wont work on Windows but works on Linux (or anywhere a long is 64 bit) as it turns out since boost is casting the time_t to a long (thanks to eerorika for pointing that out) – aatwo May 20 '19 at 15:23
  • Consider using [Howard Hinnant's date lib](https://github.com/HowardHinnant/date) which does not have this problem. Just including `"date/date.h"` is enough to get a solution. – Howard Hinnant May 20 '19 at 15:39

3 Answers3

3

It appears that you're experiencing the Year 2038 problem.

The largest number representable by 32 bit signed integer is 2'147'483'647. 2'147'483'647 seconds since 00:00:00 UTC on 1st of January 1970 (the UNIX epoch) is 03:14:07 UTC on 19th of January 2038. Any UNIX time after that is unrepresentable using a 32 bit signed integer.

Either std::time_t on the system is 32 bits, or it is converted into 32 bits inside the boost library. You can see from the source that boost converts the input into long using static_cast (and still does in version 1.70). long is 32 bits for example on windows, even on 64 bit architectures. It is 64 bits on many other systems such as 64 bit Linux.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • thanks for the response. I am on a platform with 64 bit time_t so it looks like the failure is occuring in boost. – aatwo May 20 '19 at 15:00
  • thanks. I never thought to look at the source. Looks like even in boost 1.69.0 the overflow is still occuring due to this cast. To circumvent the issue I am no longer using the boost::posix_time::from_time_t function and it works just fine. – aatwo May 20 '19 at 15:24
  • 1
    to clarify I am now using the following code to convert time_t to boost::gregorian::date. if you want to update your answer with this feel free... 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(); – aatwo May 20 '19 at 15:27
  • 1
    Thanks. For the sake of completeness I just looked at the boost 1.70.0 source (latest at the time of writing) and issue is still present. The issue does however appear to exists in the boost issue tracker (https://github.com/boostorg/date_time/issues/87). – aatwo May 20 '19 at 15:47
  • ergh, sorry I must have tested that incorrectly because it doesn't actually appear to fix the issue. I have an actual fix that is too long for a comment so I will add it as a supplimentary answer. – aatwo May 20 '19 at 16:17
  • @aatwo OK. I removed it from the answer. – eerorika May 20 '19 at 16:26
2

In C++20 this can now look like:

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

Output:

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

If your time_t is 32 bits, then the above isn't quite sufficient to fix the problem. In that case, you must avoid the C API completely. This looks like:

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

If your vendor isn't shipping this part of C++20 yet, a free, open-source preview that works with C++11/14/17 is available.1

Just add:

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

1 Full disclosure: I am the lead author of this library. I am not pursuing any financial gain from this effort. But sometimes people get grumpy if I don't fully disclose this information.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

As noted by eerorika this is caused by integer overflow since the boost::posix_time::from_time_t is casting the 64bit time_t value to a 32 bit long (on Windows).

If you are in a pinch and find yourself in the same position then you can use the following function to perform the conversion:

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

aatwo
  • 948
  • 6
  • 12