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
andduration
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, likedouble
,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;
}
}
To contact me, send an email anytime or leave a comment below.