jwm
jwm

Reputation: 1844

Constructing std::chrono::system_clock::time_point from a duration

I have a data source that provides time since the start of the day (00:00:00Z). I want to construct a std::chrono::time_point based in this input.

For the sake of example, the current time is 2020-02-27T01:05:30.687073184. My source provides me a binary-coded decimal value, to 0.01 seconds. I construct intermeditate values to separate the parsing/mediation from the combination.

        using Days = std::chrono::duration<int, std::ratio<86400>>;

        auto hr10  = std::chrono::duration<unsigned, std::ratio<36000>> ( 0 );
        auto hr1   = std::chrono::duration<unsigned, std::ratio<3600>>  ( 1 );
        auto mn10  = std::chrono::duration<unsigned, std::ratio<600>>   ( 0 );
        auto mn1   = std::chrono::duration<unsigned, std::ratio<60>>    ( 5 );
        auto sc10  = std::chrono::duration<unsigned, std::ratio<10>>    ( 3 );
        auto sc1   = std::chrono::duration<unsigned, std::ratio<1>>     ( 0 );
        auto ms100 = std::chrono::duration<unsigned, std::ratio<1, 10>> ( 6 );
        auto ms10  = std::chrono::duration<unsigned, std::ratio<1, 100>>( 8 );

        auto t = hr10 + hr1 + mn10 + mn1 + sc10 + sc1 + ms100 + ms10;  // 393068
        auto now = std::chrono::system_clock::now();
        auto sinceEpoch = now.time_since_epoch();      // 1582765530687073184 ns

        auto today = std::chrono::duration_cast<Days>(sinceEpoch);    // 18319 days
        auto sinceDay = sinceEpoch - today;            // 3930687073184 ns

// There is logic to determine if there is a day roll-over; it is not relevant here
        auto adjust = Days(0);

// Create the time_point
        auto tp = std::chrono::system_clock::time_point();
        std::cout << "\n\tnull:       " << tp.time_since_epoch().count();

        tp += today;
        std::cout << "\n\ttoday:      " << tp.time_since_epoch().count();

        tp += adjust;
        std::cout << "\n\tadjust:     " << tp.time_since_epoch().count();

        tp += t;
        std::cout << "\n\tt:          " << tp.time_since_epoch().count();

        std::cout << "\n\tall-in-one: " << decltype(tp)(today+adjust+t).time_since_epoch().count();

This results in the following output:

        null:       0
        today:      1582761600000000000
        adjust:     1582761600000000000
        t:          1582765530680000000
        all-in-one: 36577304120000000

What I don't understand is why incrementally adding each duration to the time_point produces the desired effect, but trying to construct the time_point from an aggregate duration does not.

Upvotes: 0

Views: 710

Answers (2)

jwm
jwm

Reputation: 1844

When declaring your own duration types, use 64-bit values

The problem is overflowing the duration when converting from Days. Changing the custom duration types to use a 64-bit integer eliminates the problem:

        auto hr10  = std::chrono::duration<uint64_t, std::ratio<36000>> ( std::get<0>(bcd) );
        auto hr1   = std::chrono::duration<uint64_t, std::ratio<3600>>  ( std::get<1>(bcd) );
        auto mn10  = std::chrono::duration<uint64_t, std::ratio<600>>   ( std::get<2>(bcd) );
        auto mn1   = std::chrono::duration<uint64_t, std::ratio<60>>    ( std::get<3>(bcd) );
        auto sc10  = std::chrono::duration<uint64_t, std::ratio<10>>    ( std::get<4>(bcd) );
        auto sc1   = std::chrono::duration<uint64_t, std::ratio<1>>     ( std::get<5>(bcd) );
        auto ms100 = std::chrono::duration<uint64_t, std::ratio<1, 10>> ( std::get<6>(bcd) );
        auto ms10  = std::chrono::duration<uint64_t, std::ratio<1, 100>>( std::get<7>(bcd) );

Or, if that's undesirable, cast t to a larger type before adding in today:

        auto tp = std::chrono::system_clock::time_point(
                      std::chrono::duration_cast::microseconds(t) + today + adjust
                  );

Remember, the number of nanoseconds since 1970 is a big number, and will certainly overflow a 32-bit quantity.

Upvotes: 1

walnut
walnut

Reputation: 22152

You are overflowing the tick counter by adding the duration objects directly.

You gave the durations tick counter types of int and unsigned. The common type of these is unsigned. Therefore all the math is done in unsigned for today+adjust+t.

Assuming 32bit unsigned, the typical maximum value is 2^32-1 = 4294967295. The value you expect to get is 1582761600000000000, which is clearly larger.

Just use unsigned long long (or a sufficiently large fixed-size integer type) as duration tick counter type.

The type used for system_clock::duration (and thus system_clock::time_point) has probably higher rank than unsigned and seems to be capable of storing the full number.

Upvotes: 1

Related Questions