This document reviews the overall design of the C++ <chrono> library. There is
an insane amount of documentation available in the Bibliography
section, this review cannot cover it all.
The C++ standard library time facilities is a collection of libraries that build on top of each other:
<ratio>: compile-time rational-numbers arithmetic:ratio<Numerator,Denominator><chrono>: clocks, time points, durations<date>: additional durations (days, weeks, months, years), additional time points (system time, local time), calendar types (year_month_day, ...), partial calendar types (weekday,month_weekday, ...)- calendar libraries: build on top of
date:<iso_week>: iso-week date calendar (Gregorian)<julian>: Julian calendar<islamic>: Islamic calendar- ...
<tz>: time-zone support: complete parser of the IANA data-base using the types of<date>and<chrono>. It adds a data-base type with facilities to query relevant information (like leap seconds), it extends<chrono>with new clock types (utc,tai, andgps), it provides a time-zone aware time pointzoned_time<Duration>.
This document focuses on <chrono> and considers interactions with the other
libraries of the "time" stack.
The <chrono> library is built on top of <ratio> (compile-time rational
numbers: std::ratio<N, D>), and exposes the following components:
- duration: a span of time, defined as some number of ticks of some time unit.
- clocks: a starting point (epoch from now on) and a tick rate.
- time point: a duration since the epoch of a clock
- io: formatting and parsing utilities
Many types in the time facilities provide the following generic I/O operations:
operator<<performs stream outputfrom_streamparses from a stream according to the provided formatto_streamoutputs into a stream according to the provided format
The duration<Representation, TickPeriodInSeconds> class template represents a
time interval, where
Representation: is an arithmetic type used to store a tick countTickPeriodInSeconds: is a compile-time rational constant representing the number of seconds per tick
The only thing the duration stores is an internal count of ticks. This count
has type Representation, and the duration type has sizeof(Representation).
The TickPeriodInSeconds is used to convert between different duration types.
If Representation is a floating point number type then the duration can
represent fractions of ticks.
The API of duration provides the following:
count(): returns the count of tickszero: constant, zero-length durationmin: constant, duration with the lowest possible valuemax: constant, duration with the largest possible value- binary arithmetic operators:
D2 operator+(D0,D1),D2 operator-(D0,D1), ...+=,-=, ... where the two types involved don't need to be equal, and the result does not need to be equal to any of them - comparisons:
bool operator==(D0,D1),!=,<,<=, ... where the two types involved don't need to be equal D1 duration_cast(D0): converts a duration from one type to another. See docs for more info about truncation,NaNto integer, etc.floor,ceil,round: like their floating-point number equivalentsabs(provided for signed representation types only): like the floating-point equivalent- I/O facilities
The library defines the following type aliases of duration using ratio:
nanoseconds duration</*signed integer type of at least 64 bits*/, ratio<1, 1000000000>>microseconds duration</*signed integer type of at least 55 bits*/,ratio<1, 1000000>>milliseconds duration</*signed integer type of at least 45 bits*/, ratio<1, 1000>>seconds = duration</*signed integer type of at least 35 bits*/, ratio<1,1>>minutes = duration</*signed integer type of at least 29 bits*/, ratio<60, 1>>hours = duration</*signed integer type of at least 23 bits*/, ratio<3600, 1>>days = duration</*signed integer type of at least 25 bits*/, ratio<86400, 1>>weeks = duration</*signed integer type of at least 22 bits*/, ratio<604800, 1>>months = duration</*signed integer type of at least 20 bits*/, ratio<2629746, 1>>years = duration</*signed integer type of at least 17 bits*/, ratio<31556952, 1>>
And user-defined literals for these types: h for hours, min for minutes,
etc. This allows auto three_hours = 3h;.
Becuase the duration type is generic, users can easily define their own durations:
constexpr auto year = 31556952ll; // seconds in average Gregorian year
using microfortnights = std::chrono::duration<float, std::ratio<14*24*60*60, 1000000>>;
using nanocenturies = std::chrono::duration<float, std::ratio<100*year, 1000000000>>;See Microfortnights?! Are you serious? for a full example.
The Clock concept specifies the interface of all clocks:
-
Clock::repis the arithmetic type used in the internal representation ofClock::duration -
Clock::periodis the type of a compile-time rational number, that is, astd::ratio<N, D>specifying the tick period of the clock in seconds (N/D). -
Clock::durationis the duration type of the clock which is just an instance ofstd::chrono::duration<Clock::rep, Clock::period> -
Clock::time_pointis the time point of the clock which is just an instance ofstd::chrono::time_point<Clock>orstd::chrono::time_point<OtherClock, Clock::duration>. -
Clock::is_steadyis a compile-time constant that istrueift1 <= t2(wheret2is produced by a call tonowthat happens after the call tonowthat producest1) is always true and the time between clock ticks is constant, otherwise it isfalse. -
Clock::now() -> Clock::time_pointreturns a time-point representing the current point in time.
The type trait is_clock can be used to query whether a type is a Clock.
There are different kinds of clocks:
steadyaka monotonic: seeClock::is_steadyaboverealtime/async_progress/systemwide: wall clock time from the system-wide realtime clock
The realtime clock property is severely unspecified:
- there does not seem to be a way to query whether a clock is a
realtimeclock - only
realtimeclocks are guaranteed to be advanced while their associated thread sleeps - there does not seem to be a definition of "associated clock thread" in the library
However, realtime clocks are critical for for timeouts. If the clock is not
realtime, a thread that sleeps might never be awaken because the clock is
never advanced.
The C++11 version of the library contained three clock types:
system_clock: wall clock time from the system-wide realtime clock- provides
to_time_t/from_time_tfunctions thatmap it to C'stime_t.
- provides
steady_clock: monotonic clock that will never be adjustedhigh_resolution_clock: the clock with the shortest tick period available which might be an alias to eithersystem_clockorsteady_clock.
The <chrono> library was updated in C++20 with clocks that provide time-zone
support:
utc_clock: Clock for Coordinated Universal Time (UTC)from_sys/to_sysfunctions converting time points from/tosystem_clock.
tai_clock: Clock for International Atomic Time (TAI)from_utc/to_utcfunctions converting time points from/toutc_clock.
gps_clock: Clock for GPS timefrom_utc/to_utcfunctions converting time points from/toutc_clock.
file_clock: Clock used for file timefrom_utc/to_utcfunctions converting time points from/toutc_clock.from_sys/to_sysfunctions converting time points from/tosystem_clock.
local_t: pseudo-clock representing local time
@HowardHinnat mentioned in this discussion:
I would not be opposed to deprecating high_resolution_clock, with the intent to remove it after a suitable period of deprecation. The reality is that it is always a typedef to either steady_clock or system_clock, and the programmer is better off choosing one of those two and know what he’s getting, than choose high_resolution_clock and get some other clock by a roll of the dice.
While that particular discussion did not appear to achieve consensus it shows that exposing a "clock with the shortest tick period available" is not something that can be solved by just simply adding a new clock type, since that can result in a type that is hard to use correctly in practice.
The <chrono> clock survey shows that:
high_resolution_clockhas the same period assteady_clockin all major platforms (Linux, Windows, and MacOSX).- all major platforms use a 64-bit integer type as the internal representation type of the duration type.
The time_point<Clock, Duration> class template represents a point in time as a
Duration from the Clock's epoch, so that sizeof(time_point<Clock, Duration>) == sizeof(Duration).
The API is:
time_since_epoch: returns the time point as duration since the start of its clock+=,-=: modifies the time point by aDurationof the same type as that of thetime_point.++,--: increments/decrement the duration by one tick+,-: works on:- time points returning durations:
(time_point<C,D0>, time_point<C,D1>) -> D2whereD2is aDurationthat is appropriate for representing the result of the operation (it is obtained viacommon_type_t<D0,D1>) - time points and durations (and vice-versa):
(time_point<C,D0>, D1) -> time_point<C,D2>
- time points returning durations:
min/max: constants returning the time points corresponding to the smallest and largest durationstime_point_cast(time_point<SameClock, DifferentDuration>): converts the time point to another one on the same clock but with a different duration type.floor,ceil,round: perform the respective operaiton on theDuration.
Time points can be converted to a different Clock using the
clock_cast(TimePoint) -> TimePoint function, where the time point might follow
a sequence of conversions (e.g. to UTC, then system time, and from there to a
different clock type). The conversions to apply are specified via the
clock_time_conversion type trait.
The C++ <date> library adds the following time_point aliases:
sys_days = time_point<system_clock, days>sys_seconds = time_point<system_clock, seconds>
The C++ <date> library distinguishes between time-points with "serial layout",
like time_point, and time-points with "field layout", like the following
time-point types with a one-day resolution:
year_month_dayyear_month_weekdayyear_month_day_last
IIUC all these types represent "time-points" but the library does not appear to
have a TimePoint concept though.
The <date> library documentation explains that both layouts are good at some
operations and bad at others. The different layouts expose only those operations
that they are good at, and these must interoperate via conversions. The date
algorithms documentation describes efficient implementations
of these conversions. For example (taken verbatim from the <date>
documentation):
-
Field types are good at returning the values of the fields. Serial types aren't (except for weekdays). So
year_month_dayhas accessors foryear,monthandday. Andsys_daysdoes not. -
Field types are good at month and year-oriented arithmetic. Serial types aren't. So
year_month_dayhas month and year-oriented arithmetic. Andsys_daysdoes not. -
Serial types are good at day-oriented arithmetic. Field types aren't. So sys_days has day-oriented arithmetic. And
year_month_daydoes not. Though one can perform day-oriented arithmetic on the day field of ayear_month_day, with no impact on the other fields. -
To efficiently compute a day of the week, one first needs to compute a serial date. So weekday is constructible from
sys_days.
The <date> library also provides facilities to construct time-points with
one-day resolution, like auto date = 2015_y/mar/22;. Also, this library allows
users to construct invalid time-points, and provides an ok method to check
whether time-point is valid.
The <date> library uses 32-bit integers to represent minutes, but this
overflow after 4000 years. It might have made more sense to use 64-bit integers for these.
The <tz> library introduces a zoned_time time-point.
- [0] Howard Hinnant's website, containing many documents about
the rationale and design of
dateandtz - [1] The C++
dateandtzlibraries, itsreadmecontains many links to the relevant documentation, talks, and design documents.
-
[2] N2661: A Foundation to Sleep On - Clocks, Points in Time, and Time Durations is the ISO proposal for C++11's
<chrono> -
[4]
<chrono>I/O - i/o facilities andfloor,round, andceil. -
[5] N3531: User-defined Literals for Standard Library Types (version 3) added
std::chrono::duration's suffixesh,min,s,ms,us,nsin inline namespacestd::literals::chrono_literalsto C++14. -
[6] P0092R1: Polishing
<chrono>includes the<chrono>changes for C++17, which add alternative rounding modes fordurationsandtime_points(floor,ceil,round), and it addsabsfor signed duration types.
- [9]
date - [10] N3344: Toward a Standard C++
DateClass - [11]
datealgorithms
- [12]
tz