// Formatting library for C++ - chrono support // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ #include #include #include #include #include "format.h" #include "locale.h" FMT_BEGIN_NAMESPACE // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 #endif #if FMT_SAFE_DURATION_CAST // For conversion between std::chrono::durations without undefined // behaviour or erroneous results. // This is a stripped down version of duration_cast, for inclusion in fmt. // See https://github.com/pauldreik/safe_duration_cast // // Copyright Paul Dreik 2019 namespace safe_duration_cast { template ::value && std::numeric_limits::is_signed == std::numeric_limits::is_signed)> FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; static_assert(F::is_integer, "From must be integral"); static_assert(T::is_integer, "To must be integral"); // A and B are both signed, or both unsigned. if (F::digits <= T::digits) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. if (from < T::min() || from > T::max()) { // outside range. ec = 1; return {}; } } return static_cast(from); } /** * converts From to To, without loss. If the dynamic value of from * can't be converted to To without loss, ec is set. */ template ::value && std::numeric_limits::is_signed != std::numeric_limits::is_signed)> FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; static_assert(F::is_integer, "From must be integral"); static_assert(T::is_integer, "To must be integral"); if (F::is_signed && !T::is_signed) { // From may be negative, not allowed! if (fmt::detail::is_negative(from)) { ec = 1; return {}; } // From is positive. Can it always fit in To? if (F::digits <= T::digits) { // yes, From always fits in To. } else { // from may not fit in To, we have to do a dynamic check if (from > static_cast(T::max())) { ec = 1; return {}; } } } if (!F::is_signed && T::is_signed) { // can from be held in To? if (F::digits < T::digits) { // yes, From always fits in To. } else { // from may not fit in To, we have to do a dynamic check if (from > static_cast(T::max())) { // outside range. ec = 1; return {}; } } } // reaching here means all is ok for lossless conversion. return static_cast(from); } // function template ::value)> FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { ec = 0; return from; } // function // clang-format off /** * converts From to To if possible, otherwise ec is set. * * input | output * ---------------------------------|--------------- * NaN | NaN * Inf | Inf * normal, fits in output | converted (possibly lossy) * normal, does not fit in output | ec is set * subnormal | best effort * -Inf | -Inf */ // clang-format on template ::value)> FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { ec = 0; using T = std::numeric_limits; static_assert(std::is_floating_point::value, "From must be floating"); static_assert(std::is_floating_point::value, "To must be floating"); // catch the only happy case if (std::isfinite(from)) { if (from >= T::lowest() && from <= T::max()) { return static_cast(from); } // not within range. ec = 1; return {}; } // nan and inf will be preserved return static_cast(from); } // function template ::value)> FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { ec = 0; static_assert(std::is_floating_point::value, "From must be floating"); return from; } /** * safe duration cast between integral durations */ template ::value), FMT_ENABLE_IF(std::is_integral::value)> To safe_duration_cast(std::chrono::duration from, int& ec) { using From = std::chrono::duration; ec = 0; // the basic idea is that we need to convert from count() in the from type // to count() in the To type, by multiplying it with this: struct Factor : std::ratio_divide {}; static_assert(Factor::num > 0, "num must be positive"); static_assert(Factor::den > 0, "den must be positive"); // the conversion is like this: multiply from.count() with Factor::num // /Factor::den and convert it to To::rep, all this without // overflow/underflow. let's start by finding a suitable type that can hold // both To, From and Factor::num using IntermediateRep = typename std::common_type::type; // safe conversion to IntermediateRep IntermediateRep count = lossless_integral_conversion(from.count(), ec); if (ec) { return {}; } // multiply with Factor::num without overflow or underflow if (Factor::num != 1) { const auto max1 = detail::max_value() / Factor::num; if (count > max1) { ec = 1; return {}; } const auto min1 = std::numeric_limits::min() / Factor::num; if (count < min1) { ec = 1; return {}; } count *= Factor::num; } // this can't go wrong, right? den>0 is checked earlier. if (Factor::den != 1) { count /= Factor::den; } // convert to the to type, safely using ToRep = typename To::rep; const ToRep tocount = lossless_integral_conversion(count, ec); if (ec) { return {}; } return To{tocount}; } /** * safe duration_cast between floating point durations */ template ::value), FMT_ENABLE_IF(std::is_floating_point::value)> To safe_duration_cast(std::chrono::duration from, int& ec) { using From = std::chrono::duration; ec = 0; if (std::isnan(from.count())) { // nan in, gives nan out. easy. return To{std::numeric_limits::quiet_NaN()}; } // maybe we should also check if from is denormal, and decide what to do about // it. // +-inf should be preserved. if (std::isinf(from.count())) { return To{from.count()}; } // the basic idea is that we need to convert from count() in the from type // to count() in the To type, by multiplying it with this: struct Factor : std::ratio_divide {}; static_assert(Factor::num > 0, "num must be positive"); static_assert(Factor::den > 0, "den must be positive"); // the conversion is like this: multiply from.count() with Factor::num // /Factor::den and convert it to To::rep, all this without // overflow/underflow. let's start by finding a suitable type that can hold // both To, From and Factor::num using IntermediateRep = typename std::common_type::type; // force conversion of From::rep -> IntermediateRep to be safe, // even if it will never happen be narrowing in this context. IntermediateRep count = safe_float_conversion(from.count(), ec); if (ec) { return {}; } // multiply with Factor::num without overflow or underflow if (Factor::num != 1) { constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { ec = 1; return {}; } constexpr auto min1 = std::numeric_limits::lowest() / static_cast(Factor::num); if (count < min1) { ec = 1; return {}; } count *= static_cast(Factor::num); } // this can't go wrong, right? den>0 is checked earlier. if (Factor::den != 1) { using common_t = typename std::common_type::type; count /= static_cast(Factor::den); } // convert to the to type, safely using ToRep = typename To::rep; const ToRep tocount = safe_float_conversion(count, ec); if (ec) { return {}; } return To{tocount}; } } // namespace safe_duration_cast #endif // Prevents expansion of a preceding token as a function-style macro. // Usage: f FMT_NOMACRO() #define FMT_NOMACRO namespace detail { inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } } // namespace detail // Thread-safe replacement for std::localtime inline std::tm localtime(std::time_t time) { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} bool run() { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } bool handle(std::tm* tm) { return tm != nullptr; } bool handle(detail::null<>) { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } bool fallback(int res) { return res == 0; } #if !FMT_MSC_VER bool fallback(detail::null<>) { using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } #endif }; dispatcher lt(time); // Too big time values may be unsupported. if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); return lt.tm_; } // Thread-safe replacement for std::gmtime inline std::tm gmtime(std::time_t time) { struct dispatcher { std::time_t time_; std::tm tm_; dispatcher(std::time_t t) : time_(t) {} bool run() { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } bool handle(std::tm* tm) { return tm != nullptr; } bool handle(detail::null<>) { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } bool fallback(int res) { return res == 0; } #if !FMT_MSC_VER bool fallback(detail::null<>) { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; } #endif }; dispatcher gt(time); // Too big time values may be unsupported. if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); return gt.tm_; } namespace detail { inline size_t strftime(char* str, size_t count, const char* format, const std::tm* time) { return std::strftime(str, count, format, time); } inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, const std::tm* time) { return std::wcsftime(str, count, format, time); } } // namespace detail template struct formatter { template auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto it = ctx.begin(); if (it != ctx.end() && *it == ':') ++it; auto end = it; while (end != ctx.end() && *end != '}') ++end; tm_format.reserve(detail::to_unsigned(end - it + 1)); tm_format.append(it, end); tm_format.push_back('\0'); return end; } template auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { basic_memory_buffer buf; size_t start = buf.size(); for (;;) { size_t size = buf.capacity() - start; size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); if (count != 0) { buf.resize(start + count); break; } if (size >= tm_format.size() * 256) { // If the buffer is 256 times larger than the format string, assume // that `strftime` gives an empty result. There doesn't seem to be a // better way to distinguish the two cases: // https://github.com/fmtlib/fmt/issues/367 break; } const size_t MIN_GROWTH = 10; buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); } return std::copy(buf.begin(), buf.end(), ctx.out()); } basic_memory_buffer tm_format; }; namespace detail { template FMT_CONSTEXPR const char* get_units() { return nullptr; } template <> FMT_CONSTEXPR const char* get_units() { return "as"; } template <> FMT_CONSTEXPR const char* get_units() { return "fs"; } template <> FMT_CONSTEXPR const char* get_units() { return "ps"; } template <> FMT_CONSTEXPR const char* get_units() { return "ns"; } template <> FMT_CONSTEXPR const char* get_units() { return "µs"; } template <> FMT_CONSTEXPR const char* get_units() { return "ms"; } template <> FMT_CONSTEXPR const char* get_units() { return "cs"; } template <> FMT_CONSTEXPR const char* get_units() { return "ds"; } template <> FMT_CONSTEXPR const char* get_units>() { return "s"; } template <> FMT_CONSTEXPR const char* get_units() { return "das"; } template <> FMT_CONSTEXPR const char* get_units() { return "hs"; } template <> FMT_CONSTEXPR const char* get_units() { return "ks"; } template <> FMT_CONSTEXPR const char* get_units() { return "Ms"; } template <> FMT_CONSTEXPR const char* get_units() { return "Gs"; } template <> FMT_CONSTEXPR const char* get_units() { return "Ts"; } template <> FMT_CONSTEXPR const char* get_units() { return "Ps"; } template <> FMT_CONSTEXPR const char* get_units() { return "Es"; } template <> FMT_CONSTEXPR const char* get_units>() { return "m"; } template <> FMT_CONSTEXPR const char* get_units>() { return "h"; } enum class numeric_system { standard, // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. alternative }; // Parses a put_time-like format string and invokes handler actions. template FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, const Char* end, Handler&& handler) { auto ptr = begin; while (ptr != end) { auto c = *ptr; if (c == '}') break; if (c != '%') { ++ptr; continue; } if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': handler.on_text(ptr - 1, ptr); break; case 'n': { const Char newline[] = {'\n'}; handler.on_text(newline, newline + 1); break; } case 't': { const Char tab[] = {'\t'}; handler.on_text(tab, tab + 1); break; } // Day of the week: case 'a': handler.on_abbr_weekday(); break; case 'A': handler.on_full_weekday(); break; case 'w': handler.on_dec0_weekday(numeric_system::standard); break; case 'u': handler.on_dec1_weekday(numeric_system::standard); break; // Month: case 'b': handler.on_abbr_month(); break; case 'B': handler.on_full_month(); break; // Hour, minute, second: case 'H': handler.on_24_hour(numeric_system::standard); break; case 'I': handler.on_12_hour(numeric_system::standard); break; case 'M': handler.on_minute(numeric_system::standard); break; case 'S': handler.on_second(numeric_system::standard); break; // Other: case 'c': handler.on_datetime(numeric_system::standard); break; case 'x': handler.on_loc_date(numeric_system::standard); break; case 'X': handler.on_loc_time(numeric_system::standard); break; case 'D': handler.on_us_date(); break; case 'F': handler.on_iso_date(); break; case 'r': handler.on_12_hour_time(); break; case 'R': handler.on_24_hour_time(); break; case 'T': handler.on_iso_time(); break; case 'p': handler.on_am_pm(); break; case 'Q': handler.on_duration_value(); break; case 'q': handler.on_duration_unit(); break; case 'z': handler.on_utc_offset(); break; case 'Z': handler.on_tz_name(); break; // Alternative representation: case 'E': { if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case 'c': handler.on_datetime(numeric_system::alternative); break; case 'x': handler.on_loc_date(numeric_system::alternative); break; case 'X': handler.on_loc_time(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); } break; } case 'O': if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; case 'u': handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': handler.on_24_hour(numeric_system::alternative); break; case 'I': handler.on_12_hour(numeric_system::alternative); break; case 'M': handler.on_minute(numeric_system::alternative); break; case 'S': handler.on_second(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); } break; default: FMT_THROW(format_error("invalid format")); } begin = ptr; } if (begin != ptr) handler.on_text(begin, ptr); return ptr; } struct chrono_format_checker { FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } template void on_text(const Char*, const Char*) {} FMT_NORETURN void on_abbr_weekday() { report_no_date(); } FMT_NORETURN void on_full_weekday() { report_no_date(); } FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } FMT_NORETURN void on_abbr_month() { report_no_date(); } FMT_NORETURN void on_full_month() { report_no_date(); } void on_24_hour(numeric_system) {} void on_12_hour(numeric_system) {} void on_minute(numeric_system) {} void on_second(numeric_system) {} FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } FMT_NORETURN void on_us_date() { report_no_date(); } FMT_NORETURN void on_iso_date() { report_no_date(); } void on_12_hour_time() {} void on_24_hour_time() {} void on_iso_time() {} void on_am_pm() {} void on_duration_value() {} void on_duration_unit() {} FMT_NORETURN void on_utc_offset() { report_no_date(); } FMT_NORETURN void on_tz_name() { report_no_date(); } }; template ::value)> inline bool isnan(T) { return false; } template ::value)> inline bool isnan(T value) { return std::isnan(value); } template ::value)> inline bool isfinite(T) { return true; } template ::value)> inline bool isfinite(T value) { return std::isfinite(value); } // Converts value to int and checks that it's in the range [0, upper). template ::value)> inline int to_nonnegative_int(T value, int upper) { FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); (void)upper; return static_cast(value); } template ::value)> inline int to_nonnegative_int(T value, int upper) { FMT_ASSERT( std::isnan(value) || (value >= 0 && value <= static_cast(upper)), "invalid value"); (void)upper; return static_cast(value); } template ::value)> inline T mod(T x, int y) { return x % static_cast(y); } template ::value)> inline T mod(T x, int y) { return std::fmod(x, static_cast(y)); } // If T is an integral type, maps T to its unsigned counterpart, otherwise // leaves it unchanged (unlike std::make_unsigned). template ::value> struct make_unsigned_or_unchanged { using type = T; }; template struct make_unsigned_or_unchanged { using type = typename std::make_unsigned::type; }; #if FMT_SAFE_DURATION_CAST // throwing version of safe_duration_cast template To fmt_safe_duration_cast(std::chrono::duration from) { int ec; To to = safe_duration_cast::safe_duration_cast(from, ec); if (ec) FMT_THROW(format_error("cannot format duration")); return to; } #endif template ::value)> inline std::chrono::duration get_milliseconds( std::chrono::duration d) { // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type::type; const auto d_as_common = fmt_safe_duration_cast(d); const auto d_as_whole_seconds = fmt_safe_duration_cast(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = fmt_safe_duration_cast>(diff); return ms; #else auto s = std::chrono::duration_cast(d); return std::chrono::duration_cast(d - s); #endif } template ::value)> inline std::chrono::duration get_milliseconds( std::chrono::duration d) { using common_type = typename std::common_type::type; auto ms = mod(d.count() * static_cast(Period::num) / static_cast(Period::den) * 1000, 1000); return std::chrono::duration(static_cast(ms)); } template OutputIt format_duration_value(OutputIt out, Rep val, int precision) { const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0}; if (precision >= 0) return format_to(out, pr_f, val, precision); const Char fp_f[] = {'{', ':', 'g', '}', 0}; const Char format[] = {'{', '}', 0}; return format_to(out, std::is_floating_point::value ? fp_f : format, val); } template OutputIt copy_unit(string_view unit, OutputIt out, Char) { return std::copy(unit.begin(), unit.end(), out); } template OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { // This works when wchar_t is UTF-32 because units only contain characters // that have the same representation in UTF-16 and UTF-32. utf8_to_utf16 u(unit); return std::copy(u.c_str(), u.c_str() + u.size(), out); } template OutputIt format_duration_unit(OutputIt out) { if (const char* unit = get_units()) return copy_unit(string_view(unit), out, Char()); const Char num_f[] = {'[', '{', '}', ']', 's', 0}; if (Period::den == 1) return format_to(out, num_f, Period::num); const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; return format_to(out, num_def_f, Period::num, Period::den); } template struct chrono_formatter { FormatContext& context; OutputIt out; int precision; // rep is unsigned to avoid overflow. using rep = conditional_t::value && sizeof(Rep) < sizeof(int), unsigned, typename make_unsigned_or_unchanged::type>; rep val; using seconds = std::chrono::duration; seconds s; using milliseconds = std::chrono::duration; bool negative; using char_type = typename FormatContext::char_type; explicit chrono_formatter(FormatContext& ctx, OutputIt o, std::chrono::duration d) : context(ctx), out(o), val(static_cast(d.count())), negative(false) { if (d.count() < 0) { val = 0 - val; negative = true; } // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST // might need checked conversion (rep!=Rep) auto tmpval = std::chrono::duration(val); s = fmt_safe_duration_cast(tmpval); #else s = std::chrono::duration_cast( std::chrono::duration(val)); #endif } // returns true if nan or inf, writes to out. bool handle_nan_inf() { if (isfinite(val)) { return false; } if (isnan(val)) { write_nan(); return true; } // must be +-inf if (val > 0) { write_pinf(); } else { write_ninf(); } return true; } Rep hour() const { return static_cast(mod((s.count() / 3600), 24)); } Rep hour12() const { Rep hour = static_cast(mod((s.count() / 3600), 12)); return hour <= 0 ? 12 : hour; } Rep minute() const { return static_cast(mod((s.count() / 60), 60)); } Rep second() const { return static_cast(mod(s.count(), 60)); } std::tm time() const { auto time = std::tm(); time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_min = to_nonnegative_int(minute(), 60); time.tm_sec = to_nonnegative_int(second(), 60); return time; } void write_sign() { if (negative) { *out++ = '-'; negative = false; } } void write(Rep value, int width) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); out = format_decimal(out, n, num_digits); } void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } void format_localized(const tm& time, char format, char modifier = 0) { if (isnan(val)) return write_nan(); auto locale = context.locale().template get(); auto& facet = std::use_facet>(locale); std::basic_ostringstream os; os.imbue(locale); facet.put(os, os, ' ', &time, format, modifier); auto str = os.str(); std::copy(str.begin(), str.end(), out); } void on_text(const char_type* begin, const char_type* end) { std::copy(begin, end, out); } // These are not implemented because durations don't have date information. void on_abbr_weekday() {} void on_full_weekday() {} void on_dec0_weekday(numeric_system) {} void on_dec1_weekday(numeric_system) {} void on_abbr_month() {} void on_full_month() {} void on_datetime(numeric_system) {} void on_loc_date(numeric_system) {} void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} void on_utc_offset() {} void on_tz_name() {} void on_24_hour(numeric_system ns) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) return write(hour(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); format_localized(time, 'H', 'O'); } void on_12_hour(numeric_system ns) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) return write(hour12(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); format_localized(time, 'I', 'O'); } void on_minute(numeric_system ns) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) return write(minute(), 2); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); format_localized(time, 'M', 'O'); } void on_second(numeric_system ns) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { write(second(), 2); #if FMT_SAFE_DURATION_CAST // convert rep->Rep using duration_rep = std::chrono::duration; using duration_Rep = std::chrono::duration; auto tmpval = fmt_safe_duration_cast(duration_rep{val}); #else auto tmpval = std::chrono::duration(val); #endif auto ms = get_milliseconds(tmpval); if (ms != std::chrono::milliseconds(0)) { *out++ = '.'; write(ms.count(), 3); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); format_localized(time, 'S', 'O'); } void on_12_hour_time() { if (handle_nan_inf()) return; format_localized(time(), 'r'); } void on_24_hour_time() { if (handle_nan_inf()) { *out++ = ':'; handle_nan_inf(); return; } write(hour(), 2); *out++ = ':'; write(minute(), 2); } void on_iso_time() { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; write(second(), 2); } void on_am_pm() { if (handle_nan_inf()) return; format_localized(time(), 'p'); } void on_duration_value() { if (handle_nan_inf()) return; write_sign(); out = format_duration_value(out, val, precision); } void on_duration_unit() { out = format_duration_unit(out); } }; } // namespace detail template struct formatter, Char> { private: basic_format_specs specs; int precision; using arg_ref_type = detail::arg_ref; arg_ref_type width_ref; arg_ref_type precision_ref; mutable basic_string_view format_str; using duration = std::chrono::duration; struct spec_handler { formatter& f; basic_format_parse_context& context; basic_string_view format_str; template FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { context.check_arg_id(arg_id); return arg_ref_type(arg_id); } FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view arg_id) { context.check_arg_id(arg_id); return arg_ref_type(arg_id); } FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { return arg_ref_type(context.next_arg_id()); } void on_error(const char* msg) { FMT_THROW(format_error(msg)); } void on_fill(basic_string_view fill) { f.specs.fill = fill; } void on_align(align_t align) { f.specs.align = align; } void on_width(int width) { f.specs.width = width; } void on_precision(int _precision) { f.precision = _precision; } void end_precision() {} template void on_dynamic_width(Id arg_id) { f.width_ref = make_arg_ref(arg_id); } template void on_dynamic_precision(Id arg_id) { f.precision_ref = make_arg_ref(arg_id); } }; using iterator = typename basic_format_parse_context::iterator; struct parse_range { iterator begin; iterator end; }; FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context& ctx) { auto begin = ctx.begin(), end = ctx.end(); if (begin == end || *begin == '}') return {begin, begin}; spec_handler handler{*this, ctx, format_str}; begin = detail::parse_align(begin, end, handler); if (begin == end) return {begin, begin}; begin = detail::parse_width(begin, end, handler); if (begin == end) return {begin, begin}; if (*begin == '.') { if (std::is_floating_point::value) begin = detail::parse_precision(begin, end, handler); else handler.on_error("precision not allowed for this argument type"); } end = parse_chrono_format(begin, end, detail::chrono_format_checker()); return {begin, end}; } public: formatter() : precision(-1) {} FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { auto range = do_parse(ctx); format_str = basic_string_view( &*range.begin, detail::to_unsigned(range.end - range.begin)); return range.end; } template auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { auto begin = format_str.begin(), end = format_str.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. basic_memory_buffer buf; auto out = std::back_inserter(buf); detail::handle_dynamic_spec(specs.width, width_ref, ctx); detail::handle_dynamic_spec(precision, precision_ref, ctx); if (begin == end || *begin == '}') { out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); } else { detail::chrono_formatter f( ctx, out, d); f.precision = precision; parse_chrono_format(begin, end, f); } return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } }; FMT_END_NAMESPACE #endif // FMT_CHRONO_H_