Document number: Dxxxx=xx-xxxx

Howard Hinnant
2011-05-17

chrono I/O

Introduction

This paper proposes I/O for durations and time_points. This I/O makes use of these types much more convenient. In following the "you only pay for what you use" philosophy, this extra functionality is located in a header separate from <chrono>, namely <chrono_io>.

To facilitate not only duration I/O, but the I/O of client-written types which may make use of ratio (e.g. a class representing length in meters), <ratio_io> is proposed which establishes textural representations of ratios. This textural representation uses SI prefixes whenever possible. This makes it easy for std::milliseconds to be represented by the text "milliseconds", or a hypothetical meter class to print out "millimeter".

system_clock::time_point I/O is proposed in terms of UTC timepoints, strongly guided by ISO 9899:1999, Programming languages — C, ISO 9945:2003, Information Technology — Portable Operating System Interface (POSIX) and ISO 8601:2004, Data elements and interchange formats — Information interchange — Representation of dates and times.

Finally this paper also proposes three rounding convenience functions for duration:

to be added to <chrono>.

Examples

duration

The following demonstrates duration output for several concrete duration types:

cout << nanoseconds(123) << '\n';               // 123 nanoseconds
cout << microseconds(456) << '\n';              // 456 microseconds
cout << milliseconds(789) << '\n';              // 789 milliseconds
cout << seconds(55) << '\n';                    // 55 seconds
cout << minutes(3) << '\n';                     // 3 minutes
cout << hours(24) << '\n';                      // 24 hours
typedef duration<long, ratio<2, 60>> Ticks;
cout << Ticks(44) << '\n';                      // 44 [1/30]seconds
typedef duration<long, kilo> kilosecond;
cout << kilosecond(2) << '\n';                  // 2 kiloseconds
typedef duration<double> dsec;
cout << dsec(3+1/3.) << '\n';                   // 3.33333 seconds

As can be seen, each duration type can be streamed without having to manually stream the compile-time units after the run-time value. And when the compile-time unit is known to be a "common unit", English names are used. For "uncommon units" a unit name is composed from the reduced numerator and denominator of the associated ratio. Whatever stream/locale settings are set for duration::rep are used for the value.

Sometimes it is desired to shorten these names by using the SI symbols instead of SI prefixes. This can be accomplished with the use of the duration_short manipulator:

cout << duration_fmt(symbol);
cout << nanoseconds(123) << '\n';               // 123 ns
cout << microseconds(456) << '\n';              // 456 µs
cout << milliseconds(789) << '\n';              // 789 ms
cout << seconds(55) << '\n';                    // 55 s
cout << minutes(3) << '\n';                     // 3 m
cout << hours(24) << '\n';                      // 24 h
typedef duration<long, ratio<2, 60>> Ticks;
cout << Ticks(44) << '\n';                      // 44 [1/30]s
typedef duration<long, kilo> kilosecond;
cout << kilosecond(2) << '\n';                  // 2 ks
typedef duration<double> dsec;
cout << dsec(3+1/3.) << '\n';                   // 3.33333 s

The µ for microsecond is specified to be U+00B5, encoded as UTF-8, UTF-16 or UTF-32 as appropriate for the stream's character size.

You can set the long form back with:

cout << duration_fmt(prefix);

durations can be read using the same format (short or long form). When parsing a duration, the duration type need only to be able to exactly represent the textual type for the parse to be successful. For example one can parse "789 milliseconds" into a duration of type microseconds. The corresponding value parsed will be 789,000. The rules mirror those already established for duration implicit conversions.

Sometimes in templated code it is difficult to know what the unit of your duration is. It is all deterministic, and inspectable. But it can be inconvenient to do so, especially if you just need to print out a "debugging" statement. For example:

// round to nearest, to even on tie
template <class To, class Rep, class Period>
To
round(const duration<Rep, Period>& d)
{
    To t0 = duration_cast<To>(d);
    To t1 = t0;
    ++t1;
    auto diff0 = d - t0;
    cout << "diff0 = " << diff0 << '\n';
    auto diff1 = t1 - d;
    cout << "diff1 = " << diff1 << '\n';
    if (diff0 == diff1)
    {
        if (t0.count() & 1)
            return t1;
        return t0;
    }
    else if (diff0 < diff1)
        return t0;
    return t1;
}

This is where I/O for duration really shines. The compiler knows what the type of diff0 is and with this proposal that type (with proper units) will automatically be printed out for you. For example:

milliseconds ms = round<milliseconds>(nanoseconds(123));  // diff0 = 123 nanoseconds
                                                          // diff1 = 999877 nanoseconds
milliseconds ms = round<milliseconds>(Ticks(44));         // diff0 = 2 [1/3000]seconds
                                                          // diff1 = 1 [1/3000]seconds

This simple I/O will make duration so much more accessible to programmers.

system_clock::time_point

system_clock is special. It is the only clock that has conversions between its time_point and time_t. C subsequently relates time_t to the Gregorian calendar via ctime, gmtime, localtime, and strftime. Neither C, nor POSIX relate time_t to any calendar other than the Gregorian calendar. ISO 8601 is specified only in terms of the Gregorian calendar.

This paper proposes system_clock::time_point I/O in terms of the Gregorian calendar, and no other calendar. However as system_clock::time_point remains convertible with time_t, it is possible for clients to create other calendars which interoperate with time_t and subsequently system_clock::time_point.

Furthermore, it is existing practice for all major hosted operating systems to store system time in a format which facilitates display as Coordinated Universal Time (UTC). Therefore this paper proposes that the default output for system_clock::time_point be in a format that represents a point in time with respect to UTC.

cout << system_clock::now() << '\n';  // 2011-04-24 18:36:59.325132 +0000

This format is strongly influenced by ISO 8601, but places a ' ' between the date and time instead of a 'T'. The former appears to more accurately represent existing practice. A fully numeric format was chosen so as to be understandable to as large a group of human readers as possible. A 24 hour format was chosen for the same reasons.

Of the referenced standards, only ISO 8601 discusses the output of fractional seconds. Neither C nor POSIX have built-in functionality for this. However it appears to be universal (as of this writing) that system_clock::period is sub-second. And it seems desirable that if you stream out a system_clock::time_point, you ought to be able to stream it back in and get the same value. Therefore the streaming of fractional seconds (at least by default) appears to be unavoidable.

Finally the trailing " +0000" disambiguates the UTC-formatted system_clock::time_point from one formatted with respect to the local time zone of the computer. The latter can easily be achieved with:

cout << time_fmt(local) << system_clock::now() << '\n';  // 2011-04-24 14:36:59.325132 -0400

Note that system_clock::time_point itself is neither UTC, nor the local time. Exactly what it is, is unspecified, except to the extent we have already specified it in C++11. However in practice, system_clock::time_point is a count of ticks beyond some epoch which is synchronized with UTC. So as a mobile computer moves across time zones, the time zone traversal does not impact the value of a system_clock::time_point produced by system_clock::now(). And it is only in formatting it for human consumption that one can choose UTC or the local time zone. C and POSIX treat time_t just as this paper proposes we treat system_clock::time_point:

This proposal simply extends the C/POSIX time_t functionality to C++ syntax and system_clock::time_point.

The time_fmt() manipulator is "sticky". It will remain in effect until the stream destructs or until it is changed. The stream can be reset to its default state with:

cout << time_fmt(utc);

And the formatting can be further customized by using the time format sequences as specified in [locale.time.put.members]. For example:

cout << time_fmt(local, "%A %B %e, %Y %r");
cout << system_clock::now() << '\n';  // Sunday April 24, 2011 02:36:59 PM

When specifying formatting manipulators for wide streams, use wide strings.

You can use the same manipulators with istreams to specify parsing sequences as specified in [locale.time.get.members].

Unfortunately there are no formatting/parsing sequences which indicate fractional seconds. This paper does not propose such sequences, but also does not object to adding such sequences to [locale.time.get.members] and [locale.time.put.members]. This is a separable issue and should be treated as such. In the meantime, one can format and parse fractional seconds for system_clock::time_point by defaulting the format, or by using an empty string in time_fmt().

As already specified in [category.time], the stream's current locale may impact the parsing/format sequences supplied to the system_clock::time_point manipulators (e.g. names of days of the week, and names of months).

steady_clock::time_point

Unlike system_clock::time_point, steady_clock::time_point has no conversion with time_t. There is likely no relationship between steady_clock::time_point and UTC at all (UTC is not steady). On Apple's OS, steady_clock::time_point is simply a count of nanoseconds since the computer booted up.

The formatting for steady_clock::time_point is unspecified but should be sufficiently descriptive for a human to understand. One should also be able to parse back in the same string, and get the same value back.

Example:

cout << steady_clock::now() << '\n';  // 22644271279698 nanoseconds since boot

high_resolution_clock::time_point

high_resolution_clock may be an alias for either system_clock or steady_clock. If it is, its I/O will obviously reflect that fact. If it is not an alias, its formatting and parsing is unspecified, but should follow the style for either system_clock or steady_clock.

Example Implementation

There exists an example implementation here for <chrono_io> and <ratio_io>.

Proposed Wording

Text formatted like this is intended to be rationale for some of the proposed wording. It is not part of the proposed wording.

Header <ratio_io> synopsis

namespace std {

#include <ratio>
#include <string>

template <class Ratio, class charT> struct ratio_string;

template <> struct ratio_string<yocto, char>;      // conditionally supported
template <> struct ratio_string<yocto, char16_t>;  // conditionally supported
template <> struct ratio_string<yocto, char32_t>;  // conditionally supported
template <> struct ratio_string<yocto, wchar_t>;   // conditionally supported

template <> struct ratio_string<zepto, char>;      // conditionally supported
template <> struct ratio_string<zepto, char16_t>;  // conditionally supported
template <> struct ratio_string<zepto, char32_t>;  // conditionally supported
template <> struct ratio_string<zepto, wchar_t>;   // conditionally supported

template <> struct ratio_string<atto, char>;
template <> struct ratio_string<atto, char16_t>;
template <> struct ratio_string<atto, char32_t>;
template <> struct ratio_string<atto, wchar_t>;

template <> struct ratio_string<femto, char>;
template <> struct ratio_string<femto, char16_t>;
template <> struct ratio_string<femto, char32_t>;
template <> struct ratio_string<femto, wchar_t>;

template <> struct ratio_string<pico, char>;
template <> struct ratio_string<pico, char16_t>;
template <> struct ratio_string<pico, char32_t>;
template <> struct ratio_string<pico, wchar_t>;

template <> struct ratio_string<nano, char>;
template <> struct ratio_string<nano, char16_t>;
template <> struct ratio_string<nano, char32_t>;
template <> struct ratio_string<nano, wchar_t>;

template <> struct ratio_string<micro, char>;
template <> struct ratio_string<micro, char16_t>;
template <> struct ratio_string<micro, char32_t>;
template <> struct ratio_string<micro, wchar_t>;

template <> struct ratio_string<milli, char>;
template <> struct ratio_string<milli, char16_t>;
template <> struct ratio_string<milli, char32_t>;
template <> struct ratio_string<milli, wchar_t>;

template <> struct ratio_string<centi, char>;
template <> struct ratio_string<centi, char16_t>;
template <> struct ratio_string<centi, char32_t>;
template <> struct ratio_string<centi, wchar_t>;

template <> struct ratio_string<deci, char>;
template <> struct ratio_string<deci, char16_t>;
template <> struct ratio_string<deci, char32_t>;
template <> struct ratio_string<deci, wchar_t>;

template <> struct ratio_string<deca, char>;
template <> struct ratio_string<deca, char16_t>;
template <> struct ratio_string<deca, char32_t>;
template <> struct ratio_string<deca, wchar_t>;

template <> struct ratio_string<hecto, char>;
template <> struct ratio_string<hecto, char16_t>;
template <> struct ratio_string<hecto, char32_t>;
template <> struct ratio_string<hecto, wchar_t>;

template <> struct ratio_string<kilo, char>;
template <> struct ratio_string<kilo, char16_t>;
template <> struct ratio_string<kilo, char32_t>;
template <> struct ratio_string<kilo, wchar_t>;

template <> struct ratio_string<mega, char>;
template <> struct ratio_string<mega, char16_t>;
template <> struct ratio_string<mega, char32_t>;
template <> struct ratio_string<mega, wchar_t>;

template <> struct ratio_string<giga, char>;
template <> struct ratio_string<giga, char16_t>;
template <> struct ratio_string<giga, char32_t>;
template <> struct ratio_string<giga, wchar_t>;

template <> struct ratio_string<tera, char>;
template <> struct ratio_string<tera, char16_t>;
template <> struct ratio_string<tera, char32_t>;
template <> struct ratio_string<tera, wchar_t>;

template <> struct ratio_string<peta, char>;
template <> struct ratio_string<peta, char16_t>;
template <> struct ratio_string<peta, char32_t>;
template <> struct ratio_string<peta, wchar_t>;

template <> struct ratio_string<exa, char>;
template <> struct ratio_string<exa, char16_t>;
template <> struct ratio_string<exa, char32_t>;
template <> struct ratio_string<exa, wchar_t>;

template <> struct ratio_string<zetta, char>;      // conditionally supported
template <> struct ratio_string<zetta, char16_t>;  // conditionally supported
template <> struct ratio_string<zetta, char32_t>;  // conditionally supported
template <> struct ratio_string<zetta, wchar_t>;   // conditionally supported

template <> struct ratio_string<yotta, char>;      // conditionally supported
template <> struct ratio_string<yotta, char16_t>;  // conditionally supported
template <> struct ratio_string<yotta, char32_t>;  // conditionally supported
template <> struct ratio_string<yotta, wchar_t>;   // conditionally supported

}  // std

Class template <ratio_io>

namespace std {

  template <class Ratio, class charT>
  struct ratio_string
  {
      static basic_string<charT> prefix();
      static basic_string<charT> symbol();
  };

}  // std

The class template ratio_string provides textual representations of the associated ratio appropriate for the character type charT.

The primary template provides generic strings. Specializations provide the same static member functions but these functions return the English SI prefix and symbol names as specified by the General Conference on Weights and Measures.

template<class Ratio, class charT>
basic_string<charT>
ratio_string<Ratio, charT>::prefix();

Returns: A basic_string of the form: [Ratio::num/Ratio::den]

Example: ratio_string<ratio<2, 60>, wchar_t>::prefix() returns L"[1/30]".

template<class Ratio, class charT>
basic_string<charT>
ratio_string<Ratio, charT>::symbol();

Returns: prefix().

For each specialization the table gives the return value for prefix() and symbol().

The return values of specializations of ratio_string
Specializationprefix()symbol()
ratio_string<atto, char>
ratio_string<atto, char16_t>
ratio_string<atto, char32_t>
ratio_string<atto, wchar_t>
"atto"
u"atto"
U"atto"
L"atto"
"a"
u"a"
U"a"
L"a"
ratio_string<femto, char>
ratio_string<femto, char16_t>
ratio_string<femto, char32_t>
ratio_string<femto, wchar_t>
"femto"
u"femto"
U"femto"
L"femto"
"f"
u"f"
U"f"
L"f"
ratio_string<pico, char>
ratio_string<pico, char16_t>
ratio_string<pico, char32_t>
ratio_string<pico, wchar_t>
"pico"
u"pico"
U"pico"
L"pico"
"p"
u"p"
U"p"
L"p"
ratio_string<nano, char>
ratio_string<nano, char16_t>
ratio_string<nano, char32_t>
ratio_string<nano, wchar_t>
"nano"
u"nano"
U"nano"
L"nano"
"n"
u"n"
U"n"
L"n"
ratio_string<micro, char>
ratio_string<micro, char16_t>
ratio_string<micro, char32_t>
ratio_string<micro, wchar_t>
"micro"
u"micro"
U"micro"
L"micro"
u8"\u00B5"
u"\u00B5"
U"\u00B5"
L"\u00B5"
ratio_string<milli, char>
ratio_string<milli, char16_t>
ratio_string<milli, char32_t>
ratio_string<milli, wchar_t>
"milli"
u"milli"
U"milli"
L"milli"
"m"
u"m"
U"m"
L"m"
ratio_string<centi, char>
ratio_string<centi, char16_t>
ratio_string<centi, char32_t>
ratio_string<centi, wchar_t>
"centi"
u"centi"
U"centi"
L"centi"
"c"
u"c"
U"c"
L"c"
ratio_string<deci, char>
ratio_string<deci, char16_t>
ratio_string<deci, char32_t>
ratio_string<deci, wchar_t>
"deci"
u"deci"
U"deci"
L"deci"
"d"
u"d"
U"d"
L"d"
ratio_string<deca, char>
ratio_string<deca, char16_t>
ratio_string<deca, char32_t>
ratio_string<deca, wchar_t>
"deca"
u"deca"
U"deca"
L"deca"
"da"
u"da"
U"da"
L"da"
ratio_string<hecto, char>
ratio_string<hecto, char16_t>
ratio_string<hecto, char32_t>
ratio_string<hecto, wchar_t>
"hecto"
u"hecto"
U"hecto"
L"hecto"
"h"
u"h"
U"h"
L"h"
ratio_string<kilo, char>
ratio_string<kilo, char16_t>
ratio_string<kilo, char32_t>
ratio_string<kilo, wchar_t>
"kilo"
u"kilo"
U"kilo"
L"kilo"
"k"
u"k"
U"k"
L"k"
ratio_string<mega, char>
ratio_string<mega, char16_t>
ratio_string<mega, char32_t>
ratio_string<mega, wchar_t>
"mega"
u"mega"
U"mega"
L"mega"
"M"
u"M"
U"M"
L"M"
ratio_string<giga, char>
ratio_string<giga, char16_t>
ratio_string<giga, char32_t>
ratio_string<giga, wchar_t>
"giga"
u"giga"
U"giga"
L"giga"
"G"
u"G"
U"G"
L"G"
ratio_string<tera, char>
ratio_string<tera, char16_t>
ratio_string<tera, char32_t>
ratio_string<tera, wchar_t>
"tera"
u"tera"
U"tera"
L"tera"
"T"
u"T"
U"T"
L"T"
ratio_string<peta, char>
ratio_string<peta, char16_t>
ratio_string<peta, char32_t>
ratio_string<peta, wchar_t>
"peta"
u"peta"
U"peta"
L"peta"
"P"
u"P"
U"P"
L"P"
ratio_string<exa, char>
ratio_string<exa, char16_t>
ratio_string<exa, char32_t>
ratio_string<exa, wchar_t>
"exa"
u"exa"
U"exa"
L"exa"
"E"
u"E"
U"E"
L"E"

For each of the conditionally supported specializations the table gives the return value for prefix() and symbol(). If the corresponding ratio is supported, then the ratio_string is supported. Else it is not.

Conditionally supported specializations of ratio_string
Specializationprefix()symbol()
ratio_string<yocto, char>
ratio_string<yocto, char16_t>
ratio_string<yocto, char32_t>
ratio_string<yocto, wchar_t>
"yocto"
u"yocto"
U"yocto"
L"yocto"
"y"
u"y"
U"y"
L"y"
ratio_string<zepto, char>
ratio_string<zepto, char16_t>
ratio_string<zepto, char32_t>
ratio_string<zepto, wchar_t>
"zepto"
u"zepto"
U"zepto"
L"zepto"
"z"
u"z"
U"z"
L"z"
ratio_string<zetta, char>
ratio_string<zetta, char16_t>
ratio_string<zetta, char32_t>
ratio_string<zetta, wchar_t>
"zetta"
u"zetta"
U"zetta"
L"zetta"
"Z"
u"Z"
U"Z"
L"Z"
ratio_string<yotta, char>
ratio_string<yotta, char16_t>
ratio_string<yotta, char32_t>
ratio_string<yotta, wchar_t>
"yotta"
u"yotta"
U"yotta"
L"yotta"
"Y"
u"Y"
U"Y"
L"Y"

Header <chrono_io> synopsis

#include <chrono>
#include <ratio_io>

namespace std {
namespace chrono {

enum {prefix, symbol};
enum {utc, local};

// facets

template <class charT> class durationpunct;
template <class charT> class timepunct;

// manipulators

class duration_fmt;
unspecified time_fmt(int f);
unspecified time_fmt(int f, string fmt);
unspecified time_fmt(int f, wstring fmt);

template<class charT, class traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, duration_fmt d);

template<class charT, class traits>
std::basic_istream<charT, traits>&
operator>>(std::basic_istream<charT, traits>& is, duration_fmt d);

// duration I/O

template <class charT, class traits, class Rep, class Period>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, const duration<Rep, Period>& d);

template <class charT, class traits, class Rep, class Period>
basic_istream<charT, traits>&
operator>>(basic_istream<charT, traits>& is, duration<Rep, Period>& d);

// system_clock I/O

template <class charT, class traits, class Duration>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os,
           const time_point<system_clock, Duration>& tp);

template <class charT, class traits, class Duration>
basic_istream<charT, traits>&
operator>>(basic_istream<charT, traits>& is,
           time_point<system_clock, Duration>& tp);

// steady_clock I/O

template <class charT, class traits, class Duration>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os,
           const time_point<steady_clock, Duration>& tp);

template <class charT, class traits, class Duration>
basic_istream<charT, traits>&
operator>>(basic_istream<charT, traits>& is,
           time_point<steady_clock, Duration>& tp);

// high_resolution_clock I/O

template <class charT, class traits, class Duration>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os,
           const time_point<high_resolution_clock, Duration>& tp);

template <class charT, class traits, class Duration>
basic_istream<charT, traits>&
operator>>(basic_istream<charT, traits>& is,
           time_point<high_resolution_clock, Duration>& tp);

}  // chrono
}  // std