Dates, Times and Durations

Learn how to deal with times, dates and durations in C++ using the Chrono library
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a sundial in a fantasy environment
Ryan McCombe
Ryan McCombe
Posted

Our software often needs to deal with dates, times and durations. This lesson gives an brief introduction to the 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

Durations

A duration is a period of time. The chrono library gives us a range of functions to express durations, such as std::chrono::weeks, std::chrono::seconds and more.

#include <chrono>
using namespace std::chrono;

int main() {
  duration Duration1 { weeks(2) };
  duration Duration2 { hours(5) };
  duration Duration3 { milliseconds(100) };
}

Where it makes sense, we can apply arithmetic and comparison operators to durations:

// Add durations together
duration Duration { weeks(2) + days(4) };

// Increase an existing duration
Duration += days(2);

// Doubling a duration
Duration *= 2;

// Comparing durations
if (Duration > days(100)) {
  // Duration is longer than 100 days
}

Duration Literals

Duration literals for hours and below are available:

#include <chrono>
using namespace std::chrono;

int main() {
    duration A { 3h };
    duration B { 2min };
    duration C { 1h + 2min + 3s + 4ms + 5us + 6ns };
}

These literals require an appropriate using namespace statement to be available, such as using namespace std::chrono

Duration count

The underlying numeric value within a duration is available using the count method:

#include <chrono>
#include <iostream>

using namespace std::chrono;

int main() {
  duration Duration { 3h };
  std::cout << Duration.count() << " Hours" << std::endl;
}
3 Hours

duration_cast

The duration_cast function allows us to convert a duration into an equivalent one, but using a different unit of measurement. For example, here we convert 3 hours to 180 minutes:

#include <chrono>
#include <iostream>

using namespace std::chrono;

int main() {
    duration Duration { 3h };
    duration InMinutes { duration_cast<minutes>(Duration) };
    std::cout << InMinutes.count() << " Minutes" << std::endl;
}
180 Minutes

Pausing Execution Using sleep_for

In combination with the <thread> library, we can use durations to cause our application to go to sleep for a period of time:

#include <iostream>
#include <chrono>
#include <thread>

using namespace std;
using namespace std::chrono;

int main() {
  cout << "Starting" << endl;
  this_thread::sleep_for(seconds(5));
  cout << "Done!";
}

Clocks

A clock is a source of time information. By default, chrono offers 3 clocks:

  • The system_clock, which uses data from the operating system's built in clock. For most cases, using this is the simplest approach.
  • The steady_clock, which is designed to not 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.

Time Points

Using data from a clock, we can generate a value representing a specific point in time. For example, to get the current time point from the system clock, we can do this:

#include <chrono>

using namespace std::chrono;

int main() {
  time_point CurrentTime {
    system_clock::now()
  };
}

We can also add or remove durations to time points:

time_point ThreeWeeksAgo {
  system_clock::now() - weeks(3)
};

And we can generate durations by comparing two time points:

#include <chrono>
#include <thread>
#include <iostream>

using namespace std;
using namespace std::chrono;

int main() {
  time_point StartTime {
    system_clock::now()
  };

  this_thread::sleep_for(seconds(5));

  time_point EndTime {
    system_clock::now()
  };
  
  // Create a duration from two timepoints
  duration RunningTime {
    EndTime - StartTime
  };

  if (RunningTime >= 5s) {
    cout << "That took a while!";
  }
}
That took a while!

Outputting Times

Often, we will want to see the actual times being calculated within our software. The time_point objects are not designed for visual output, so there are a few steps we need to go through to get user friendly output.

First, we need to convert our time to a time_t object. This will convert our time point to a numeric value, usually representing the number of seconds that have passed since midnight on 1st January 1970. This format is commonly called epoch time.

When we're using the system_clock, a static function system_clock::to_time_t is available:

time_point StartTime {
  system_clock::now()
};
time_t TimeSinceEpoch {
  system_clock::to_time_t(StartTime)
};
cout << TimeSinceEpoch;

This will output a number, similar to this:

1651075702

This is enough to let us confirm our time is working, but it's not the most user friendly output. We want something like 28th April 2023

To get there, first we can use the localtime_r function. This function takes two arguments. The first is a pointer to our time_t. The second argument is a pointer to a tm.

tm is a type of struct designed to hold date and time values, so we will need to create that object:

tm TimeContainer;

Now that we have our time_t (which we called TimeSinceEpoch) and our tm (which we called TimeContainer), we pass their pointers into the localtime_r function.

The localtime_r function will then fill our TimeContainer with the correct data:

time_point StartTime {
  system_clock::now()
};
time_t TimeSinceEpoch {
  system_clock::to_time_t(StartTime)
};

tm TimeContainer;
localtime_r(&TimeSinceEpoch, &TimeContainer);

If we inspect our TimeContainer after running this code, we will find it has properties like tm_year, tm_month, tm_hour and more. These properties will be populated with appropriate values calculated from our TimeSinceEpoch variable.

We could use this this to generate our desired output, but the standard library provides a std::put_time function that can help us.

To access it, we need to #include <iomanip>. We can then call std::put_time, passing in a pointer to our time container, and a string that describes how we want our time to be formatted.

#include <chrono>
#include <iostream>
#include <iomanip>

using namespace std;
using namespace std::chrono;

int main() {
  time_point StartTime {
    system_clock::now()
  };
  time_t TimeSinceEpoch {
    system_clock::to_time_t(StartTime)
  };

  tm TimeContainer;
  localtime_r(&TimeSinceEpoch, &TimeContainer);

  cout << put_time(
    &TimeContainer,
    "The current time is %r on %A"
  );
}

Running this code, we'd see we finally get a user friendly time output:

The current time is 17:08:22 on Wednesday

The put_time function examines our format string, and replaces tokens like %r and %A with appropriate content from our TimeContainer.

There are dozens of different tokens we can use to get the output we want. Our options are listed on the official documentation.

Putting Everything Together

Lets 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>

using namespace std;
using namespace std::chrono;

void PrintTime(const time_t& Time) {
  tm TimeContainer;
  localtime_r(&Time, &TimeContainer);

  cout << put_time(
    &TimeContainer,
    "The current time is %r on %A"
  ) << endl;
}

int main() {
  time_point StartTime{system_clock::now()};
  PrintTime(system_clock::to_time_t(StartTime));

  this_thread::sleep_for(seconds(5));

  time_point EndTime{system_clock::now()};
  PrintTime(system_clock::to_time_t(EndTime));

  duration<float> RunningTime{EndTime - StartTime};

  cout << "The running time was "
       << RunningTime.count()
       << " seconds";
}

Our output will be something like:

The current time is 17:23:21 on Wednesday
The current time is 17:23:26 on Wednesday
The running time was 5.01718s

Clock Drift

There is something to bear in mind when using sleep_for , and almost any other way of causing our application to sleep.

That is, our applications will always sleep for slightly longer than we requested. In the previous run, we slept for approximately 0.017 seconds longer than intended.

This is simply due to a nuance in how operating systems schedule processes. We should consider it an instruction to "sleep for at least this amount of time".

In most use cases, these fractions of a second don't matter - however, in some scenarios, they do. For example, if we were creating a clock that updated every second, and updates are delayed by 0.01 seconds on average, our clock would run slow, losing 14 minutes per day.

For these scenarios, we need to compensate for the drift. A common way of doing this is to compare the current time point and the time point from the previous iteration of the loop.

For example, if we find the two time points are 1.01 seconds apart, our next sleep_for call should only be 0.99 seconds. This causes our program to self-correct, and remain accurate even when running for long periods of time.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Overloading the << operator

Learn how to overload the << operator, so our custom types can stream information directly to the console using std::cout
3D art showing a maid character
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved