utl::time
utl::time implements some simple additions to make <chrono>
less verbose for some common use cases.
Feature summary:
- Split time into hours / minutes / seconds / milliseconds
- Time string formatting
- Floating-point
<chrono>
time units <chrono>
-compatible timers & stopwatches- C++ wrappers for
<ctime>
date-time functionality
[!Note] With C++20
<chrono>
gets a native way of handling date-time, last point will no longer be needed.
Definitions
// Unit split
struct SplitDuration {
std::chrono::hours hours;
std::chrono::minutes min;
std::chrono::seconds sec;
std::chrono::milliseconds ms;
std::chrono::microseconds us;
std::chrono::nanoseconds ns;
};
template <class Rep, class Period>
SplitDuration unit_split(std::chrono::duration<Rep, Period> val);
template <class Rep, class Period>
std::string to_string(std::chrono::duration<Rep, Period> value, std::size_t relevant_units = 3);
// Floating-point time
template <class T> using float_duration;
using ns = float_duration<std::chrono::nanoseconds >;
using us = float_duration<std::chrono::microseconds>;
using ms = float_duration<std::chrono::milliseconds>;
using sec = float_duration<std::chrono::seconds >;
using min = float_duration<std::chrono::minutes >;
using hours = float_duration<std::chrono::hours >;
// Stopwatch
template <class Clock = std::chrono::steady_clock>
struct Stopwatch {
using clock = Clock;
using time_point = typename clock::time_point;
using duration = typename clock::duration;
Stopwatch();
void start();
duration elapsed() const;
ns elapsed_ns() const;
us elapsed_us() const;
ms elapsed_ms() const;
sec elapsed_sec() const;
min elapsed_min() const;
hours elapsed_hours() const;
std::string elapsed_string(std::size_t relevant_units = 3) const;
};
// Timer
template <class Clock = std::chrono::steady_clock>
struct Timer {
using clock = Clock;
using time_point = typename clock::time_point;
using duration = typename clock::duration;
Timer();
template <class Rep, class Period>
explicit Timer(std::chrono::duration<Rep, Period> length);
template <class Rep, class Period>
void start(std::chrono::duration<Rep, Period> length);
void stop() noexcept;
duration elapsed() const;
ns elapsed_ns() const;
us elapsed_us() const;
ms elapsed_ms() const;
sec elapsed_sec() const;
min elapsed_min() const;
hours elapsed_hours() const;
std::string elapsed_string(std::size_t relevant_units = 3) const;
bool finished() const;
bool running() const noexcept;
duration length() const noexcept;
};
// Local datetime
std::tm to_localtime(const std::time_t& time);
std::string datetime_string(const char* format = "%Y-%m-%d %H:%M:%S");
Methods
Unit split
struct SplitDuration { std::chrono::hours hours; std::chrono::minutes min; std::chrono::seconds sec; std::chrono::milliseconds ms; std::chrono::microseconds us; std::chrono::nanoseconds ns; };
POD struct representing duration split into individual units.
template <class Rep, class Period> SplitDuration unit_split(std::chrono::duration<Rep, Period> val);
Splits given duration into distinct units.
For example, 73432
milliseconds will be split into 1
minute, 13
seconds and 432
milliseconds.
template <class Rep, class Period> std::string to_string(std::chrono::duration<Rep, Period> value, std::size_t relevant_units = 3);
Converts given duration to a string, showing counts only for the highest relevant_units
.
See table below for an example:
Duration | relevant_units |
Resulting string |
---|---|---|
73432 milliseconds |
4 | 1 min 13 sec 432 ms 0 us |
73432 milliseconds |
Default (3) | 1 min 13 sec 432 ms |
73432 milliseconds |
2 | 1 min 13 sec |
73432 milliseconds |
1 | 1 min |
73432 milliseconds |
0 | Empty string |
Floating-point time
[!Note] While all
std
clocks return time with integer representation,<chrono>
has native support for all arithmetic types, floating-point-represented time can be used seamlessly with all of the standard functionality.
template <class T> using float_duration;
Returns a floating-point time type corresponding to a given standard time type T
. Floating-point time uses double
representation.
using ns = float_duration<std::chrono::nanoseconds >; using us = float_duration<std::chrono::microseconds>; using ms = float_duration<std::chrono::milliseconds>; using sec = float_duration<std::chrono::seconds >; using min = float_duration<std::chrono::minutes >; using hours = float_duration<std::chrono::hours >;
Typedefs for floating-point-represented time units.
Stopwatch
Stopwatch(); void start();
Starts the time measurement.
duration elapsed() const;
Returns time elapsed since last start()
(or Stopwatch
construction) as a Clock::duration
.
ns elapsed_ns() const; us elapsed_us() const; ms elapsed_ms() const; sec elapsed_sec() const; min elapsed_min() const; hours elapsed_hours() const;
Returns time elapsed since last start()
(or Stopwatch
construction) as a floating-point duration.
std::string elapsed_string() const;
Returns time elapsed since last start()
(or Stopwatch
construction) as a formatted std::string
.
Note: See time::to_string()
for an example of output string format.
Timer
Timer();
Creates timer with a default state. Does not start time measurement.
template <class Rep, class Period> explicit Timer(std::chrono::duration<Rep, Period> length); template <class Rep, class Period> void start(std::chrono::duration<Rep, Period> length);
Starts timer with duration length
.
void stop() noexcept;
Stops the timer returning it to a default state.
duration elapsed() const;
Returns time elapsed since last start()
as a Clock::duration
.
ns elapsed_ns() const; us elapsed_us() const; ms elapsed_ms() const; sec elapsed_sec() const; min elapsed_min() const; hours elapsed_hours() const;
Returns time elapsed since last start()
as a floating-point duration.
std::string elapsed_string() const;
Returns time elapsed since last start()
as a formatted std::string
.
Note: See time::to_string()
for an example of output string format.
bool finished() const;
Returns true
if elapsed time is larger than timer length, false
otherwise.
bool running() const noexcept;
Returns true
if the timer is currently running, false
if it’s in a default state (either because it wasn’t started or because it was explicitly reset with .stop()
).
duration length() const noexcept;
Return timer length.
Local datetime
std::tm to_localtime(const std::time_t& time);
Thread-safe replacement for std::localtime()
.
There are 3 ways of getting local time in C-stdlib:
std::localtime()
— isn’t thread-safe and will be marked as “deprecated” by MSVClocaltime_r()
— isn’t a part of C++, it’s a part of C11, in reality provided by POSIXlocaltime_s()
— isn’t a part of C++, it’s a part of C23, in reality provided by Windows with reversed order of arguments
Usually working around this will requires some #ifdef
’s and non-portable code, however it is possible to exploit some side effects of std::mktime()
to emulate this function in a thread-safe manner that works on every compiler.
std::string datetime_string(const char* format = "%Y-%m-%d %H:%M:%S");
Returns current date as a string of a given format. Format strings follow std::strftime()
specification.
Thread-safe just like the previous function.
Examples
Get elapsed time
[ Run this code ]
using namespace utl;
const auto some_work = []{ std::this_thread::sleep_for(time::sec(1.7)); };
// Elapsed time as string
time::Stopwatch watch;
some_work();
std::cout << time::to_string(watch.elapsed()) << '\n';
// Elapsed time as double
watch.start();
some_work();
std::cout << watch.elapsed_ms().count() << '\n';
Output:
1 sec 700 ms 67 us
1700.48
Accumulate time
[ Run this code ]
using namespace utl;
const auto some_work = [] { std::this_thread::sleep_for(time::sec(0.05)); };
// Accumulate time on 'some_work()' in a loop
time::Stopwatch watch;
time::ms total{};
for (std::size_t i = 0; i < 20; ++i) {
watch.start();
some_work();
total += watch.elapsed();
}
std::cout << time::to_string(total, 2) << '\n';
Output:
1 sec 4 ms
Set timers
[ Run this code ]
using namespace utl;
time::Timer timer;
std::uint64_t count = 0;
timer.start(time::sec(1));
while (!timer.finished()) ++count;
std::cout << "Counted to " << count << " while looping for " << time::to_string(timer.length()) << '\n';
Output:
Counted to 42286547 while looping for 1 sec 0 ms 0 us
Get local date & time
[ Run this code ]
using namespace utl;
std::cout
<< "Current date: " << time::datetime_string("%y-%m-%d") << '\n'
<< "Current time: " << time::datetime_string("%H:%M:%S") << '\n'
<< "Current datetime: " << time::datetime_string() << '\n';
Output:
Current date: 25-03-18
Current time: 04:14:13
Current datetime: 2025-03-18 04:14:13
Motivation
Most of the things implemented in this module are quite simple to do using a native <chrono>
API, however a lot of the time simple and common use cases require a rather hefty amount of boilerplate or tends to be implemented incorrectly by most examples found online.
Below are some of the “motivating examples” showcasing that:
Get elapsed time as double
std
:
const auto start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(1700)); // wait 1.7 sec
const auto end = std::chrono::steady_clock::now();
const double ms = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() / 1e6;
// this is how it's usually done, however a truly correct way would be to use
// floating-point 'duration_cast()' which ends up being even more verbose:
// const double ms = std::chrono::duration_cast<std::chrono::duration<double,
// std::chrono::milliseconds::period>>(end - start).count();
utl::time
:
const time::Stopwatch watch;
std::this_thread::sleep_for(time::sec(1.7)); // wait 1.7 sec
const double ms = watch.elapsed_ms().count();
Convert duration to different units
std
:
const auto time_ms = std::chrono::milliseconds(1700);
const auto time_sec = std::chrono::duration_cast<std::chrono::seconds>(time_ms);
// conversion to rougher units is a loss of information (700 ms),
// due to that it requires an explicit 'duration_cast()'
const auto time_ns = std::chrono::nanoseconds(time_ms);
// conversion to finer units happens seamlessly
utl::time
:
const auto time_ms = time::ms(1700);
const auto time_sec = time::sec(time_ms);
const auto time_ns = time::ns(time_ms);
// no loss of precision regardless of units, all conversions happen seamlessly
Get current date & time
std
:
const auto now = std::chrono::system_clock::now();
const auto c_time = std::chrono::system_clock::to_time_t(now);
const auto c_tm = *std::localtime(&c_time);;
std::array<char, 256> buffer;
std::strftime(buffer.data(), buffer.size(), "%Y-%m-%d %H:%M:%S", &c_tm);
const auto result = std::string(buffer.data());
// not thread-safe, will give a warning in MSVC, we need '#ifdef's to specify 'localtime_r()'
// for POSIX, 'localtime_s()' for Windows and mutex-protected 'std::localtime()' otherwise
// doesn't include error handling, if format string was larger that in this example 'std::strftime()'
// will mess up the formatting and return '0' which usually goes unchecked in examples found online
// in C++20 this gets much easier due to a new <chrono> API
utl::time
:
const auto result = time::datetime_string();
// thread-safe, compiles everywhere, handles possible format errors