C++ `<chrono>` for Dummies

Well, not for dummies, but easier to understand!

C++ date and time can be a bit hard to wrap your head around as it’s not an intuitive flow and takes some time to understand. Making it simple for you (future me) here!

Time components are part of STL and are contained in the <chrono> header (#include <chrono>). They use std::chrono namespace as well.

There are two main concepts in chrono - time_point and duration. As name suggests, time_point is a point in time. In other languages this would be something like datetime (Python) or DateTime etc. duration is a difference, or time between two time points. This can be called something like timedelta or TimeSpan in other languages. These are the two most important terms you need to understand, everything else flows from those.

Then, there are clocks. Unlike other languages, C++ has more than one clocking system. Although in reality you will most probably just use one clock, it’s important to understand this terminology.

Both time_point and duration are in one way or another are obtained from a clock.

The need for different clock types in C++ originated from the stringent needs of high-energy physics.

Clock Type Description Since C++
system_clock System-wide real time wall clock. 11
steady_clock Monotonic clock, time cannot decrease as physical time moves forward and the time between ticks of this clock is constant. Most suitable for measuring interval. This is the most popular clock. 11
high_resolution_clock High resolution clock with the smallest possible tick period (nanosecond). In MSVC it’s a synonym for steady_clock. 11
file_clock Clock used for time on filesystem. 20
utc_clock Represents UTC time. 20
tai_clock Represents Atomic Time (TAI). 20
gps_clock Represents GPS time. 20
local_t Pseudo-clock representing local time. 20

Clock

All the clocks have now method (and this is pretty much the only method they have) that return the current point in time:

auto now_st = steady_clock::now();
auto now_hr = high_resolution_clock::now();
auto now_sy = system_clock::now();

And of course, they return time_point. However, because both time_point and duration are clock specific, the steady_clock for instance will return time_point<steady_clock> and so on (hence auto to shorten the syntax).

In order to display the time in the terminal, you can resort to C functions. I.e.

time_t tt = system_clock::to_time_t(now_sy);
cout << ctime(&tt) << endl;

prints

Fri Dec 16 10:08:31 2022

Duration

Internally duration is a template:

template <class _Rep, class _Period>
    class duration { // represents a time duration
    public:
        using rep    = _Rep;
        using period = typename _Period::type;
// ...

Basically, duration type needs:

  • _Rep is the numeric type than holds duration value, like double, int etc.

  • _Period holds the number of clock ticks of the period.

Needless to say, duration is clock specific (or time_point specific).

Examples:

duration<long long,milli> d1 {7}; // 7 milliseconds
duration<double ,pico> d2 {3.33}; // 3.33 picoseconds
duration<int,ratio<1,1>> d3 {}; // 0 seconds

Milli and pico are defined in <ratio> as:

using milli = ratio<1, 1000>;
using pico  = ratio<1, 1000000000000LL>;

And ratio is a template that holds the ratio of _Nx to _Dx:

template <intmax_t _Nx, intmax_t _Dx = 1>
struct ratio { 
    static constexpr intmax_t num = _Sign_of(_Nx) * _Sign_of(_Dx) * _Abs(_Nx) / _Gcd(_Nx, _Dx);
    static constexpr intmax_t den = _Abs(_Dx) / _Gcd(_Nx, _Dx);

    using type = ratio<num, den>;
};

To be honest, you’ll most often just auto durations reported after some calculations. For instance, the simplest one is taking the difference between two time points:

auto start = steady_clock::now();
// something happening...
auto end = steady_clock::now();

nanoseconds len = end - start;

- operator of steady_clock returns nanoseconds which is an alias for duration:

// ratio.h
using nano  = ratio<1, 1000000000>;
using nanoseconds  = duration<long long, nano>;

To get the actual number of nanoseconds, you can call len.count().

Duration Arithmetic

Previously you did get duration in nanoseconds, and what if you need seconds, hours and so on? Of course you could do simple math and convert it, but that’s not going to be very flexible, because you need to know beforehand which precision specific clock returns, so the code will be clock-specific.

There’s a duration_cast function that makes life much easier. Example:

nanoseconds elapsed = end - start;
seconds elapsed_seconds = duration_cast<seconds>(elapsed);
cout << "finished in " << elapsed_seconds.count() << " second(s)";

As you can see this code doesn’t actually care what type is returned from end-start, it just performs duration_cast to the required precision (seconds) and prints the number.

The other duration types that can be used:

using nanoseconds  = duration<long long, nano>;
using microseconds = duration<long long, micro>;
using milliseconds = duration<long long, milli>;
using seconds      = duration<long long>;
using minutes      = duration<int, ratio<60>>;
using hours        = duration<int, ratio<3600>>;

// C++ 20 and later:
using days   = duration<int, ratio_multiply<ratio<24>, hours::period>>;
using weeks  = duration<int, ratio_multiply<ratio<7>, days::period>>;
using years  = duration<int, ratio_multiply<ratio<146097, 400>, days::period>>;
using months = duration<int, ratio_divide<years::period, ratio<12>>>;

You can use duration also outside of any date and time clocking, to perform conversions. For instance, to convert seconds to hours I can just do this:

size_t number_of_seconds = ...;
size_t hours = duration_cast<hours>(seconds{number_of_seconds});

Convenient, right?

Appendix. Formatting Duration in Human Readable Form

Having read all the above, it’s easy to create a utility method that formats duration to more human readable format. Something like 1 day 4 hours 3 minutes 2 seconds. This is in no way optimal implementation but easy to read for sure:

std::string humanise(int value, string singular, string plural, string once, string twice) {
    if (value == 1 && !once.empty()) {
        return once;
    }

    if (value == 2 && !twice.empty()) {
        return twice;
    }

    string r = std::to_string(value);
    bool is_singular = r.ends_with('1');
    r += " ";

    r += singular;
    if (!is_singular) {
        r += "s";
    }
    return r;
}

std::string human_readable_duration(std::chrono::seconds seconds, bool short_format) {

    size_t idays{0}, ihours{0}, iminutes{0}, iseconds{0};

    // get numbers above

    auto rem = seconds;
    auto d_days = duration_cast<days>(rem);
    if(d_days.count() > 0) {
        idays = d_days.count();
        rem -= d_days;
    }

    auto d_hours = duration_cast<hours>(rem);
    if(d_hours.count() > 0) {
        ihours = d_hours.count();
        rem -= d_hours;
    }

    auto d_minutes = duration_cast<minutes>(rem);
    if(d_minutes.count() > 0) {
        iminutes = d_minutes.count();
        rem -= d_minutes;
    }

    if(rem.count() > 0) {
        iseconds = rem.count();
    }

    // now format it
    if(short_format) {
        string s;
        if(idays) {
            s += std::to_string(idays);
            s += "d";
        }
        if(ihours) {
            if(!s.empty()) s += " ";
            s += std::to_string(ihours);
            s += "h";
        }
        if(iminutes) {
            if(!s.empty()) s += " ";
            s += std::to_string(iminutes);
            s += "m";
        }
        if(iseconds) {
            if(!s.empty()) s += " ";
            s += std::to_string(iseconds);
            s += "s";
        }
        return s;
    } else {
        string s;
        if(idays) {
            s += str::humanise(idays, "day", "days");
        }
        if(ihours) {
            if(!s.empty()) s += " ";
            s += str::humanise(ihours, "hour", "hours");
        }
        if(iminutes) {
            if(!s.empty()) s += " ";
            s += str::humanise(iminutes, "minute", "minutes");
        }
        if(iseconds) {
            if(!s.empty()) s += " ";
            s += str::humanise(iseconds, "second", "seconds");
        }
        return s;
    }

}

Thanks! You can always email me or use contact form for more questions/comments etc.


cpp

1141 Words

2022-12-18