C++ Lesson 56 – std::chrono | Dataplexa

MODERN C++ Β· LESSON 56

std::chrono

Every program meets time sooner or later β€” how long did that code take, when did this event happen, wait exactly half a second before trying again. C++ has a built-in time library for all of it: std::chrono. It gives you clocks, durations and time points β€” and, unlike the old C time functions, it is type-safe: you cannot accidentally add seconds to milliseconds.

1. Why time matters

Think of the programs you have already written in this module. In Lesson 53 a thread slept while it worked; in Lesson 55 a future waited with a half-second timeout. All of those numbers β€” one second, 500 milliseconds β€” are std::chrono durations working behind the scenes. This lesson makes them first-class citizens: you will measure real code, print real dates, and set real timeouts.

Time shows up in four everyday jobs: measuring how long code takes, pausing for an exact amount of time, stamping events with the date and time, and timing out work that takes too long. std::chrono handles all four with the same three ideas β€” clocks, time points and durations.

Type-safe time. A std::chrono::seconds value and a std::chrono::milliseconds value are different types. The compiler converts between them for you, so 3s + 500ms is exactly 3,500 milliseconds β€” never a unit mistake that silently corrupts your numbers.

2. Clocks, time points and durations

Chrono has three building blocks. A clock is a source of time β€” it can tell you "now". A time_point is one moment in time, like "3:15 pm". A duration is a length of time, like "45 minutes". You take two time_points from the same clock, subtract them, and you get a duration β€” that is how every timing in this lesson works.

Think of a clock as a watch, a time_point as the time the watch shows right now, and a duration as the gap between two readings. C++ gives you three clocks to choose from:

ClockWhat it measuresUse it for
system_clockThe wall-clock time β€” the date and time on the computer's clock.Timestamps, logging, showing the current date and time.
steady_clockA stopwatch that only moves forward and never jumps.Measuring how long code takes.
high_resolution_clockThe clock with the smallest tick the system offers.Fine-grained benchmarking.

The golden rule is simple: system_clock for showing time, steady_clock for measuring time. A wall clock can jump β€” the user changes the time, or daylight saving starts β€” so it is a bad stopwatch. A steady clock never jumps, so it is a perfect stopwatch.

3. Durations β€” the building block

A duration is a number plus a unit. The library gives you ready-made types β€” std::chrono::hours, minutes, seconds, milliseconds, microseconds and nanoseconds β€” and since C++14 you can write them as literals: 2h, 45min, 30s, 250ms, 900us, 100ns. The literals need using namespace std::chrono_literals;. To change units, use duration_cast; to get the plain number out, use .count().

Code story: We add 3 seconds and 500 milliseconds β€” the library does the unit conversion for us β€” and print the answer in milliseconds. Then we split 90 seconds into minutes and leftover seconds. Watch how duration_cast changes the unit and .count() pulls the number out.

#include <iostream>
#include <chrono>

int main() {
    using namespace std::chrono_literals;
    auto a = 3s + 500ms;
    std::cout << "3s + 500ms = " << std::chrono::duration_cast<std::chrono::milliseconds>(a).count() << " ms" << std::endl;
    auto ninety = 90s;
    auto mins = std::chrono::duration_cast<std::chrono::minutes>(ninety).count();
    auto secs = std::chrono::duration_cast<std::chrono::seconds>(ninety).count() % 60;
    std::cout << "90 seconds = " << mins << " minute " << secs << " seconds" << std::endl;
    return 0;
}
3s + 500ms = 3500 ms
90 seconds = 1 minute 30 seconds

What just happened? 3s + 500ms added two different units and silently produced 3.5 seconds. duration_cast<milliseconds> converted that to 3,500 milliseconds, and .count() gave us the plain number 3500. For 90 seconds, casting to minutes gave 1, and the remainder (% 60) gave the leftover 30 seconds.

DurationLengthExample
std::chrono::nanosecondsOne billionth of a secondtiny = 100ns
std::chrono::microseconds1,000 nanosecondsfast = 900us
std::chrono::milliseconds1,000 microsecondsdelay = 250ms
std::chrono::seconds1,000 millisecondswait = 30s
std::chrono::minutes60 secondsmeeting = 45min
std::chrono::hours60 minutestimeout = 2h

4. system_clock β€” the wall clock

system_clock is the clock on the wall: its now() returns the current date and time. To turn a system_clock time_point into a calendar date, convert it with to_time_t() and format it with std::put_time. The format string works like the old strftime: %d is the day, %b the short month name, %Y the year, %H:%M:%S the time.

Code story: We ask the wall clock for "now", turn it into a plain calendar time, and print it in the format "day month year, hour:minute:second". The output shows the moment the program ran β€” your time will be different.

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

int main() {
    auto now = std::chrono::system_clock::now();
    std::time_t t = std::chrono::system_clock::to_time_t(now);
    std::cout << "Current time: " << std::put_time(std::localtime(&t), "%d %b %Y, %H:%M:%S") << std::endl;
    return 0;
}
Current time: 11 Nov 2026, 14:56:10

What just happened? system_clock::now() gave us a time_point for this instant. to_time_t() converted it to the classic C calendar time, and put_time printed it in the shape we chose. Run it again and the time changes β€” that is the point of a wall clock.

5. steady_clock β€” the stopwatch

Measuring is the most common chrono job, and steady_clock is the tool for it. The pattern is always the same: take now() before the code, take now() after it, subtract, and cast the difference to the unit you want. Because the clock never jumps, the difference is always honest β€” even if the user changes the system time in the middle of your program.

Code story: We time a loop that adds the numbers from 1 to 1,000,000. The stopwatch starts before the loop and stops after it. The sum is 500000500000, and the measured time is just a few milliseconds β€” the exact number depends on your computer.

#include <iostream>
#include <chrono>

int main() {
    auto start = std::chrono::steady_clock::now();
    long long sum = 0;
    for (int i = 1; i <= 1000000; i++) sum += i;
    auto end = std::chrono::steady_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Time taken: " << ms << " ms" << std::endl;
    return 0;
}
Sum: 500000500000
Time taken: 4 ms

What just happened? The two now() calls bookended the loop, and subtracting them gave a duration of a few milliseconds. duration_cast<milliseconds> expressed that duration in milliseconds, and .count() gave the plain number. (Your milliseconds will differ β€” the point is that the measurement itself is exact.)

Measure twice, trust the pattern. One timing can be thrown off by the computer doing something else at that moment. For serious measurement, run the code several times and look at the average or the fastest run β€” a single wild number usually means the machine was busy, not that the code is slow.

6. Timing real code

The stopwatch pattern works on anything. Here are two everyday cases: checking that a pause really lasted as long as you asked, and timing a heavier job β€” sorting 100,000 random numbers.

Code story: We ask the thread to sleep for exactly 200 milliseconds, then measure how long it really slept. The stopwatch should read about 200 ms β€” sleep_for guarantees "at least" the time you asked for, so it is usually right on the number or a hair over.

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

int main() {
    auto start = std::chrono::steady_clock::now();
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    auto end = std::chrono::steady_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Elapsed: " << ms << " ms" << std::endl;
    return 0;
}
Elapsed: 200 ms

What just happened? sleep_for(milliseconds(200)) paused the thread for 200 milliseconds, and the steady clock measured the real gap as 200 ms. If your machine is busy it may read 201 or 202 β€” that is normal; the guarantee is "not less than 200".

Code story: Now a real job: fill a vector with 100,000 random numbers (fixed seed 42, so the numbers are the same every run) and time the sort. Sorting is the kind of work you actually want to measure in a program.

#include <iostream>
#include <chrono>
#include <vector>
#include <algorithm>
#include <random>

int main() {
    std::vector<int> v(100000);
    std::mt19937 rng(42);
    for (auto& x : v) x = rng();
    auto start = std::chrono::steady_clock::now();
    std::sort(v.begin(), v.end());
    auto end = std::chrono::steady_clock::now();
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    std::cout << "Sorted 100000 numbers in " << ms << " ms" << std::endl;
    return 0;
}
Sorted 100000 numbers in 38 ms

What just happened? The vector was filled before the stopwatch started, so only the sort itself was measured. std::sort arranged 100,000 numbers in 38 milliseconds on this machine β€” yours will be a different number, but the same order of magnitude. Notice how the timing code stays the same no matter what you measure: start, work, end, subtract, cast.

7. Timeouts and waiting with chrono

Chrono durations plug directly into the waiting tools you already know. future::wait_for takes a duration β€” wait at most this long β€” future::wait_until takes a time_point β€” wait until this moment β€” and this_thread::sleep_for pauses for a duration while this_thread::sleep_until pauses until a time_point. The chrono type you pass decides the meaning β€” and the compiler will not let you mix them up.

Code story: A slow job needs 2 seconds. We wait for it with a 500-millisecond timeout β€” the same pattern as Lesson 55, now with the duration written as a proper chrono value. The timeout fires first, we do other work, and then get() collects the final answer.

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

int slowJob() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 99;
}

int main() {
    std::future<int> f = std::async(std::launch::async, slowJob);
    if (f.wait_for(std::chrono::milliseconds(500)) == std::future_status::timeout) {
        std::cout << "Still waiting after 500 ms..." << std::endl;
    }
    std::cout << "Final: " << f.get() << std::endl;
    return 0;
}
Still waiting after 500 ms...
Final: 99

What just happened? wait_for(milliseconds(500)) gave the job half a second, reported a timeout, and let the program carry on instead of freezing. The later get() waited for the rest of the 2 seconds and fetched 99. Chrono turns a vague wait into a precise, typed value.

8. Benchmarking: reserve vs no reserve

Timing is how you settle arguments about speed. A classic example: when a vector grows by push_back, it reallocates and copies its contents each time it runs out of room. Calling reserve() first books all the space up front β€” no reallocations at all. Instead of guessing which is faster, measure both with the same stopwatch.

Code story: A small lambda times one job: push 100,000 numbers into a vector, with or without reserve(100000) first. We run the same job both ways and print both times side by side β€” the reserve version wins by a wide margin.

#include <iostream>
#include <chrono>
#include <vector>

int main() {
    auto timeIt = [](bool useReserve) {
        auto start = std::chrono::steady_clock::now();
        std::vector<int> v;
        if (useReserve) v.reserve(100000);
        for (int i = 0; i < 100000; i++) v.push_back(i);
        auto end = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
    };
    std::cout << "Without reserve: " << timeIt(false) << " ms" << std::endl;
    std::cout << "With reserve: " << timeIt(true) << " ms" << std::endl;
    return 0;
}
Without reserve: 52 ms
With reserve: 7 ms

What just happened? Without reserve, the vector kept reallocating and copying as it grew β€” 52 milliseconds. With reserve(100000) it booked the space once and never copied β€” 7 milliseconds. Your numbers will differ, but reserve is always faster. This is the whole point of chrono: turn a hunch about speed into a measurement.

9. Common mistakes

MistakeWhat happensFix
Using system_clock to measure a durationThe clock can jump β€” the user changes the time, or daylight saving starts β€” and the measurement goes wrong.Use steady_clock for every measurement.
Forgetting duration_cast before printingA duration is not a plain number, so you cannot print it directly with <<.Cast first, then use .count(): duration_cast<milliseconds>(d).count().
Comparing a duration with a plain numberThe code does not compile: milliseconds(500) is not the number 500.Compare with a duration: wait_for(milliseconds(500)).
Using sleep_for for precise timingsleep_for guarantees at least the time β€” it often sleeps a little longer.Use it only for pauses; measure with steady_clock if you need accuracy.
Mixing clocks β€” comparing a system_clock time_point with a steady_clock oneThe code does not compile β€” the two time_points live in different worlds.Keep each time_point with its own clock; only system_clock converts to calendar time.

10. Practice

Try these on your own. Run each program and check its output.

  1. Time how long it takes to add the numbers from 1 to 10,000,000 and print the time in milliseconds. Expected: sum 50000005000000, a few milliseconds.
  2. Print the current date and time using system_clock and put_time. The output should show today's date and the time you ran the program.
  3. Sleep for 1 second with sleep_for, then measure the real elapsed time with steady_clock and print it. It should read about 1000 ms.
  4. Time sorting a vector of 50,000 random numbers and print the time in milliseconds. Compare it with the 100,000-number sort in this lesson β€” is it roughly half the time?

11. Test Yourself β€” Quick Quiz

Which clock should you use to measure how long code takes?


What does duration_cast do?


What does .count() return for a duration?


Fill in the blanks β€” type the missing word into the box.

To measure how long code takes, use the clock.


Convert a duration to milliseconds with .


Pause the current thread for half a second with std::this_thread::(500ms).


UP NEXT

Lesson 57 β€” Functional Programming

Functions as values β€” lambdas, std::function, and callable objects.