Unify and simplify fractional decimal seconds formatting

* Many of the ideas and some of the code herein is credited to
  Adrian Colomitchi

* Decouple fractional decimal seconds formatting from time_of_day
  formatting so that it can be more easily used elsewhere in the
  future.

* Include super-second durations such as nanocenturies and
  microfortnights in the class of durations that will get formatted
  with fractional decimal seconds.

* If a duration is exactly representable with fractional decimal
  seconds not exceeding 18 decimal fractional digits, format it
  exactly.  Otherwise format it to 6 fractional decimal digits
  (microseconds precision).  The number 18 is chosen as this is the
  limit of std::ratio using 64 bits (i.e. atto).

* The above bullet implies that durations with ratio<1, 4> will now
  be formatted with 2 fractional decimal digits instead of 1.
  ratio<1, 8> will be formatted with 3, and ratio<1, 3> with 6.

* Unify the implementation into one C++11 implementation that works
  equally well with C++14.

* Drive-by fix a couple formatting bugs dealing with negative
  durations.

* Deprecate the make_time functions taking unsigned md by removing
  their documentation.  Also deprecate the corresponding time_of_day
  constructors taking unsigned md.

* This change paves the way for future formatting improvements.
This commit is contained in:
Howard Hinnant
2016-11-24 19:43:14 -05:00
parent 44e0480087
commit 0cfa783b14

300
date.h
View File

@ -4,6 +4,7 @@
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) 2015, 2016 Howard Hinnant // Copyright (c) 2015, 2016 Howard Hinnant
// Copyright (c) 2016 Adrian Colomitchi
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -3383,6 +3384,130 @@ enum {am = 1, pm};
namespace detail namespace detail
{ {
// width<n>::value is the number of fractional decimal digits in 1/n
// width<0>::value and width<1>::value are defined to be 0
// If 1/n takes more than 18 fractional decimal digits,
// the result is truncated to 19.
// Example: width<2>::value == 1
// Example: width<3>::value == 19
// Example: width<4>::value == 2
// Example: width<10>::value == 1
// Example: width<1000>::value == 3
template <std::uint64_t n, std::uint64_t d = 10, unsigned w = 0,
bool should_continue = !(n < 2) && d != 0 && w < 19>
struct width
{
static CONSTDATA unsigned value = 1 + width<n, d%n*10, w+1>::value;
};
template <std::uint64_t n, std::uint64_t d, unsigned w>
struct width<n, d, w, false>
{
static CONSTDATA unsigned value = 0;
};
template <unsigned exp>
struct static_pow10
{
private:
static CONSTDATA std::uint64_t h = static_pow10<exp/2>::value;
public:
static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1);
};
template <>
struct static_pow10<0>
{
static CONSTDATA std::uint64_t value = 1;
};
template <unsigned w, bool in_range = w < 19>
struct make_precision
{
using type = std::chrono::duration<std::int64_t,
std::ratio<1, static_pow10<w>::value>>;
static CONSTDATA unsigned width = w;
};
template <unsigned w>
struct make_precision<w, false>
{
using type = std::chrono::microseconds;
static CONSTDATA unsigned width = 6;
};
template <class Duration, unsigned w = width<Duration::period::den>::value>
class decimal_format_seconds
{
public:
using precision = typename make_precision<w>::type;
static auto CONSTDATA width = make_precision<w>::width;
private:
std::chrono::seconds s_;
precision sub_s_;
public:
CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT
: s_(std::chrono::duration_cast<std::chrono::seconds>(d))
, sub_s_(std::chrono::duration_cast<precision>(d - s_))
{}
CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;}
CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;}
CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;}
CONSTCD14 precision to_duration() const NOEXCEPT
{
return s_ + sub_s_;
}
template <class CharT, class Traits>
friend
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, const decimal_format_seconds& x)
{
date::detail::save_stream<CharT, Traits> _(os);
os.fill('0');
os.flags(std::ios::dec | std::ios::right);
os.width(2);
os << x.s_.count() <<
std::use_facet<std::numpunct<char>>(os.getloc()).decimal_point();
os.width(width);
os << x.sub_s_.count();
return os;
}
};
template <class Duration>
class decimal_format_seconds<Duration, 0>
{
static CONSTDATA unsigned w = 0;
public:
using precision = std::chrono::seconds;
private:
std::chrono::seconds s_;
public:
CONSTCD11 explicit decimal_format_seconds(const precision& s) NOEXCEPT
: s_(s)
{}
template <class CharT, class Traits>
friend
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os, const decimal_format_seconds& x)
{
date::detail::save_stream<CharT, Traits> _(os);
os.fill('0');
os.flags(std::ios::dec | std::ios::right);
os.width(2);
os << x.s_.count();
return os;
}
};
enum class classify enum class classify
{ {
not_valid, not_valid,
@ -3392,57 +3517,34 @@ enum class classify
subsecond subsecond
}; };
#if !defined(_MSC_VER) || (_MSC_VER >= 1900)
template <class Duration> template <class Duration>
struct classify_duration struct classify_duration
{ {
static CONSTDATA classify value = static CONSTDATA classify value =
Duration{1} >= days{1} ? classify::not_valid : std::is_convertible<Duration, std::chrono::hours>::value
Duration{1} >= std::chrono::hours{1} ? classify::hour :
Duration{1} >= std::chrono::minutes{1} ? classify::minute :
Duration{1} >= std::chrono::seconds{1} ? classify::second :
classify::subsecond;
};
#else
template <class Duration>
struct classify_duration
{
static CONSTDATA classify value =
std::ratio_greater_equal<
typename Duration::period,
days::period >::value
? classify::not_valid :
std::ratio_greater_equal<
typename Duration::period,
std::chrono::hours::period>::value
? classify::hour : ? classify::hour :
std::ratio_greater_equal< std::is_convertible<Duration, std::chrono::minutes>::value
typename Duration::period,
std::chrono::minutes::period>::value
? classify::minute : ? classify::minute :
std::ratio_greater_equal< std::is_convertible<Duration, std::chrono::seconds>::value
typename Duration::period,
std::chrono::seconds::period>::value
? classify::second : ? classify::second :
std::chrono::treat_as_floating_point<typename Duration::rep>::value
? classify::not_valid :
classify::subsecond; classify::subsecond;
}; };
#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900)
class time_of_day_base class time_of_day_base
{ {
protected: protected:
std::chrono::hours h_; std::chrono::hours h_;
unsigned char mode_; unsigned char mode_;
bool neg_;
enum {is24hr}; enum {is24hr};
CONSTCD11 time_of_day_base(std::chrono::hours h, unsigned m) NOEXCEPT CONSTCD11 time_of_day_base(std::chrono::hours h, unsigned m) NOEXCEPT
: h_(h) : h_(abs(h))
, mode_(static_cast<decltype(mode_)>(m)) , mode_(static_cast<decltype(mode_)>(m))
, neg_(h < std::chrono::hours{0})
{} {}
CONSTCD14 void make24() NOEXCEPT; CONSTCD14 void make24() NOEXCEPT;
@ -3528,7 +3630,10 @@ public:
CONSTCD14 explicit operator precision() const NOEXCEPT CONSTCD14 explicit operator precision() const NOEXCEPT
{ {
return to24hr(); auto p = to24hr();
if (neg_)
p = -p;
return p;
} }
CONSTCD14 precision to_duration() const NOEXCEPT CONSTCD14 precision to_duration() const NOEXCEPT
@ -3546,6 +3651,8 @@ public:
{ {
using namespace std; using namespace std;
detail::save_stream<CharT, Traits> _(os); detail::save_stream<CharT, Traits> _(os);
if (t.neg_)
os << '-';
os.fill('0'); os.fill('0');
os.flags(std::ios::dec | std::ios::right); os.flags(std::ios::dec | std::ios::right);
if (t.mode_ != am && t.mode_ != pm) if (t.mode_ != am && t.mode_ != pm)
@ -3580,7 +3687,7 @@ public:
CONSTCD11 explicit time_of_day_storage(std::chrono::minutes since_midnight) NOEXCEPT CONSTCD11 explicit time_of_day_storage(std::chrono::minutes since_midnight) NOEXCEPT
: base(std::chrono::duration_cast<std::chrono::hours>(since_midnight), is24hr) : base(std::chrono::duration_cast<std::chrono::hours>(since_midnight), is24hr)
, m_(since_midnight - h_) , m_(abs(since_midnight) - h_)
{} {}
CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m,
@ -3595,7 +3702,10 @@ public:
CONSTCD14 explicit operator precision() const NOEXCEPT CONSTCD14 explicit operator precision() const NOEXCEPT
{ {
return to24hr() + m_; auto p = to24hr() + m_;
if (neg_)
p = -p;
return p;
} }
CONSTCD14 precision to_duration() const NOEXCEPT CONSTCD14 precision to_duration() const NOEXCEPT
@ -3613,15 +3723,15 @@ public:
{ {
using namespace std; using namespace std;
detail::save_stream<CharT, Traits> _(os); detail::save_stream<CharT, Traits> _(os);
if (static_cast<precision>(t) < std::chrono::hours{0}) if (t.neg_)
os << '-'; os << '-';
os.fill('0'); os.fill('0');
os.flags(std::ios::dec | std::ios::right); os.flags(std::ios::dec | std::ios::right);
if (t.mode_ != am && t.mode_ != pm) if (t.mode_ != am && t.mode_ != pm)
os.width(2); os.width(2);
os << std::abs(t.h_.count()) << ':'; os << t.h_.count() << ':';
os.width(2); os.width(2);
os << std::abs(t.m_.count()); os << t.m_.count();
switch (t.mode_) switch (t.mode_)
{ {
case am: case am:
@ -3649,8 +3759,8 @@ public:
CONSTCD11 explicit time_of_day_storage(std::chrono::seconds since_midnight) NOEXCEPT CONSTCD11 explicit time_of_day_storage(std::chrono::seconds since_midnight) NOEXCEPT
: base(std::chrono::duration_cast<std::chrono::hours>(since_midnight), is24hr) : base(std::chrono::duration_cast<std::chrono::hours>(since_midnight), is24hr)
, m_(std::chrono::duration_cast<std::chrono::minutes>(since_midnight - h_)) , m_(std::chrono::duration_cast<std::chrono::minutes>(abs(since_midnight) - h_))
, s_(since_midnight - h_ - m_) , s_(abs(since_midnight) - h_ - m_)
{} {}
CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m,
@ -3668,7 +3778,10 @@ public:
CONSTCD14 explicit operator precision() const NOEXCEPT CONSTCD14 explicit operator precision() const NOEXCEPT
{ {
return to24hr() + s_ + m_; auto p = to24hr() + s_ + m_;
if (neg_)
p = -p;
return p;
} }
CONSTCD14 precision to_duration() const NOEXCEPT CONSTCD14 precision to_duration() const NOEXCEPT
@ -3686,17 +3799,17 @@ public:
{ {
using namespace std; using namespace std;
detail::save_stream<CharT, Traits> _(os); detail::save_stream<CharT, Traits> _(os);
if (static_cast<precision>(t) < std::chrono::hours{0}) if (t.neg_)
os << '-'; os << '-';
os.fill('0'); os.fill('0');
os.flags(std::ios::dec | std::ios::right); os.flags(std::ios::dec | std::ios::right);
if (t.mode_ != am && t.mode_ != pm) if (t.mode_ != am && t.mode_ != pm)
os.width(2); os.width(2);
os << std::abs(t.h_.count()) << ':'; os << t.h_.count() << ':';
os.width(2); os.width(2);
os << std::abs(t.m_.count()) << ':'; os << t.m_.count() << ':';
os.width(2); os.width(2);
os << std::abs(t.s_.count()); os << t.s_.count();
switch (t.mode_) switch (t.mode_)
{ {
case am: case am:
@ -3715,21 +3828,21 @@ class time_of_day_storage<std::chrono::duration<Rep, Period>, detail::classify::
: private detail::time_of_day_base : private detail::time_of_day_base
{ {
public: public:
using precision = std::chrono::duration<Rep, Period>; using Duration = std::chrono::duration<Rep, Period>;
using dfs = decimal_format_seconds<Duration>;
using precision = typename dfs::precision;
private: private:
using base = detail::time_of_day_base; using base = detail::time_of_day_base;
std::chrono::minutes m_; std::chrono::minutes m_;
std::chrono::seconds s_; dfs s_;
precision sub_s_;
public: public:
CONSTCD11 explicit time_of_day_storage(precision since_midnight) NOEXCEPT CONSTCD11 explicit time_of_day_storage(Duration since_midnight) NOEXCEPT
: base(std::chrono::duration_cast<std::chrono::hours>(since_midnight), is24hr) : base(std::chrono::duration_cast<std::chrono::hours>(since_midnight), is24hr)
, m_(std::chrono::duration_cast<std::chrono::minutes>(since_midnight - h_)) , m_(std::chrono::duration_cast<std::chrono::minutes>(abs(since_midnight) - h_))
, s_(std::chrono::duration_cast<std::chrono::seconds>(since_midnight - h_ - m_)) , s_(abs(since_midnight) - h_ - m_)
, sub_s_(since_midnight - h_ - m_ - s_)
{} {}
CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m,
@ -3737,20 +3850,22 @@ public:
unsigned md) NOEXCEPT unsigned md) NOEXCEPT
: base(h, md) : base(h, md)
, m_(m) , m_(m)
, s_(s) , s_(s + sub_s)
, sub_s_(sub_s)
{} {}
CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;} CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;}
CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;} CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;}
CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_;} CONSTCD14 std::chrono::seconds& seconds() NOEXCEPT {return s_.seconds();}
CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_;} CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();}
CONSTCD11 precision subseconds() const NOEXCEPT {return sub_s_;} CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();}
CONSTCD11 unsigned mode() const NOEXCEPT {return mode_;} CONSTCD11 unsigned mode() const NOEXCEPT {return mode_;}
CONSTCD14 explicit operator precision() const NOEXCEPT CONSTCD14 explicit operator precision() const NOEXCEPT
{ {
return to24hr() + s_ + sub_s_ + m_; auto p = to24hr() + s_.to_duration() + m_;
if (neg_)
p = -p;
return p;
} }
CONSTCD14 precision to_duration() const NOEXCEPT CONSTCD14 precision to_duration() const NOEXCEPT
@ -3768,33 +3883,15 @@ public:
{ {
using namespace std; using namespace std;
detail::save_stream<CharT, Traits> _(os); detail::save_stream<CharT, Traits> _(os);
if (static_cast<precision>(t) < std::chrono::hours{0}) if (t.neg_)
os << '-'; os << '-';
os.fill('0'); os.fill('0');
os.flags(std::ios::dec | std::ios::right); os.flags(std::ios::dec | std::ios::right);
if (t.mode_ != am && t.mode_ != pm) if (t.mode_ != am && t.mode_ != pm)
os.width(2); os.width(2);
os << std::abs(t.h_.count()) << ':'; os << t.h_.count() << ':';
os.width(2); os.width(2);
os << std::abs(t.m_.count()) << ':'; os << t.m_.count() << ':' << t.s_;
os.width(2);
os << std::abs(t.s_.count())
<< use_facet<numpunct<char>>(os.getloc()).decimal_point();
os.imbue(locale{});
#if __cplusplus >= 201402
CONSTDATA auto cl10 = ceil_log10(Period::den);
using scale = std::ratio_multiply<Period, std::ratio<pow10(cl10)>>;
os.width(cl10);
os << std::abs(t.sub_s_.count()) * scale::num / scale::den;
#else // __cplusplus >= 201402
// inefficient sub-optimal run-time mess, but gets the job done
const unsigned long long cl10 =
static_cast<unsigned long long>(std::ceil(log10(Period::den)));
const auto p10 = std::pow(10., cl10);
os.width(cl10);
os << static_cast<unsigned long long>(std::abs(t.sub_s_.count())
* Period::num * p10 / Period::den);
#endif // __cplusplus >= 201402
switch (t.mode_) switch (t.mode_)
{ {
case am: case am:
@ -3806,50 +3903,6 @@ public:
} }
return os; return os;
} }
private:
#if __cplusplus >= 201402
CONSTCD11 static int ceil_log10(unsigned long long i) NOEXCEPT
{
--i;
int n = 0;
if (i >= 10000000000000000) {i /= 10000000000000000; n += 16;}
if (i >= 100000000) {i /= 100000000; n += 8;}
if (i >= 10000) {i /= 10000; n += 4;}
if (i >= 100) {i /= 100; n += 2;}
if (i >= 10) {i /= 10; n += 1;}
if (i >= 1) {i /= 10; n += 1;}
return n;
}
CONSTCD11 static unsigned long long pow10(unsigned y) NOEXCEPT
{
CONSTDATA unsigned long long p10[] =
{
1ull,
10ull,
100ull,
1000ull,
10000ull,
100000ull,
1000000ull,
10000000ull,
100000000ull,
1000000000ull,
10000000000ull,
100000000000ull,
1000000000000ull,
10000000000000ull,
100000000000000ull,
1000000000000000ull,
10000000000000000ull,
100000000000000000ull,
1000000000000000000ull,
10000000000000000000ull
};
return p10[y];
}
#endif // __cplusplus >= 201402
}; };
} // namespace detail } // namespace detail
@ -3866,7 +3919,8 @@ public:
#else #else
// MS cl compiler workaround. // MS cl compiler workaround.
template <class ...Args> template <class ...Args>
explicit time_of_day(Args&& ...args) CONSTCD11
explicit time_of_day(Args&& ...args) NOEXCEPT
: base(std::forward<Args>(args)...) : base(std::forward<Args>(args)...)
{} {}
#endif #endif