Chrono on Steroids
Many new exciting features made it into C++20. Who has never heard of Modules or Concepts, or even Ranges and the spaceship operator? This article however takes a look at another part, namely the chrono library and the upcoming calendar and time zone support.
Voted into the working draft in 2018, the P0355R7 proposal by Howard Hinnant and Tomasz Kamiński aims to extend the date and time utilities bundled in the chrono library with calendar and time zone support.
What do we get?
A lot. Seriously, take a look at the proposal or read the already available
documentation on cppreference.
Everything you can do with tm
and time_t
from the old C API you can now do
with convenient C++ types and functions.
The new features come with many promises (quoted from the proposal):
- Seamless integration with the existing
<chrono>
library. - Type safety.
- Detection of errors at compile time.
- Performance.
- Ease of use.
- Readable code.
- No artificial restrictions on precision. Note: current financial software is
currently in the middle of a transition from seconds precision to
milliseconds precision. This library handles that transition as seamlessly as
<chrono>
does.
Pre C++20
Before we dig into the new fancy code let’s take a look at how we had to do things before.
Current time
Following code will print the current time:
|
|
Sat Jul 18 15:36:25 2020
So we first use the C++ API in <chrono>
header with system_clock
and then
convert back to a time_t
to process it via ctime
in <ctime>
header. Not
only do we needlessly switch API but we also are not thread-safe with ctime
.
Let’s try to stick to one API:
|
|
Sat Jul 18 15:36:25 2020
Ok that is cleaner but we are back to the C API and still aren’t thread-safe.
Thread-safe:
|
|
Sat Jul 18 15:36:25 2020
Now we are thread-safe but we are also in C land (depending on your system
headers you might need to use ctime_s
).
Current time but custom format
How do we display a different format, let’s say I also want to see milliseconds and have a numerical date display.
The numerical display can be achieved with put_time
from <iomanip>
and its
many format specifiers.
|
|
2020-07-18 15:36:25
Thread-safe:
|
|
2020-07-18 15:36:25
Depending on your system headers you might need to use localtime_s
.
However showing anything more precise than seconds is impossible with the tm
struct (and respectively time_t
holding seconds since 1 January 1970). That
means we have to fallback to std::chrono::system_clock
or clock_gettime
which use the system clock and generally have a higher precision.
|
|
2020-07-18 15:36:25.368
Can we get this with chrono? Well kinda, but we are back to a bunch of conversions back and forth.
|
|
2020-07-18 15:36:25.368
Specific time zone
Now let’s try to show the time in Québec, Canada which follows the Eastern Time Zone (ET) (UTC -5/-4).
|
|
Sat Jul 18 09:36:25 2020
As I’m located in Germany (UTC +1/+2) the time is now six hours earlier.
Set to specific time
Let’s assume we want to create a time point 18 July 2020, 15:36:25 in CEST time zone.
|
|
2020-07-18 15:36:25
I’m using designated initializers just so it is possible to see what all the
fields are for. The field tm_yday
I had to calculate. It describes the
zero-initialized day count in the current year.
Let’s see how it looks like when we parse the time point from a string:
|
|
2020-07-18 15:36:25
Summary
Now it’s not the biggest deal to use the C API but the usage examples above show time management is definitely not a first class citizen in C++.
New stuff
Let’s try all the above examples with the new C++ 20 functionality. As I write this article neither libstd++ nor libc++ have implemented it yet, so I will use the reference implementation HowardHinnant/date.
Current time (C++ 20)
For the following to work with the reference implementation, specifically to
use the operator<<
the line using namespace date;
has to be added in the
beginning.
|
|
2020-07-18 13:36:25.368151566
What we see here is the system clock precision (nanoseconds on my system) and the time is displayed in UTC (my system timezone is CEST = UTC +2).
Let’s show the local time. Note that with the reference implementation you have
to include the date
namespace for the streaming operator and use zoned_time
and current_zone
from the reference implementation (instead of std::
namespace use date::
namespace for those).
|
|
2020-07-18 15:36:25.368151566 CEST
Current time but custom format (C++ 20)
Now let’s format the output so it just displays the date and the time with
millisecond precision with help of the <format>
header. Again with the
reference implementation we have to use the date
namespace for the streaming
operator and use floor
, zoned_time
, current_zone
and format
from there
(the <format>
header is not shipped with my stdlibc++ version yet).
|
|
2020-07-18 15:36:25.368
Specific time zone (C++ 20)
Selecting a specific time zone is quite straightforward. We just take the same
command we took for the local time
and change the time zone description. With the reference
implementation we have to include the date
namespace for the streaming
operator and also use the zoned_time
function from there.
|
|
2020-07-18 09:36:25.368151566 EDT
Set to specific time (C++ 20)
Following code sets the current time. With the reference implementation we have
to enable the date
namespace again for the streaming operator and we also
have to use sys_days
from there.
|
|
2020-07-18 15:36:25
Not only is this very nicely readable but it’s also typesafe. The fancy
operator/
construction of a date can be used in all sensible orderings, e.g.
2020y/July/18
, 18d/July/2020
, … Keep also in mind that the construction
is very efficient as the compiler will calculate the result at compile time in
this case.
Let’s take a look at how we parse a time point. Again the date
namespace is
required when using the reference implementation as well as the parse
function.
|
|
2020-07-18 15:36:25.000000000
Conclusion
The new chrono features simplify working with times, dates and time zones tremendously. It is no longer required to work with the C API. That results in type safety when working with time and dates. Additionally time conversions are covered by the chrono API and manual conversions mistakes are no longer an issue.
Chrono uses the C API under the hood and a recent compiler will optimize to the exact same assembler a human would get when programming with C.1
Apart from the aspects shown in this article there are many more new features like custom calendar support, calendar conversions, indexed weekdays (e.g. 2nd Monday of a month), last weekday of a month and am/pm hour format among others.