Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a custom clock for use in std::chrono functions?

Tags:

c++

c++11

chrono

I have some arbitrary epoch, like July 13, 1988. Essentially I want to measure the time relative to this. I was thinking of writing a custom clock class, so that I could write code like this:

using std::chrono;
time_point<My_Clock> tp;
std::cout << duration_cast<seconds>(tp.time_since_epoch()).count() << std::endl;

Is this possible? If not, what's the cleanest way to accomplish this?

like image 578
sagargp Avatar asked May 14 '13 00:05

sagargp


1 Answers

The hard part of writing this custom clock is figuring out how to write its now() function. In the example below I base the now() off of system_clock's now(). First I do some detective work to discover that my system_clock has an epoch of New Years 1970, neglecting leap seconds. This is known as unix time. As it turns out, every implementation I'm aware of (and I think I've checked them all) have this very same epoch (but this is unspecified by the C++11 standard).

Next I compute that 1988-07-13 is 6768 days after 1970-01-01. Using these two facts, the rest is easy:

#include <chrono>

struct My_Clock
{
    typedef std::chrono::seconds              duration;
    typedef duration::rep                     rep;
    typedef duration::period                  period;
    typedef std::chrono::time_point<My_Clock> time_point;
    static const bool is_steady =             false;

    static time_point now() noexcept
    {
        using namespace std::chrono;
        return time_point
          (
            duration_cast<duration>(system_clock::now().time_since_epoch()) -
            hours(6768*24)
          );
    }
};

MyClock needs nested typedefs to describe its duration, rep, period, and time_point. Based on your question, I've chosen seconds as the duration, but you can choose anything you want.

For the now() function I just call the system_clock::now() and subtract off the epoch in units of seconds. I got just a little clever with this computation by writing everything in terms of MyClock::duration so that I can more easily change duration. Note that I was able to subtract off the epoch in terms of hours, which implicitly converts to duration (which is seconds). Alternatively I could have built myself a custom duration of days:

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

And then the return of now() could have been written:

        return time_point
          (
            duration_cast<duration>(system_clock::now().time_since_epoch()) -
            days(6768)
          );

At any rate, now you can use this like:

#include <iostream>

int
main()
{
    using namespace std::chrono;
    time_point<My_Clock> tp = My_Clock::now();
    std::cout << tp.time_since_epoch().count() << '\n';
}

Which for me just printed out:

786664963

Which demonstrates that today (2013-06-16) is approximately 24.9 years after 1988-07-13.

Update for C++20

In C++20 you can use the updated <chrono> calendrical services to eliminate the "magic number" from the implementation:

#include <chrono>

struct My_Clock
{
    typedef std::chrono::seconds              duration;
    typedef duration::rep                     rep;
    typedef duration::period                  period;
    typedef std::chrono::time_point<My_Clock> time_point;
    static const bool is_steady =             false;

    static time_point now() noexcept
    {
        using namespace std::chrono;
        return time_point
          {
            duration_cast<duration>(system_clock::now() - sys_days{July/13/1988})
          };
    }
};

There is no run-time expense for this convenience. The sub-expression sys_days{July/13/1988} is optimized at compile-time into a count of days since the system_clock epoch, and then further converted to system_clock::duration (still at compile-time) before being subtracted from system_clock::now().

like image 170
Howard Hinnant Avatar answered Oct 27 '22 00:10

Howard Hinnant