Frage

Is it possible to manually set the epoch date/time to the January 1, 0000, so I might use the std::chrono::time_point::time_since_epoch to calculate the difference between a given date and January 1, 0000?

I tried the following:

#include <iostream>
#include <chrono>
#include <ctime>

int main(int argc, char*argv[])
{
    std::tm epochStart = {};
    epochStart.tm_sec = 0;
    epochStart.tm_min = 0;
    epochStart.tm_hour = 0;
    epochStart.tm_mday = 0;
    epochStart.tm_mon = 0;
    epochStart.tm_year = -1900;
    epochStart.tm_wday = 0;
    epochStart.tm_yday = 0;
    epochStart.tm_isdst = -1;

    std::time_t base = std::mktime(&epochStart);

    std::chrono::system_clock::time_point baseTp=
        std::chrono::system_clock::from_time_t(base);
    std::time_t btp = std::chrono::system_clock::to_time_t(baseTp);
    std::cout << "time: " << std::ctime(&btp);

}

but this gives me

time: Thu Jan  1 00:59:59 1970
War es hilfreich?

Lösung

I would avoid std::time_t altogether. Using days_from_civil from chrono-Compatible Low-Level Date Algorithms, you can immediately compute any difference between std::chrono::system_clock::time_point, and any date in the proleptic Gregorian calendar1.

In addition to days_from_civil which takes a year/month/day triple and converts it into a count of days before/since 1970-01-01 (a chrono-compatible epoch), it is also convenient to create a custom chrono::duration to represent 24 hours:

typedef std::chrono::duration
        <
            int,
            std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
        > days;

Now you can create any epoch you want with just:

constexpr days epoch = days(days_from_civil(0, 1, 1));  // 0000-01-01

In C++1y this is even a compile-time computation!

And you can subtract this std::chrono::duration from any other std::chrono::duration:

auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;

delta is now a std::chrono::duration representing the amount of time between now, and 0000-01-01. You can then print that out however you want, or otherwise manipulate it. For example here is an entire working demo:

#include "../date_performance/date_algorithms"
#include <iostream>
#include <chrono>

typedef std::chrono::duration
        <
            int,
            std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>
        > days;

int
main()
{
    constexpr days epoch = days(days_from_civil(0, 1, 1));
    auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;
    days d = std::chrono::duration_cast<days>(delta);
    std::cout << "It has been " << d.count() << " days, ";
    delta -= d;
    auto h = std::chrono::duration_cast<std::chrono::hours>(delta);
    std::cout << h.count() << " hours, ";
    delta -= h;
    auto m = std::chrono::duration_cast<std::chrono::minutes>(delta);
    std::cout << m.count() << " minutes, ";
    delta -= m;
    auto s = std::chrono::duration_cast<std::chrono::seconds>(delta);
    std::cout << s.count() << " seconds ";
    std::cout << " since 0000-01-01\n";
}

Which for me output:

It has been 735602 days, 19 hours, 14 minutes, 32 seconds  since 0000-01-01

A word of warning about overflow:

The std::chrono::system_clock::time_point::duration is not guaranteed to have a range large enough to do this. It turns out that on my system it does. It is microseconds in a signed long long which will span +/- 292,000 years. If you need to avoid an overflow problem, you could truncate your std::chrono::system_clock::time_point::duration to courser units (e.g. seconds or days) to extend the range prior to subtracting 0000-01-01.

I got to thinking

And that usually leads to a disaster. However in this case I decided I should add to this post anyway. This:

constexpr days epoch = days(days_from_civil(0, 1, 1));

has type days, which is a duration. But it really isn't a duration. It is a point in time. It is a date. It is a time_point with a coarse precision. By introducing a new typedef, the code in this post can be cleaned up just a little bit more:

typedef std::chrono::time_point<std::chrono::system_clock, days> date_point;

Now instead of writing:

constexpr days epoch = days(days_from_civil(0, 1, 1));

One can write:

constexpr date_point epoch{days(days_from_civil(0, 1, 1))};

But even more importantly, instead of:

auto delta = std::chrono::system_clock::now().time_since_epoch() - epoch;

we can now write:

auto delta = std::chrono::system_clock::now() - epoch;

This delta still has exactly the same type and value as it did previously, and everything else in the demo still proceeds as exactly as it did before.

This is both a small change, and a big change. By treating epoch as a time_point instead of a duration, the algebra of time_point's and duration's works for us, both simplifying and type-checking our expressions to help us write cleaner code with fewer mistakes.

For example one can add two duration's together. But it doesn't make any sense at all to:

epoch + epoch

By using time_point instead of duration for the type of epoch, the compiler catches such non-sensical expressions at compile time.

1The proleptic Gregorian calendar has a year 0. In the year 0 it is 2 days behind the Julian calendar. Using a year 0 is also consistent with ISO 8601. As long as all parties involved know what calendar you are using, then everything is fine. Conversion between non-positive years and "BC years" is trivial if desired.

Andere Tipps

It's possible, the code you've given (minus a small fix, tm_mday starts with 1) yields:

Sat Jan  1 00:00:00 0

Live example

The real problem is: Are you on 32-bit or 64-bit? With a 32-bit system, time_t is also only 32 bits and you are limited to 1970 +/- 68 years.

On a 64-bit system, the limits are given by std::mktime and std::strftime, in my own code I have unit test for those strings and the corresponding values:

"-2147481748-01-01 00:00:00" maps to -67768040609740800
"2147483647-12-31 23:59:59" maps to 67767976233532799

I should probably also mention that there are systems where the above does not work because the underlying OS functions are buggy. For the record: I'm on Linux.

No. mktime and friends are based on UNIX time, which starts on 1st January 1970.

There is in fact no such thing as 0th January, 0000, so it seems likely that you would be better off finding another way to solve whatever is your actual problem.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top