Time Handling with std::chrono
/ 4 min read
Table of Contents
std::chrono in C++11 finally brought type-safe time handling to C++. No more confusing seconds vs milliseconds bugs or platform-specific time APIs.
What std::chrono Actually Does
It provides type-safe time points, durations, and clocks:
#include <chrono>
using namespace std::chrono;
// Durations with different unitsauto ms = 500ms; // 500 millisecondsauto sec = 5s; // 5 secondsauto min = 2min; // 2 minutes
// Time pointsauto now = steady_clock::now();auto later = now + 10s;
// Duration between time pointsauto elapsed = later - now; // 10 secondsThe type system prevents unit confusion at compile time.
The Three Main Components
std::chrono has three main parts:
- Durations: Represent time intervals
- Time points: Represent specific moments in time
- Clocks: Sources of time points
// Duration - a span of timeduration<int, std::milli> ms_duration(500); // 500 milliseconds
// Time point - a specific momenttime_point<steady_clock> start = steady_clock::now();
// Clock - provides current timeauto system_time = system_clock::now();auto steady_time = steady_clock::now();auto high_res_time = high_resolution_clock::now();When I Actually Use std::chrono
Most of the time, I use std::chrono for:
- Timing operations: Measuring how long things take:
class PerformanceTimer { std::chrono::steady_clock::time_point start_;
public: PerformanceTimer() : start_(std::chrono::steady_clock::now()) {}
void reset() { start_ = std::chrono::steady_clock::now(); }
auto elapsed() const { return std::chrono::steady_clock::now() - start_; }
double elapsed_seconds() const { auto duration = elapsed(); return std::chrono::duration<double>(duration).count(); }};
// UsagePerformanceTimer timer;expensive_operation();std::cout << "Operation took: " << timer.elapsed_seconds() << " seconds\n";- Timeouts and delays: Sleep and timeout handling:
void wait_for_connection(int timeout_seconds) { auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_seconds);
while (std::chrono::steady_clock::now() < deadline) { if (is_connected()) { return; // Connected successfully } std::this_thread::sleep_for(std::chrono::milliseconds(100)); }
throw std::runtime_error("Connection timeout");}
void rate_limited_operation() { static auto last_call = std::chrono::steady_clock::now(); const auto min_interval = std::chrono::milliseconds(100);
auto now = std::chrono::steady_clock::now(); auto elapsed = now - last_call;
if (elapsed < min_interval) { std::this_thread::sleep_for(min_interval - elapsed); }
// Do the operation last_call = std::chrono::steady_clock::now();}- Logging with timestamps: Adding time information to logs:
class Logger {public: void log(const std::string& level, const std::string& message) { auto now = std::chrono::system_clock::now(); auto time_t = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch()) % 1000;
std::cout << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S"); std::cout << '.' << std::setfill('0') << std::setw(3) << ms.count(); std::cout << " [" << level << "] " << message << std::endl; }};
// Output: 2022-09-15 14:30:25.123 [INFO] Application started- Animation and game loops: Frame timing:
class GameLoop { std::chrono::steady_clock::time_point last_frame_; std::chrono::duration<double> target_frame_time_{1.0 / 60.0}; // 60 FPS
public: void run() { last_frame_ = std::chrono::steady_clock::now();
while (running_) { auto frame_start = std::chrono::steady_clock::now(); auto delta_time = frame_start - last_frame_;
update(std::chrono::duration<double>(delta_time).count()); render();
auto frame_end = std::chrono::steady_clock::now(); auto frame_duration = frame_end - frame_start;
if (frame_duration < target_frame_time_) { std::this_thread::sleep_for(target_frame_time_ - frame_duration); }
last_frame_ = frame_start; } }};Duration Conversions
Conversions between units are explicit and safe:
auto seconds = 5s;auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(seconds);// milliseconds.count() == 5000
auto ms = 1500ms;auto sec = std::chrono::duration_cast<std::chrono::seconds>(ms);// sec.count() == 1 (truncated, not rounded)
// For exact conversions without truncation:auto precise_seconds = std::chrono::duration<double>(ms);// precise_seconds.count() == 1.5Different Clocks for Different Purposes
Choose the right clock for your use case:
// system_clock - wall clock time, can jump backwards/forwardsauto wall_time = std::chrono::system_clock::now();
// steady_clock - monotonic clock, never goes backwards (best for timing)auto monotonic_time = std::chrono::steady_clock::now();
// high_resolution_clock - highest precision available (often alias to steady_clock)auto precise_time = std::chrono::high_resolution_clock::now();Real World Example
Here’s a connection pool with timeout handling:
template<typename Connection>class ConnectionPool { std::queue<std::unique_ptr<Connection>> available_; std::mutex mutex_; std::condition_variable cv_; std::chrono::milliseconds default_timeout_{5000}; // 5 second timeout
public: std::unique_ptr<Connection> acquire(std::chrono::milliseconds timeout = {}) { if (timeout == std::chrono::milliseconds::zero()) { timeout = default_timeout_; }
std::unique_lock<std::mutex> lock(mutex_); auto deadline = std::chrono::steady_clock::now() + timeout;
while (available_.empty()) { if (cv_.wait_until(lock, deadline) == std::cv_status::timeout) { throw std::runtime_error("Connection pool timeout"); } }
auto conn = std::move(available_.front()); available_.pop(); return conn; }
void release(std::unique_ptr<Connection> conn) { { std::lock_guard<std::mutex> lock(mutex_); available_.push(std::move(conn)); } cv_.notify_one(); }};
// UsageConnectionPool<DatabaseConnection> pool;auto conn = pool.acquire(2s); // 2 second timeoutLiterals for Convenience
C++14 added time literals that make code much cleaner:
using namespace std::chrono_literals;
// Before literalsstd::chrono::milliseconds ms(500);std::chrono::seconds sec(30);
// With literalsauto ms = 500ms;auto sec = 30s;auto min = 5min;auto hour = 2h;The Pattern I Follow
I use steady_clock for timing measurements and system_clock when I need actual calendar time:
// For measuring elapsed timeauto start = std::chrono::steady_clock::now();do_work();auto elapsed = std::chrono::steady_clock::now() - start;
// For timestamps and loggingauto timestamp = std::chrono::system_clock::now();log_event(timestamp, "Something happened");
// Always use the literal suffixes when availablestd::this_thread::sleep_for(100ms);auto timeout = 5s;Common Gotchas
- Integer truncation:
duration_casttruncates, doesn’t round - Clock choice: Use
steady_clockfor intervals,system_clockfor timestamps - Precision loss: Be careful converting from high to low precision
std::chrono eliminates timing bugs and makes time handling much more robust. It’s verbose at first, but the type safety is worth it.