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 TypeDescriptionSince C++
system_clockSystem-wide real time wall clock.11
steady_clockMonotonic 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_clockHigh resolution clock with the smallest possible tick period (nanosecond). In MSVC it’s a synonym for steady_clock.11
file_clockClock used for time on filesystem.20
utc_clockRepresents UTC time.20
tai_clockRepresents Atomic Time (TAI).20
gps_clockRepresents GPS time.20
local_tPseudo-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:

1auto now_st = steady_clock::now();
2auto now_hr = high_resolution_clock::now();
3auto 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.

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

prints

Fri Dec 16 10:08:31 2022

Duration

Internally duration is a template:

1template <class _Rep, class _Period>
2    class duration { // represents a time duration
3    public:
4        using rep    = _Rep;
5        using period = typename _Period::type;
6// ...

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:

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

Milli and pico are defined in <ratio> as:

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

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

1template <intmax_t _Nx, intmax_t _Dx = 1>
2struct ratio { 
3    static constexpr intmax_t num = _Sign_of(_Nx) * _Sign_of(_Dx) * _Abs(_Nx) / _Gcd(_Nx, _Dx);
4    static constexpr intmax_t den = _Abs(_Dx) / _Gcd(_Nx, _Dx);
5
6    using type = ratio<num, den>;
7};

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:

1auto start = steady_clock::now();
2// something happening...
3auto end = steady_clock::now();
4
5nanoseconds len = end - start;

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

1// ratio.h
2using nano  = ratio<1, 1000000000>;
3using 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:

1nanoseconds elapsed = end - start;
2seconds elapsed_seconds = duration_cast<seconds>(elapsed);
3cout << "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:

 1using nanoseconds  = duration<long long, nano>;
 2using microseconds = duration<long long, micro>;
 3using milliseconds = duration<long long, milli>;
 4using seconds      = duration<long long>;
 5using minutes      = duration<int, ratio<60>>;
 6using hours        = duration<int, ratio<3600>>;
 7
 8// C++ 20 and later:
 9using days   = duration<int, ratio_multiply<ratio<24>, hours::period>>;
10using weeks  = duration<int, ratio_multiply<ratio<7>, days::period>>;
11using years  = duration<int, ratio_multiply<ratio<146097, 400>, days::period>>;
12using 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:

1size_t number_of_seconds = ...;
2size_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:

 1std::string humanise(int value, string singular, string plural, string once, string twice) {
 2    if (value == 1 && !once.empty()) {
 3        return once;
 4    }
 5
 6    if (value == 2 && !twice.empty()) {
 7        return twice;
 8    }
 9
10    string r = std::to_string(value);
11    bool is_singular = r.ends_with('1');
12    r += " ";
13
14    r += singular;
15    if (!is_singular) {
16        r += "s";
17    }
18    return r;
19}
20
21std::string human_readable_duration(std::chrono::seconds seconds, bool short_format) {
22
23    size_t idays{0}, ihours{0}, iminutes{0}, iseconds{0};
24
25    // get numbers above
26
27    auto rem = seconds;
28    auto d_days = duration_cast<days>(rem);
29    if(d_days.count() > 0) {
30        idays = d_days.count();
31        rem -= d_days;
32    }
33
34    auto d_hours = duration_cast<hours>(rem);
35    if(d_hours.count() > 0) {
36        ihours = d_hours.count();
37        rem -= d_hours;
38    }
39
40    auto d_minutes = duration_cast<minutes>(rem);
41    if(d_minutes.count() > 0) {
42        iminutes = d_minutes.count();
43        rem -= d_minutes;
44    }
45
46    if(rem.count() > 0) {
47        iseconds = rem.count();
48    }
49
50    // now format it
51    if(short_format) {
52        string s;
53        if(idays) {
54            s += std::to_string(idays);
55            s += "d";
56        }
57        if(ihours) {
58            if(!s.empty()) s += " ";
59            s += std::to_string(ihours);
60            s += "h";
61        }
62        if(iminutes) {
63            if(!s.empty()) s += " ";
64            s += std::to_string(iminutes);
65            s += "m";
66        }
67        if(iseconds) {
68            if(!s.empty()) s += " ";
69            s += std::to_string(iseconds);
70            s += "s";
71        }
72        return s;
73    } else {
74        string s;
75        if(idays) {
76            s += str::humanise(idays, "day", "days");
77        }
78        if(ihours) {
79            if(!s.empty()) s += " ";
80            s += str::humanise(ihours, "hour", "hours");
81        }
82        if(iminutes) {
83            if(!s.empty()) s += " ";
84            s += str::humanise(iminutes, "minute", "minutes");
85        }
86        if(iseconds) {
87            if(!s.empty()) s += " ";
88            s += str::humanise(iseconds, "second", "seconds");
89        }
90        return s;
91    }
92
93}

Have feedback or questions? Feel free to email me.