Blog 2026 02 04 Time in C++: Once More About Testing
Post
Cancel

Time in C++: Once More About Testing

Time is time. We tend to think it’s simple to deal with it. But it’s more complex than it sounds. Both in programming and in real life.

You want to measure how long something took, add a timestamp to a log entry, schedule a task for later, or display a date to a user in their local time. Each of these sounds straightforward — until clocks jump, time zones shift, or tests start failing randomly.

This series started with the basics of <chrono>, but it was never just about learning an API. The goal was to build a mental model for time in C++: what the standard library gives us, what guarantees it makes, and where we still need to be careful.

Modern C++ doesn’t try to hide the complexity of time. Instead, it makes it explicit. Clocks, time points, durations, calendars, and time zones all exist for a reason, and using them correctly starts with understanding how they fit together.

In this final article, we’ll take a step back and briefly revisit the core building blocks, look at the clocks C++ provides and when to use each of them, touch on conversions and custom clocks, and close with a recap of time zones. The goal isn’t to repeat the series, but to leave you with a clear, practical map you can rely on when working with time in real-world C++ code.

The Three Pillars Revisited

In the first part of the series, we talked about three fundamental building blocks behind time handling in C++: time points, durations, and clocks. Almost everything in <chrono> is built on top of these, and most mistakes come from mixing up their roles.

A time point answers a simple question: when did something happen? It represents a specific point on a timeline, but it only has meaning in the context of a clock. A time_point without knowing which clock it belongs to is incomplete — the same numeric value can represent entirely different moments depending on the clock behind it.

A duration answers a different question: how long did something take? Durations are independent of clocks and epochs. They represent an amount of time, not a position on a timeline. This is why durations are usually the safest and most flexible part of <chrono>: they compose well, they make units explicit, and they are cheap to use.

Finally, clocks define how time flows. A clock specifies an epoch, a tick rate, and the guarantees it provides — for example, whether it is steady or whether it can jump. When you choose a clock, you’re not just picking a way to read the current time, you’re choosing a set of assumptions about how that time behaves.

Seen together, these three concepts form a simple but powerful model:

  • Clocks define timelines
  • Time points locate events on those timelines
  • Durations measure distances between events

Keeping these roles separate — and being explicit about which one you’re working with — is the foundation for writing correct, maintainable time-related code in C++.

The Clock Landscape

Most mistakes with time in C++ are really mistakes about which clock is being used.

The standard library gives us several clocks, each with different guarantees. Knowing why they exist matters far more than memorizing their APIs.

Instead of relying on names or intuition, it helps to think in terms of use cases:

Use caseRecommended clock
Measure elapsed timesteady_clock
Timeouts & deadlinessteady_clock
Performance measurementssteady_clock
Logging & timestampssystem_clock
Civil time & calendarssystem_clock + calendar / time zones
Leap-second–aware logicutc_clock
File timestampsfile_clock
Simulations & testingCustom clock

Conversions: What You Can Convert — and Why Some Things Don’t

Once you start working with multiple clocks, conversions become unavoidable — and <chrono> is deliberately conservative about what it allows.

Durations are the simplest case. Converting between them is cheap, explicit, and safe, as long as you accept possible truncation. This is one of the strengths of <chrono>: units are part of the type system, and conversions are visible in the code.

Time points are different. A time_point only makes sense in the context of its clock, and not all clocks share a common timeline. As a result, only a limited set of time-point conversions is supported, typically where there is a well-defined relationship between clocks.

This is why some conversions that look “obvious” at first are not available. Preventing implicit or ambiguous conversions is a design choice, not a limitation. It forces you to acknowledge which timeline you are working on and what assumptions you are making.

In practice, this means that conversions in <chrono> tend to be:

  • explicit
  • constrained
  • and sometimes slightly verbose

That verbosity is intentional. When time crosses clock boundaries, the code should make it obvious.

Custom Clocks

Standard clocks observe the real world. That’s fine in production, but it quickly becomes a problem in tests, simulations, or any code that needs deterministic behavior.

Custom clocks let you control what “now” means and how time flows. Time can advance manually, stay frozen, or follow rules that match your domain instead of the system clock. This makes time-dependent code easier to test and reason about.

More importantly, using a custom clock turns time into an explicit dependency. Instead of hiding calls to system_clock::now(), your design clearly communicates where and how time matters.

Custom clocks aren’t exotic — they’re often the simplest and cleanest solution when real time gets in the way.

Time Zones

Time zones are where time handling usually becomes painful, and C++20 finally provides standard support for them. The goal here isn’t convenience, but correctness.

The time-zone library is built around a clear separation of concerns. sys_time acts as the anchor, representing a point on the system clock’s timeline, while local_time and zoned_time handle civil time and time-zone rules. Conversions are explicit, and ambiguous situations — such as daylight saving transitions — are surfaced rather than silently guessed.

This design can feel strict at first, especially compared to older APIs. But that strictness is intentional. It makes assumptions about civil time visible and forces code to deal with edge cases instead of ignoring them.

In practice, time zones tend to live at the edges of a system: parsing input, displaying output, or communicating with users and external services. Keeping them there, and anchoring everything else to a well-defined clock, leads to simpler and more robust designs.

Closing Thoughts

Time is one of those topics that never quite stops being tricky. Clocks jump, calendars change, and even something as simple as “now” depends on more assumptions than it first appears.

The goal of this series wasn’t to turn <chrono> into something simple, but to make it understandable. Modern C++ doesn’t hide the complexity of time — it exposes it in a structured way. Clocks, time points, durations, calendars, and time zones all exist to make different aspects of time explicit.

If there’s one takeaway worth keeping, it’s this: be deliberate about time. Choose your clocks carefully, prefer durations for measurement, keep civil time and time zones at the edges, and don’t be afraid to introduce custom clocks when real time gets in the way.

You don’t need to memorize every type in <chrono>. What matters is knowing which questions to ask and recognizing when time deserves a second thought. With that mindset, the standard library gives you the tools you need — without pretending that time is simpler than it really is.

Connect deeper

If you liked this article, please

This post is licensed under CC BY 4.0 by the author.