Skip to content
C++
Library
since C++11
Intermediate

std::chrono

Type-safe time and duration library — durations, clocks, time points, and C++20 calendar and timezone types.

std::chronosince C++11

A type-safe time library that models durations (quantities of time), time points (moments anchored to a clock), and clocks (sources of time), extended in C++20 with civil-calendar types, IANA timezone support, and additional precision clocks.

Overview

std::chrono is built on three orthogonal concepts:

  • duration<Rep, Period> — a tick count of type Rep where each tick spans Period (a std::ratio) seconds. Arithmetic between durations of different periods compiles only when the conversion is lossless, or when you explicitly call duration_cast.
  • time_point<Clock, Duration> — a point in time measured from a clock's epoch, statically bound to that clock. Subtracting two time_points of the same clock yields a duration; subtracting across clocks is a compile error.
  • Clocks — tag types exposing now() and a duration typedef. C++11 introduced three; C++20 added four more with precisely specified epochs.

C++20 substantially extended the library: civil-calendar types (year_month_day, weekday, month_day, …), IANA timezone support (time_zone, zoned_time), parsing via std::chrono::parse, and new clocks.

Syntax

Duration template and predefined aliases

cpp
// Primary template
template<class Rep, class Period = std::ratio<1>>
class std::chrono::duration;

// C++11 predefined aliases
std::chrono::nanoseconds   // duration<long long, std::nano>
std::chrono::microseconds  // duration<long long, std::micro>
std::chrono::milliseconds  // duration<long long, std::milli>
std::chrono::seconds       // duration<long long>
std::chrono::minutes       // duration<long long, std::ratio<60>>
std::chrono::hours         // duration<long long, std::ratio<3600>>

// C++20 predefined aliases
std::chrono::days          // duration<int, std::ratio<86400>>
std::chrono::weeks         // duration<int, std::ratio<604800>>
std::chrono::months        // duration<int, std::ratio<2629746>> // 30.436875 days
std::chrono::years         // duration<int, std::ratio<31556952>> // 365.2425 days

User-defined literals require using namespace std::chrono_literals; (C++11):

cpp
auto t = 2h + 30min + 15s;  // chrono::hours, minutes, seconds
auto d = 500ms + 250us;     // chrono::milliseconds, microseconds
auto n = 100ns;             // chrono::nanoseconds

C++20 literal trap: 15d creates std::chrono::day{15} — a calendar day number, not a days duration. 2025y creates std::chrono::year{2025} — a calendar year, not a years duration. There are no UDL suffixes for days, weeks, months, or years durations; construct them directly: std::chrono::days{7}.

Duration operations

cpp
using namespace std::chrono;

// Implicit conversion is allowed only when lossless
seconds s = 5min;            // OK: minutes → seconds is exact
// minutes m = 90s;          // Error: potential loss of precision

// Explicit conversion truncates toward zero
auto m = duration_cast<minutes>(90s);  // 1min, not 1.5

// C++17: directed rounding
floor<seconds>(1500ms).count();  // 1
round<seconds>(1500ms).count();  // 2  (nearest)
ceil<seconds>(1500ms).count();   // 2
abs(-500ms).count();             // 500  // C++17

// Raw tick value
milliseconds{2500}.count();      // 2500
duration<double, std::ratio<1>>{0.5}.count(); // 0.5 — fractional seconds

Clock comparison

ClockMonotonicEpochC++ version
steady_clockYesUnspecifiedC++11
system_clockNoUnix epoch (C++20: guaranteed)C++11
high_resolution_clockImpl-definedImpl-definedC++11
utc_clockYesUnix epoch + leap secondsC++20
tai_clockYes1958-01-01 (TAI)C++20
gps_clockYes1980-01-06 (GPS)C++20
file_clockImpl-definedImplementation-definedC++20

high_resolution_clock is an alias for steady_clock on most implementations (GCC/libstdc++, recent MSVC). Never assume its epoch or monotonicity; prefer steady_clock explicitly.

Examples

Timing a block of code

cpp
#include <chrono>
#include <print>  // C++23

template<class F>
std::chrono::microseconds timed(F&& f) {
    auto start = std::chrono::steady_clock::now();
    f();
    return std::chrono::duration_cast<std::chrono::microseconds>(
        std::chrono::steady_clock::now() - start);
}

auto us = timed([] { process_batch(); });
std::println("elapsed: {} µs", us.count());

// C++20: duration types are directly formattable
std::println("elapsed: {}", us);  // "12345µs"

Sleeping with steady deadlines

cpp
#include <chrono>
#include <thread>
using namespace std::chrono_literals;

std::this_thread::sleep_for(100ms);

// Absolute deadline prevents drift accumulation in loops
auto next = std::chrono::steady_clock::now();
for (int i = 0; i < 10; ++i) {
    next += 200ms;
    do_periodic_work();
    std::this_thread::sleep_until(next);
}

Wall-clock timestamps and formatting (C++20)

cpp
#include <chrono>
#include <format>  // C++20

auto now = std::chrono::system_clock::now();

// Truncate to seconds resolution
auto now_s = std::chrono::floor<std::chrono::seconds>(now);

// ISO 8601 UTC
auto utc = std::format("{:%Y-%m-%dT%H:%M:%SZ}", now_s);
// "2026-05-22T14:23:05Z"

// Local civil time via IANA timezone database (C++20)
auto zt = std::chrono::zoned_time{
    std::chrono::current_zone(),
    std::chrono::system_clock::now()
};
auto local = std::format("{:%Y-%m-%d %H:%M:%S %Z}", zt);
// "2026-05-22 16:23:05 CEST"

Calendar date arithmetic (C++20)

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

// Construct a specific date
year_month_day launch{year{2026}, June, day{1}};

// Convert a system_clock time_point to a calendar date
year_month_day today = floor<days>(system_clock::now());

// Duration between two dates — convert to sys_days first
days diff = sys_days{launch} - sys_days{today};

// Day of week
weekday wd{sys_days{today}};
bool is_friday = (wd == Friday);

// Last day of a month (C++20)
year_month_day_last last_feb = year{2026} / February / last;
year_month_day end_of_feb{last_feb};  // 2026-02-28

Parsing a date string (C++20)

cpp
#include <chrono>
#include <sstream>

std::chrono::sys_days date;
std::istringstream ss("2026-06-01");
ss >> std::chrono::parse("%Y-%m-%d", date);
// date == sys_days for 2026-06-01

Converting between clocks (C++20)

cpp
#include <chrono>

auto sys_now = std::chrono::system_clock::now();

// system_clock ignores leap seconds; utc_clock counts them
auto utc_now = std::chrono::clock_cast<std::chrono::utc_clock>(sys_now);

// time_point_cast: change duration granularity, keep the clock
auto at_second = std::chrono::time_point_cast<std::chrono::seconds>(sys_now);

Best Practices

Always use steady_clock for measuring elapsed time. Only use system_clock when you need a wall-clock value to display or store as a timestamp. The two are not substitutable: system_clock can jump backward on NTP corrections or forward on DST transitions.

Pass duration objects across API boundaries, never raw integers. A parameter typed std::chrono::milliseconds self-documents its unit and rejects unit mismatches at the call site. An int64_t timeout_ms parameter is a latent bug.

Know which rounding mode you need. duration_cast truncates; floor, round, and ceil (C++17) give directed rounding. For display, round is usually correct. For deadline computation, ceil avoids delivering early.

Seed RNGs with steady_clock, not time(0). time(0) has one-second resolution; two processes spawned in the same second get identical sequences.

cpp
auto seed = std::chrono::steady_clock::now().time_since_epoch().count();
std::mt19937_64 rng{static_cast<uint64_t>(seed)};

For calendar date arithmetic, use C++20 types. Do not implement leap-year logic or month-end arithmetic manually. year_month_day handles these correctly; converting to sys_days for duration arithmetic is the idiomatic approach.

Common Pitfalls

system_clock for elapsed time

cpp
// Bad: can go backward or jump
auto start = std::chrono::system_clock::now();
do_work();
auto elapsed = std::chrono::system_clock::now() - start; // may be negative

// Good
auto start = std::chrono::steady_clock::now();
do_work();
auto elapsed = std::chrono::steady_clock::now() - start; // always >= 0

C++20 calendar literals are not durations

cpp
using namespace std::chrono_literals;

auto x = 7d;    // std::chrono::day{7}  — a calendar day number, NOT a duration
auto y = 2025y; // std::chrono::year{2025} — a calendar year, NOT a duration

// To construct durations for days/weeks/months/years:
auto seven_days = std::chrono::days{7};
auto two_years  = std::chrono::years{2};

duration_cast truncates, not rounds

cpp
// duration_cast truncates toward zero
std::chrono::duration_cast<std::chrono::seconds>(999ms).count(); // 0, not 1

// When nearest-value is needed:
std::chrono::round<std::chrono::seconds>(999ms).count();  // 1
std::chrono::round<std::chrono::seconds>(500ms).count();  // 0 (round to even)

Month overflow in calendar arithmetic

cpp
using namespace std::chrono;

year_month_day d{year{2026}, January, day{31}};
// Adding one month naively produces an invalid date
auto next = d.year() / (d.month() + months{1}) / d.day();
next.ok(); // false — 2026-02-31 does not exist

// Converting to sys_days normalises the overflow (Feb 31 → Mar 3)
year_month_day normalised{sys_days{next}};

// For end-of-month clamping, use year_month_day_last explicitly
auto eom = d.year() / (d.month() + months{1}) / last; // 2026-02-28

Comparing .count() values of different duration types

cpp
std::chrono::seconds s{1};
std::chrono::milliseconds ms{1000};

s.count() == ms.count(); // false: 1 == 1000
s == ms;                 // true: implicit conversion, then compare

Never compare .count() values across duration types. Compare the duration objects directly; the library applies the appropriate unit conversion.

C++20 calendar and timezone

C++20 transforms <chrono> from a measurement library into a full civil-calendar system. The additions cover date types (year, month, day, weekday, year_month_day, …), IANA timezone support via std::chrono::time_zone and std::chrono::zoned_time, and std::chrono::parse for reading date/time strings.

cpp
// C++20
#include <chrono>
using namespace std::chrono;

// Construct dates using operator/ overloads
year_month_day d1 = 2026y / June / 22d;          // year/month/day
year_month_day d2 = 22d / June / 2026y;           // day/month/year
year_month_day d3 = June / 22d / 2026y;           // month/day/year

// Validate
d1.ok();  // true
year_month_day bad{year{2026}, February, day{30}};
bad.ok(); // false

// Arithmetic over calendar months and years
year_month_day next_year = d1 + years{1};   // 2027-06-22
year_month_day two_months_later = d1 + months{2};  // 2026-08-22

Caveat: months and years arithmetic can produce invalid dates (e.g., Jan 31 + 1 month = Feb 31). Always call .ok() or normalise through sys_days afterward.

std::chrono::year_month_day

year_month_day is the primary C++20 civil-calendar date type, storing a year, month, and day component separately. Convert to sys_days for duration arithmetic, and back to year_month_day to recover calendar fields.

cpp
// C++20
#include <chrono>
using namespace std::chrono;

// Today as a calendar date
year_month_day today{floor<days>(system_clock::now())};

// Extract components
int y = static_cast<int>(today.year());
unsigned m = static_cast<unsigned>(today.month());
unsigned d = static_cast<unsigned>(today.day());

// Duration between two dates (always work in sys_days)
year_month_day birth{year{1990}, March, day{15}};
days age_days = floor<days>(system_clock::now()) - sys_days{birth};
int age_years = static_cast<int>((age_days / days{365}).count());  // approx

// Last day of the current month
auto end_of_month = today.year() / today.month() / last;
year_month_day eom{end_of_month};  // e.g. 2026-06-30

year_month_day is not directly comparable to time_point; the intermediate sys_days conversion (which is a time_point<system_clock, days>) is the bridge between the two worlds.

Time zone conversions

std::chrono::zoned_time pairs a time_point with an IANA timezone rule, handling DST and UTC offsets automatically. The timezone database is accessed via std::chrono::locate_zone and std::chrono::get_tzdb.

cpp
// C++20
#include <chrono>
#include <format>

auto utc_now = std::chrono::system_clock::now();

// Attach a timezone
std::chrono::zoned_time zt_ny{"America/New_York", utc_now};
std::chrono::zoned_time zt_tok{"Asia/Tokyo",      utc_now};
std::chrono::zoned_time zt_local{std::chrono::current_zone(), utc_now};

// Get the local time_point and UTC offset
auto local_tp  = zt_ny.get_local_time();
auto utc_tp    = zt_ny.get_sys_time();
auto info      = zt_ny.get_info();       // sys_info: offset, abbrev, DST flag
auto offset_h  = std::chrono::duration_cast<std::chrono::hours>(info.offset);

// Format in local time
std::string s = std::format("{:%Y-%m-%d %H:%M:%S %Z}", zt_ny);
// "2026-06-22 10:23:05 EDT"
cpp
// C++20 — convert between two named zones
auto to_zone = [](std::chrono::system_clock::time_point tp,
                  std::string_view from_zone,
                  std::string_view to_zone_name) {
    std::chrono::zoned_time from{from_zone, tp};
    return std::chrono::zoned_time{to_zone_name, from.get_sys_time()};
};

Platform note: The IANA database is embedded in libc++ (Apple, most Linux) and available as an optional install (/usr/share/zoneinfo) on MSVC. Verify availability at runtime with std::chrono::get_tzdb_list().

Formatting durations and time points (C++20)

C++20 makes all <chrono> types directly formattable via std::format, with strftime-style conversion specifiers extended for sub-second precision. No manual .count() conversion is required.

cpp
// C++20
#include <chrono>
#include <format>
#include <print>   // C++23

using namespace std::chrono;
using namespace std::chrono_literals;

// Duration formatting — unit suffix appended automatically
milliseconds d1{1234};
std::println("{}", d1);            // "1234ms"
std::println("{:%Q %q}", d1);      // "1234 ms"  (%Q = value, %q = unit)

// time_point formatting
auto now = system_clock::now();
std::println("{:%Y-%m-%d}", now);  // "2026-06-22"
std::println("{:%T}", now);        // "14:23:05.123456789"  (full sub-second)

// Truncate before formatting to control precision
auto now_s = floor<seconds>(now);
std::println("{:%Y-%m-%dT%H:%M:%SZ}", now_s);  // "2026-06-22T14:23:05Z"

// zoned_time: local time + timezone abbreviation
zoned_time zt{"Europe/Paris", now};
std::println("{:%F %T %Z}", zt);   // "2026-06-22 16:23:05.123456789 CEST"

// calendar types
year_month_day ymd = 2026y / June / 22d;
std::println("{:%B %d, %Y}", ymd); // "June 22, 2026"
weekday wd{sys_days{ymd}};
std::println("{:%A}", wd);         // "Monday"

std::chrono::parse is the inverse of std::format, reading date/time strings using the same format codes. Use it to deserialise timestamps from files, databases, or network protocols.

cpp
// C++20
#include <chrono>
#include <sstream>

system_clock::time_point tp;
std::istringstream ss{"2026-06-22T14:23:05Z"};
ss >> std::chrono::parse("%Y-%m-%dT%H:%M:%SZ", tp);

Benchmarking with high_resolution_clock

steady_clock is the right choice for benchmarking — it is guaranteed monotonic, whereas high_resolution_clock is an implementation-defined alias that may map to system_clock on some platforms. Wrap the pattern in a reusable template to avoid repetition.

cpp
// C++11 and later
#include <chrono>
#include <utility>    // std::forward
#include <string_view>
#include <print>      // C++23; replace with printf for C++11

template<class F, class... Args>
auto benchmark(std::string_view label, int iterations, F&& f, Args&&... args) {
    using clock = std::chrono::steady_clock;
    using ns    = std::chrono::nanoseconds;

    // Warm-up pass (fills instruction cache, branch predictor)
    std::forward<F>(f)(std::forward<Args>(args)...);

    auto start = clock::now();
    for (int i = 0; i < iterations; ++i)
        std::forward<F>(f)(std::forward<Args>(args)...);
    auto stop = clock::now();

    auto total = std::chrono::duration_cast<ns>(stop - start);
    auto per_op = total / iterations;
    std::println("{}: {} total, {} per op", label, total, per_op);
    return per_op;
}
cpp
// C++11 — preventing the compiler from optimising away the work
// Use a volatile sink or compiler barrier to avoid dead-code elimination
template<class T>
inline void do_not_optimise(T const& val) {
    // GCC/Clang: asm volatile ("" : : "r,m"(val) : "memory");
    // MSVC:      _ReadWriteBarrier();
    (void)val;
}

Caveat: On modern CPUs, frequency scaling, turbo boost, and NUMA effects can produce high variance. Run at least 1 000 iterations, discard the first few results, and report median and P99 rather than mean.

Custom duration units

Any compile-time std::ratio can define a custom duration type, making domain-specific time units first-class citizens that participate in the usual duration_cast / arithmetic machinery.

cpp
// C++11
#include <chrono>
#include <ratio>

// Video frame durations
using frames_24fps  = std::chrono::duration<long long, std::ratio<1, 24>>;
using frames_60fps  = std::chrono::duration<long long, std::ratio<1, 60>>;

// Audio samples at 44100 Hz
using audio_samples = std::chrono::duration<long long, std::ratio<1, 44100>>;

// Fortnights (2 weeks)
using fortnights = std::chrono::duration<int, std::ratio<1209600>>;

void example() {
    using namespace std::chrono;

    frames_24fps f{48};   // 48 frames at 24 fps = 2 seconds
    auto secs = duration_cast<seconds>(f);    // 2s
    auto ms   = duration_cast<milliseconds>(f); // 2000ms

    // Implicit conversion works when lossless
    frames_60fps f60 = duration_cast<frames_60fps>(f); // 120 frames at 60fps

    audio_samples samples{88200};  // 2 seconds of audio at 44.1kHz
    auto s2 = duration_cast<seconds>(samples); // 2s

    fortnights fn{3};
    auto days = duration_cast<std::chrono::days>(fn); // 42 days
}

Custom duration types are zero-overhead; all conversions are computed entirely at compile time via std::ratio arithmetic and the conversion factor is folded into a single multiply/divide.

std::chrono::tai_clock and GPS time (C++20)

C++20 adds tai_clock and gps_clock for domains that require time scales with no leap-second discontinuities. tai_clock counts from 1958-01-01 00:00:00 TAI; gps_clock counts from 1980-01-06 00:00:00 GPS. Both are monotonic and fully interconvertible with utc_clock.

cpp
// C++20
#include <chrono>

// Current time on each scale
auto sys = std::chrono::system_clock::now();
auto utc = std::chrono::clock_cast<std::chrono::utc_clock>(sys);
auto tai = std::chrono::clock_cast<std::chrono::tai_clock>(sys);
auto gps = std::chrono::clock_cast<std::chrono::gps_clock>(sys);

// As of 2026 there are 37 TAI-UTC leap seconds and 18 GPS-UTC leap seconds
// TAI is always ahead of UTC by 37 seconds (since 2017-01-01)
// GPS is always ahead of UTC by 18 seconds (since 2017-01-01)
auto tai_offset = std::chrono::duration_cast<std::chrono::seconds>(
    tai.time_since_epoch() - utc.time_since_epoch());  // +37s

// Convert back to system_clock for display
auto back_to_sys = std::chrono::clock_cast<std::chrono::system_clock>(tai);
cpp
// C++20 — practical: timestamp a log entry in TAI (no leap-second ambiguity)
struct LogEntry {
    std::chrono::tai_clock::time_point timestamp;
    std::string message;
};

LogEntry make_entry(std::string msg) {
    return {std::chrono::tai_clock::now(), std::move(msg)};
}

Why TAI/GPS? Embedded systems, financial markets, and scientific instruments often need a smooth, continuous time scale without the discontinuities introduced by leap seconds in UTC. utc_clock represents those discontinuities explicitly (it counts leap seconds), whereas system_clock/POSIX time silently smears them over the surrounding window.

See Also

  • <ctime> — time_t, tm, strftime for C-style time and legacy API interop
  • std::format / std::print (C++20/23) — {:%Y-%m-%d %H:%M:%S} format specifiers work directly on duration, time_point, zoned_time, and calendar types
  • std::this_thread::sleep_for / sleep_until (<thread>) — chrono-aware thread blocking
  • std::filesystem::file_time_type — file_clock::time_point returned by std::filesystem::last_write_time
  • std::condition_variable::wait_for / wait_until — chrono-aware condition variable blocking