19

What is the correct way to persist std::chrono time_point instances and then read them back into another instance of the same type?

   typedef std::chrono::time_point<std::chrono::high_resolution_clock> time_point_t;

   time_point_t tp = std::chrono::high_resolution_clock::now();
   serializer.write(tp);
   .
   .
   .
   time_point_t another_tp;
   serializer.read(another_tp);

The calls to write/read, assume that the instance of type time_point_t, can be somehow converted to a byte representation, which can then be written to or read from a disk or a socket etc.

A possible solution suggested by Alf is as follows:

   std::chrono::high_resolution_clock::time_point t0 = std::chrono::high_resolution_clock::now();

   //Generate POD to write to disk
   unsigned long long ns0 = t0.time_since_epoch().count();

   //Read POD from disk and attempt to instantiate time_point
   std::chrono::high_resolution_clock::duration d(ns0)

   std::chrono::high_resolution_clock::time_point t1(d);

   unsigned long long ns1 = t1.time_since_epoch().count();

   if ((t0 != t1) || (ns0 != ns1))
   {
      std::cout << "Error time points don't match!\n";
   }

Note: The above code has a bug as the final instantiated time point does not match the original.

In the case of of the old style time_t, one typically just writes the entire entity to disk based on its sizeof and then reads it back the same way - In short what would be the equivalent for the new std::chrono types?

Zamfir Kerlukson
  • 1,046
  • 1
  • 10
  • 20

2 Answers2

19

Reading from a disk or socket implies that you might be reading in an instance of the application that did not do the write. And in this case, serializing the duration alone is not sufficient.

A time_point is a duration amount of time since an unspecified epoch. The epoch could be anything. On my computer the epoch of std::chrono::high_resolution_clock is whenever the computer booted. I.e. this clock reports the number of nanoseconds since boot.

If one application writes the time_since_epoch().count(), the computer is rebooted, and then another (or even the same) application reads it back in, the read in value has no meaning whatsoever, unless you happen to somehow know the amount of time between boots.

To reliably serialize a time_point one has to arrange for the writer and the reader to agree upon some epoch, and then ensure that the time_point written and read is with respect to that epoch. For example one might arrange to use the POSIX epoch: New Years 1970 UTC.

As it turns out, every std::chrono::system_clock implementation I'm aware of uses Unix time, a close approximation of UTC measured from New Years 1970. However I know of no common epoch for std::chrono::high_resolution_clock.

Only if you can somehow ensure that the reader and writer clocks agree upon a common epoch, can you serialize a time_point as a duration.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Would it be a good idea to serialize the Clock's epoch along with the `time_point`s duration? During deserialization, a check can be made if the clock of the `time_point` into which we are deserializing also has the same epoch and take corrective action if that's not the case? – Pavan Manjunath Jun 02 '22 at 13:06
  • The only thing a `time_point` knows about its epoch is that it has the value 0. That isn't enough information for a reader to determine when the epoch is. In C++20 one can read/write `system_clock`-based `time_point`s with a wide variety of formats that relate to the civil calendar using UTC. Ditto for other new clock types that are introduced in C++20. However persisting `steady_clock` and `high_resolution_clock` `time_point`s remains a bad idea because their epochs can change between runs, and there is no good way to record their epochs. These clocks have no relationship to a calendar. – Howard Hinnant Jun 02 '22 at 14:04
  • I have a use case where one process needs to send the beginning of events over the wire to another process within the same machine(and in the future, this process might live on another machine)Is there no optimal way or at least some workarounds to send out time_points over the wire? We are still on C++14. – Pavan Manjunath Jun 02 '22 at 19:21
  • Use `system_clock` at a precision of your choosing (e.g. microseconds). `time_point tp = system_clock::now();`. Then you can send/receive the integral type returned from `tp.time_since_epoch().count()`. – Howard Hinnant Jun 02 '22 at 20:03
7

the time_point constructor takes a duration, and you can get a duration from member time_since_epoch. thus the question reduces to serialize a duration value. and duration has a constructor that takes a number of ticks, and a member function count that produces the number of ticks.

all this just by googling std::chrono::time_point and looking at the cppreference documentation google landed me on.

it's often a good idea to read the documentation.


Addendum: an example.

#include <chrono>
#include <iostream>
#include <typeinfo>
using namespace std;

auto main() -> int
{
    using Clock = chrono::high_resolution_clock;
    using Time_point = Clock::time_point;
    using Duration = Clock::duration;

    Time_point const t0 = Clock::now();

    //Generate POD to write to disk
    Duration::rep const ns0 = t0.time_since_epoch().count();

    //Read POD from disk and attempt to instantiate time_point
    Duration const d(ns0);
    Time_point const t1(d);

    cout << "Basic number type is " << typeid( ns0 ).name() << "." << endl;
    if( t0 != t1 )
    {
        cout << "Error time points don't match!" << endl;
    }
    else
    {
        cout << "Reconstituted time is OK." << endl;
    }
}

With Visual C++ 12.0 the reported basic type is __int64, i.e. long long, while with g++ 4.8.2 in Windows the reported type is x, which presumably means the same.

With both compilers the reconstituted time is identical to the original.

Addendum: As noted by Dina in the comments, as of C++14 the C++ standard doesn't specify the epoch, and so to make this work across machines or with different clocks it's necessary to add additional steps that normalize the epoch for the serialized data, e.g. and most naturally to Posix time, i.e. time since since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970.

Community
  • 1
  • 1
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • @Alf so what your saying, one must go from clock to time_point to duration to pod to disk/storage then from disk/storage to pod to duration to time_point - is that correct? – Zamfir Kerlukson Mar 10 '14 at 03:36
  • yep. except "must" is probably too strong. but that's what I'd do. ;-) – Cheers and hth. - Alf Mar 10 '14 at 04:11
  • @Alf I've attempted a possible solution based on your approach as noted in the question, however it seems to return time_point types of differing values. – Zamfir Kerlukson Mar 10 '14 at 04:22
  • @ZamfirKerlukson: strange. your code (with the `duration_cast`) worked just fine for me. however, I suspect that maybe it's that cast that interferes: you don't need it, because `count` already produces a value of built-in type. – Cheers and hth. - Alf Mar 10 '14 at 06:34
  • 1
    To my understanding the epoch of each clock might be different and is not specified by the standard. Specifically, it doesn't have to be the same across different machines/system. This will work if you serialize and deserialize by the same machine, but it's not guaranteed to work between difference machines. (basing my comment on http://www.informit.com/articles/article.aspx?p=1881386&seqNum=2) – Dina Jun 19 '15 at 10:32
  • @Dina: Thanks! I wasn't aware of that. Somehow I assumed Posix/Unix time. – Cheers and hth. - Alf Jun 19 '15 at 10:42
  • Is there any guarantee in the standard that `high_resolution_clock::duration` is identical in all implementations? Once an epoch is selected, wouldn't you have to explicitly cast to a specific duration type to make this portable? – Jeff G Jan 26 '20 at 23:29
  • From the same article linked by @Dina, "*The high_resolution_clock represents a clock with the shortest tick period possible on the current system. Note that the standard does not provide requirements for the precision, the epoch, and the range...*". Therefore, both the epoch and duration need to be explicitly specified in order to serialize C++ clocks in a portable way. – Jeff G Jan 27 '20 at 15:03