Dates, Times and Durations
Learn the basics of the chrono library and discover how to effectively manage durations, clocks, and time points
Our software often needs to deal with dates, times, and durations. This lesson gives a brief introduction to the most useful utilities within C++ to deal with these requirements. In C++, the chrono
library is the main way we implement these features.
We can #include
the library and access its functionality through the std::chrono
namespace.
#include <chrono>
using namespace std::chrono;
In this lesson, we will cover 3 aspects of chrono
- durations, clocks, and time points
Chrono Durations
A std::chrono::duration
is a period of time, such as two weeks, one hour, or 1 minute and 30 seconds. The chrono
library gives us a range of functions to express durations, such as std::chrono::weeks
, std::chrono::seconds
, and more:
#include <chrono>
int main() {
using namespace std::chrono;
duration Duration1 { weeks(2) };
duration Duration2 { hours(5) };
duration Duration3 { milliseconds(100) };
}
The duration
type has overloaded the arithmetic operators such as +
and *=
, allowing us to combine durations in intuitive ways:
#include <chrono>
int main() {
using namespace std::chrono;
// Add durations together
duration Duration { weeks(2) + days(4) };
// Increase an existing duration
Duration += days(2);
// Doubling an existing duration
Duration *= 2;
}
We can also compare durations using comparison operators such as >
and <=
:
#include <chrono>
int main() {
using namespace std::chrono;
duration Duration { weeks(2) + days(4) };
// This will be true
if (Duration > days(15)) {
// ...
}
}
Duration Literals
Duration literals are available. These require us to have an appropriate using namespace
statement in place. Options include:
using namespace std::chrono;
using namespace std::chrono_literals;
using namespace std::literals;
In scopes where an appropriate using
statement is in effect, we can then express durations using literals such as h
, min
and s
:
#include <chrono>
int main() {
using namespace std::chrono;
duration Hours{5h};
duration Minutes{5min};
duration Seconds{5s};
duration Milliseconds{5ms};
duration Microseconds{5us};
duration Nanoseconds{5ns};
}
Duration count()
The underlying numeric value within a duration is available using the count()
method:
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
duration Duration{5h};
std::cout << Duration.count() << " Hours";
}
5 Hours
Changing Units Using duration_cast()
The duration_cast()
function allows us to convert a duration into an equivalent duration, but using a different unit of measurement.
In the following example, we convert 1.5
hours to minutes, which results in a count()
of 90
as we'd expect:
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
duration Duration{1.5h};
duration Minutes{
duration_cast<minutes>(Duration)};
std::cout << Minutes.count() << " Minutes";
}
90 Minutes
Using a duration
to Pause Execution
In combination with the <thread>
library, we can use durations to cause our application to go to sleep for some time. This is available as the sleep_for()
function on std::this_thread
, which we have access to if we #include <thread>
Below, we use this to insert a 5-second pause between the "Starting" and "Done" logs:
#include <chrono>
#include <iostream>
#include <thread>
int main() {
using namespace std::chrono;
std::cout << "Starting\n";
std::this_thread::sleep_for(5s);
std::cout << "Done!";
}
Starting
Done!
Chrono Clocks
A clock is a source of time information. By default, chrono offers 3 clocks:
- The
system_clock
uses data from the operating system's built-in clock. For most cases, using this is the simplest approach. - The
steady_clock
is designed not to be impacted by changes to the system clock. Use this if it is important that users cannot exploit our software by changing their system clock. - The
high_resolution_clock
, which is designed to be highly accurate. Use this if we require millisecond, microsecond, or nanosecond-level accuracy. This can be useful when measuring performance, for example.
Chrono Time Points
Using data from a clock, we can generate a value representing a specific point in time. Chrono represents time points using the std::chrono::time_point
type. For example, to get the current time point from the system clock, we can do this:
#include <chrono>
int main() {
using namespace std::chrono;
time_point CurrentTime {
system_clock::now()
};
}
We can also add or remove durations to time points. Below, we generate a time point 3 weeks in the past:
#include <chrono>
int main() {
using namespace std::chrono;
time_point ThreeWeeksAgo {
system_clock::now() - weeks(3)
};
}
We can generate a duration
by comparing time points using the -
operator. Below, we generate two time points, approximately 5 seconds apart. We then store the difference between those two time points as a duration
, which we output as milliseconds using duration_cast()
:
#include <chrono>
#include <iostream>
#include <thread>
int main() {
using namespace std::chrono;
time_point StartTime{system_clock::now()};
std::this_thread::sleep_for(seconds(5));
time_point EndTime{system_clock::now()};
// Create a duration from two time points
duration RunningTime{EndTime - StartTime};
std::cout << "Time difference in ms: "
<< duration_cast<milliseconds>(
RunningTime).count();
}
Time difference in ms: 5007
Using std::time_t
Chrono is a relatively new addition to the language. C++ has an older way of representing times, which is still commonly used. These times are typically stored using the std::time_t
type, and we'll need to be able to interact with both.
Our Chrono clocks have functions that allow us to generate std::time_t
objects from chrono::time_point
objects. When we're using the system_clock
, we can call the system_clock::to_time_t()
function.
Below, we're using this to store the current time in a std::time_t
called Time
:
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
time_point StartTime {
system_clock::now()
};
std::time_t Time {
system_clock::to_time_t(StartTime)
};
std::cout << Time;
}
This will output a number, similar to this:
1651075702
Date and Time Structs (tm
)
A Unix timestamp is enough to represent a time, but it's not a particularly friendly format to work with. An alternative way time points are stored is as tm
structs. These represent dates and times in terms of their components - years, months, hours, etc.
Interpreting our std::time_t
in this way involves two parts:
- The standard library's
tm
struct gives us a way to create an object for storing these individual components. Our object will have member variables liketm_year
,tm_hour
,tm_min
, and more. The complete list of its variables, and what they mean, are available on a standard library reference such as cppreference.com. - The
localtime__r()
function populates atm
object. This function takes two arguments. The first is a pointer to ourtime_t
. The second argument is a pointer to thetm
which it will populate:
localtime_r(&TimePoint, &TimeContainer);
Below, we create our tm
called TimeContainer
, and use the localtime_r()
function to populate it using data from our time_t
, which we've called TimeSinceEpoch
:
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
time_point StartTime {
system_clock::now()
};
std::time_t TimeSinceEpoch {
system_clock::to_time_t(StartTime)
};
tm TimeContainer;
localtime_s(&TimeContainer, &TimeSinceEpoch);
std::cout << "The current time is "
<< TimeContainer.tm_hour << ":"
<< TimeContainer.tm_min;
}
The current time is 23:32
Using std::put_time()
and Format Strings
Note: C++20 introduced std::format()
, which is a modern and more flexible alternative to std::put_time()
. If using std::put_time()
causes issues or unexected output in your environment, don't worry too much about it. Just proceed to the next lesson, where we'll implement the same capability using std::format()
instead.
Formatting a date and time to be displayed in a user-friendly way is a surprisingly complex task. With some effort, we could use the various member variables of the tm
struct to accomplish it, but there are easier ways.
In this section, we'll cover std::put_time()
, which lets us output the content of std::time_t
objects using a formatting string. A formatting string is a string that contains special tokens that will be replaced by dynamic values, such as the contents of a variable.
To access std::put_time()
, we need to #include <iomanip>
. We can then call std::put_time()
, which accepts two arguments:
- A pointer to our time container
- The formatting string
The following shows this in action:
#include <chrono>
#include <iostream>
#include <iomanip>
int main() {
using namespace std::chrono;
time_point StartTime {
system_clock::now()
};
time_t TimeSinceEpoch {
system_clock::to_time_t(StartTime)
};
tm TimeContainer;
localtime_r(&TimeSinceEpoch, &TimeContainer);
std::cout << std::put_time(
&TimeContainer,
"The current time is %T on %A"
);
}
Running this code, we'd see a user-friendly time output:
The current time is 17:08:01 on Wednesday
The std::put_time()
function examines our format string and replaces tokens like %T
and %A
with dynamic content based on the values stored within the TimeContainer
.
There are dozens of different tokens we can use to get the output we want. We cover these tokens in more detail in the next lesson, covering string interpolation. A full list of options is also available from most standard library references, such as cppreference.com.
Putting Everything Together
Let's put all these concepts together, to see a more complicated example of working with clocks, time points, durations, and thread sleeping:
#include <chrono>
#include <iomanip>
#include <iostream>
#include <thread>
void PrintTime(const time_t& Time) {
tm TimeContainer;
localtime_s(&TimeContainer, &Time);
std::cout << std::put_time(
&TimeContainer,
"The current time is %T on %A\n");
}
int main() {
using namespace std::chrono;
time_point StartTime{system_clock::now()};
PrintTime(system_clock::to_time_t(StartTime));
std::this_thread::sleep_for(seconds(5));
time_point EndTime{system_clock::now()};
PrintTime(system_clock::to_time_t(EndTime));
duration RunningTime{
EndTime - StartTime};
std::cout << "The running time was "
<< duration_cast<milliseconds>(RunningTime)
.count()/1000.0 << " seconds";
}
Our output will be something like:
The current time is 17:13:12 PM on Wednesday
The current time is 17:13:17 PM on Wednesday
The running time was 5.012 seconds
Summary
In this lesson, we've explored the essential concepts of handling dates, times, and durations in C++ using the chrono library. We also introduced other representations of time, including Unix timestamps stored in the std::time_t
type, and the tm
struct.
Key Learnings:
- Understanding and using
std::chrono::duration
for representing time intervals in various units. - Implementing and manipulating time using the
chrono
library, including addition, subtraction, and comparison of durations. - Utilizing different types of clocks in C++, namely
system_clock
,steady_clock
, andhigh_resolution_clock
, for various time measurement needs. - Creating and manipulating
std::chrono::time_point
objects to represent specific points in time. - Converting time points to and from
std::time_t
and usingtm
structures for a more readable time format.
String Interpolation
A detailed guide to string formatting using C++20's std::format()
, and C++23's std::print()