chrono_io
This is a small library which does nothing but add non-configurable, non-localizable
streaming to std::chrono::duration. The streaming operator lives in
namespace date for convenience purposes, and does not live in
namespace std::chrono as that would violate the standard.
The entire API of this library is:
namespace date
{
template <class CharT, class Traits, class Rep, class Period>
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os,
const std::chrono::duration<Rep, Period>& d);
} // namespace date
Just one signature for an entire library? Are you serious?!
Very serious.
Parsing is not included because there are a million ways to parse durations, and none of them are universally agreed upon. And they are all easily implementable without private access to
std::chrono::durationjust by parsing arithmetic types and strings.
No formatting options?!
No.
The formatting provided here is based on an international SI standard. There is no need for localization. If you need other formatting options for streaming your durations that is easy enough for you to supply. What is needed here is zero-effort streaming of
chrono::durations without hassle. Streamingchrono::durations is the biggest use of.count()(a dangerous type cast), and that use should be handled by a library such as this.
#include "chrono_io.h"
#include <iostream>
void f();
int
main()
{
using namespace date;
using namespace std::chrono;
auto t0 = steady_clock::now();
f();
auto t1 = steady_clock::now();
std::cout << t1 - t0 << '\n';
}
With this library, you don't have to specify the duration type (and cast to it) in timing
situations like this. This will create whatever duration is native to
steady_clock and print out the value and units for that duration,
for example:
682580ns
Change steady_clock to system_clock (and if that clock has
different units on your platform), and the output automatically changes units for you:
auto t0 = system_clock::now(); f(); auto t1 = system_clock::now(); std::cout << t1 - t0 << '\n';
682µs
And yes, it really does print out the Greek letter µ and not
u. When streaming to char-based streams it uses UTF-8 encoding
to represent the Unicode character 'MICRO SIGN' (U+00B5). Otherwise it uses UTF-16 or
UTF-32 based on the size of character the stream is using.
In general, if the duration tick period is the same type as one of the non-optional
std::ratio SI convenience typedefs (atto thru exa),
then the unit will use the internationally accepted symbol for the
metric prefix followed
by 's'.
In addition, if the duration tick period is ratio<60>, then the
printed unit is min. And if the duration tick period is
ratio<3600>, then the unit is h.
std::cout << 1s << '\n'; std::cout << 2min << '\n'; std::cout << 3h << '\n';
Outputs:
1s 2min 3h
Sometimes odd durations pop up which have no widely recognized names. For example consider this code:
using frames = duration<int, ratio<1, 60>>;
std::cout << 45ms + frames{5} << '\n';
This sum can't be exactly represented as either milliseconds or frames (1/60 of a second).
The compiler figures out the coarsest unit which can exactly represent the sum of
any millisecond and any frame and auto-generates that unit to hold the sum. For this
example that duration will have a tick period of 1/3000 of a second. This library
puts that ratio inside of square brackets, and then appends 's':
385[1/3000]s
If the durations tick period is a whole number of seconds (period::den == 1),
then the /1 is dropped inside of the square brackets. The following example
uses months from date.h.
std::cout << months{6} << '\n';
6[2629746]s
Trivia: 2,629,746s is the exact length of the average Gregorian month, which is also exactly 30.436875 24h days.
Question: Why are there separate implementations for C++11 and C++14?
Answer: The units of a duration are always known at compile
time. Thus the string representation of the units is always known at compile time. This
is true even when the unit is of the form [num/den]s. But only C++14 has
the gravitas to do the complete string conversion at compile time. And now that I've
claimed that, I'm sure someone will come up with a C++11 that VS-2015 won't compile anyway.
So I need a run-time solution (based on std::basic_string<CharT>,
and a compile-time solution that currently only gcc and clang with -std=c++14
can handle.
For example if you compile the following code with -std=c++14 and clang:
using frames = std::chrono::duration<int, std::ratio<1, 60>>;
void
test(std::ostream& os, std::chrono::milliseconds x, frames y)
{
using namespace date;
os << x + y;
}
And then inspect the generated assembly, it contains this:
movabsq $6714920027984703835, %rax ## imm = 0x5D303030332F315B
Which is assembly-speak for "[1/3000]" all boiled down to one x86_64 instruction. The
's' wouldn't quite fit within this one instruction and follows in a later
instruction. C++14 can do some amazing things at compile time! For C++11 I just
gave up and called std::to_string.
template <class CharT, class Traits, class Rep, class Period> std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const std::chrono::duration<Rep, Period>& d);Effects: Equivalent to:
os << d.count() << detail::get_units<CharT>(duration<Rep, typename Period::type>{});Where
detail::get_units<CharT>()returns a null-terminated string ofCharTwhich depends only onPeriod::typeas follows (letperiodbe the typePeriod::type):
- If
periodis typestd::atto,as, else- if
periodis typestd::femto,fs, else- if
periodis typestd::pico,ps, else- if
periodis typestd::nano,ns, else- if
periodis typestd::micro,µs(U+00B5), else- if
periodis typestd::milli,ms, else- if
periodis typestd::centi,cs, else- if
periodis typestd::deci,ds, else- if
periodis typestd::ratio<1>,s, else- if
periodis typestd::deca,das, else- if
periodis typestd::hecto,hs, else- if
periodis typestd::kilo,ks, else- if
periodis typestd::mega,Ms, else- if
periodis typestd::giga,Gs, else- if
periodis typestd::tera,Ts, else- if
periodis typestd::peta,Ps, else- if
periodis typestd::exa,Es, else- if
periodis typestd::ratio<60>,min, else- if
periodis typestd::ratio<3600>,h, else- if
period::den == 1,[num]s, else[num/den]s.In the list above the use of
numanddenrefer to the static data members ofperiodwhich are converted to arrays ofCharTusing a decimal conversion with no leading zeroes.Returns:
os.