From 0cfa783b148e5ed25690d1276bda3c33aec7f61c Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Thu, 24 Nov 2016 19:43:14 -0500 Subject: [PATCH] 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. --- date.h | 300 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 177 insertions(+), 123 deletions(-) diff --git a/date.h b/date.h index 5ff8f01..784bd42 100644 --- a/date.h +++ b/date.h @@ -4,6 +4,7 @@ // The MIT License (MIT) // // Copyright (c) 2015, 2016 Howard Hinnant +// Copyright (c) 2016 Adrian Colomitchi // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -3383,6 +3384,130 @@ enum {am = 1, pm}; namespace detail { +// width::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 +struct width +{ + static CONSTDATA unsigned value = 1 + width::value; +}; + +template +struct width +{ + static CONSTDATA unsigned value = 0; +}; + +template +struct static_pow10 +{ +private: + static CONSTDATA std::uint64_t h = static_pow10::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 +struct make_precision +{ + using type = std::chrono::duration::value>>; + static CONSTDATA unsigned width = w; +}; + +template +struct make_precision +{ + using type = std::chrono::microseconds; + static CONSTDATA unsigned width = 6; +}; + +template ::value> +class decimal_format_seconds +{ +public: + using precision = typename make_precision::type; + static auto CONSTDATA width = make_precision::width; + +private: + std::chrono::seconds s_; + precision sub_s_; + +public: + CONSTCD11 explicit decimal_format_seconds(const Duration& d) NOEXCEPT + : s_(std::chrono::duration_cast(d)) + , sub_s_(std::chrono::duration_cast(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 + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const decimal_format_seconds& x) + { + date::detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << x.s_.count() << + std::use_facet>(os.getloc()).decimal_point(); + os.width(width); + os << x.sub_s_.count(); + return os; + } +}; + +template +class decimal_format_seconds +{ + 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 + friend + std::basic_ostream& + operator<<(std::basic_ostream& os, const decimal_format_seconds& x) + { + date::detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << x.s_.count(); + return os; + } +}; + enum class classify { not_valid, @@ -3392,57 +3517,34 @@ enum class classify subsecond }; -#if !defined(_MSC_VER) || (_MSC_VER >= 1900) - template struct classify_duration { static CONSTDATA classify value = - Duration{1} >= days{1} ? classify::not_valid : - 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 -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 + std::is_convertible::value ? classify::hour : - std::ratio_greater_equal< - typename Duration::period, - std::chrono::minutes::period>::value + std::is_convertible::value ? classify::minute : - std::ratio_greater_equal< - typename Duration::period, - std::chrono::seconds::period>::value + std::is_convertible::value ? classify::second : + std::chrono::treat_as_floating_point::value + ? classify::not_valid : classify::subsecond; }; -#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) - class time_of_day_base { protected: std::chrono::hours h_; unsigned char mode_; + bool neg_; enum {is24hr}; CONSTCD11 time_of_day_base(std::chrono::hours h, unsigned m) NOEXCEPT - : h_(h) + : h_(abs(h)) , mode_(static_cast(m)) + , neg_(h < std::chrono::hours{0}) {} CONSTCD14 void make24() NOEXCEPT; @@ -3528,7 +3630,10 @@ public: CONSTCD14 explicit operator precision() const NOEXCEPT { - return to24hr(); + auto p = to24hr(); + if (neg_) + p = -p; + return p; } CONSTCD14 precision to_duration() const NOEXCEPT @@ -3546,6 +3651,8 @@ public: { using namespace std; detail::save_stream _(os); + if (t.neg_) + os << '-'; os.fill('0'); os.flags(std::ios::dec | std::ios::right); if (t.mode_ != am && t.mode_ != pm) @@ -3580,7 +3687,7 @@ public: CONSTCD11 explicit time_of_day_storage(std::chrono::minutes since_midnight) NOEXCEPT : base(std::chrono::duration_cast(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, @@ -3595,7 +3702,10 @@ public: 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 @@ -3613,15 +3723,15 @@ public: { using namespace std; detail::save_stream _(os); - if (static_cast(t) < std::chrono::hours{0}) + if (t.neg_) os << '-'; os.fill('0'); os.flags(std::ios::dec | std::ios::right); if (t.mode_ != am && t.mode_ != pm) os.width(2); - os << std::abs(t.h_.count()) << ':'; + os << t.h_.count() << ':'; os.width(2); - os << std::abs(t.m_.count()); + os << t.m_.count(); switch (t.mode_) { case am: @@ -3649,8 +3759,8 @@ public: CONSTCD11 explicit time_of_day_storage(std::chrono::seconds since_midnight) NOEXCEPT : base(std::chrono::duration_cast(since_midnight), is24hr) - , m_(std::chrono::duration_cast(since_midnight - h_)) - , s_(since_midnight - h_ - m_) + , m_(std::chrono::duration_cast(abs(since_midnight) - h_)) + , s_(abs(since_midnight) - h_ - 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 { - return to24hr() + s_ + m_; + auto p = to24hr() + s_ + m_; + if (neg_) + p = -p; + return p; } CONSTCD14 precision to_duration() const NOEXCEPT @@ -3686,17 +3799,17 @@ public: { using namespace std; detail::save_stream _(os); - if (static_cast(t) < std::chrono::hours{0}) + if (t.neg_) os << '-'; os.fill('0'); os.flags(std::ios::dec | std::ios::right); if (t.mode_ != am && t.mode_ != pm) os.width(2); - os << std::abs(t.h_.count()) << ':'; + os << t.h_.count() << ':'; os.width(2); - os << std::abs(t.m_.count()) << ':'; + os << t.m_.count() << ':'; os.width(2); - os << std::abs(t.s_.count()); + os << t.s_.count(); switch (t.mode_) { case am: @@ -3715,21 +3828,21 @@ class time_of_day_storage, detail::classify:: : private detail::time_of_day_base { public: - using precision = std::chrono::duration; + using Duration = std::chrono::duration; + using dfs = decimal_format_seconds; + using precision = typename dfs::precision; private: using base = detail::time_of_day_base; std::chrono::minutes m_; - std::chrono::seconds s_; - precision sub_s_; + dfs s_; 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(since_midnight), is24hr) - , m_(std::chrono::duration_cast(since_midnight - h_)) - , s_(std::chrono::duration_cast(since_midnight - h_ - m_)) - , sub_s_(since_midnight - h_ - m_ - s_) + , m_(std::chrono::duration_cast(abs(since_midnight) - h_)) + , s_(abs(since_midnight) - h_ - m_) {} CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, std::chrono::minutes m, @@ -3737,20 +3850,22 @@ public: unsigned md) NOEXCEPT : base(h, md) , m_(m) - , s_(s) - , sub_s_(sub_s) + , s_(s + sub_s) {} CONSTCD11 std::chrono::hours hours() const NOEXCEPT {return h_;} CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT {return m_;} - 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 std::chrono::seconds& seconds() NOEXCEPT {return s_.seconds();} + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT {return s_.seconds();} + CONSTCD11 precision subseconds() const NOEXCEPT {return s_.subseconds();} CONSTCD11 unsigned mode() const NOEXCEPT {return mode_;} 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 @@ -3768,33 +3883,15 @@ public: { using namespace std; detail::save_stream _(os); - if (static_cast(t) < std::chrono::hours{0}) + if (t.neg_) os << '-'; os.fill('0'); os.flags(std::ios::dec | std::ios::right); if (t.mode_ != am && t.mode_ != pm) os.width(2); - os << std::abs(t.h_.count()) << ':'; + os << t.h_.count() << ':'; os.width(2); - os << std::abs(t.m_.count()) << ':'; - os.width(2); - os << std::abs(t.s_.count()) - << use_facet>(os.getloc()).decimal_point(); - os.imbue(locale{}); -#if __cplusplus >= 201402 - CONSTDATA auto cl10 = ceil_log10(Period::den); - using scale = std::ratio_multiply>; - 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(std::ceil(log10(Period::den))); - const auto p10 = std::pow(10., cl10); - os.width(cl10); - os << static_cast(std::abs(t.sub_s_.count()) - * Period::num * p10 / Period::den); -#endif // __cplusplus >= 201402 + os << t.m_.count() << ':' << t.s_; switch (t.mode_) { case am: @@ -3806,50 +3903,6 @@ public: } 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 @@ -3866,7 +3919,8 @@ public: #else // MS cl compiler workaround. template - explicit time_of_day(Args&& ...args) + CONSTCD11 + explicit time_of_day(Args&& ...args) NOEXCEPT : base(std::forward(args)...) {} #endif