// 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 "format.h" #include "locale.h" #include #include #include #include FMT_BEGIN_NAMESPACE namespace internal { template FMT_CONSTEXPR const char* get_units() { return FMT_NULL; } 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) 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) 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: throw format_error("invalid format"); } break; } case 'O': if (ptr == end) 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: throw format_error("invalid format"); } break; default: throw format_error("invalid format"); } begin = ptr; } if (begin != ptr) handler.on_text(begin, ptr); return ptr; } struct chrono_format_checker { void report_no_date() { throw format_error("no date"); } template void on_text(const Char*, const Char*) {} void on_abbr_weekday() { report_no_date(); } void on_full_weekday() { report_no_date(); } void on_dec0_weekday(numeric_system) { report_no_date(); } void on_dec1_weekday(numeric_system) { report_no_date(); } void on_abbr_month() { report_no_date(); } 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) {} void on_datetime(numeric_system) { report_no_date(); } void on_loc_date(numeric_system) { report_no_date(); } void on_loc_time(numeric_system) { report_no_date(); } void on_us_date() { report_no_date(); } 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() {} void on_utc_offset() { report_no_date(); } void on_tz_name() { report_no_date(); } }; template inline int to_int(Int value) { FMT_ASSERT(value >= (std::numeric_limits::min)() && value <= (std::numeric_limits::max)(), "invalid value"); return static_cast(value); } template OutputIt static format_chrono_duration_value(OutputIt out, Rep val, int precision) { if (precision < 0) { return format_to(out, std::is_floating_point::value ? "{:g}" : "{}", val); } return format_to(out, "{:.{}f}", val, precision); } template static OutputIt format_chrono_duration_unit(OutputIt out) { if (const char* unit = get_units()) return format_to(out, "{}", unit); else if (Period::den == 1) return format_to(out, "[{}]s", Period::num); else return format_to(out, "[{}/{}]s", Period::num, Period::den); } template struct chrono_formatter { FormatContext& context; OutputIt out; int precision; Rep val; typedef std::chrono::duration milliseconds; std::chrono::seconds s; milliseconds ms; typedef typename FormatContext::char_type char_type; explicit chrono_formatter(FormatContext& ctx, OutputIt o, std::chrono::duration d) : context(ctx), out(o), val(d.count()), s(std::chrono::duration_cast(d)), ms(std::chrono::duration_cast(d - s)) {} int hour() const { return to_int((s.count() / 3600) % 24); } int hour12() const { auto hour = to_int((s.count() / 3600) % 12); return hour > 0 ? hour : 12; } int minute() const { return to_int((s.count() / 60) % 60); } int second() const { return to_int(s.count() % 60); } std::tm time() const { auto time = std::tm(); time.tm_hour = hour(); time.tm_min = minute(); time.tm_sec = second(); return time; } void write(int value, int width) { typedef typename int_traits::main_type main_type; main_type n = to_unsigned(value); int num_digits = internal::count_digits(n); if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); out = format_decimal(out, n, num_digits); } void format_localized(const tm& time, const char* format) { 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, format + std::strlen(format)); 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 (ns == numeric_system::standard) return write(hour(), 2); auto time = tm(); time.tm_hour = hour(); format_localized(time, "%OH"); } void on_12_hour(numeric_system ns) { if (ns == numeric_system::standard) return write(hour12(), 2); auto time = tm(); time.tm_hour = hour(); format_localized(time, "%OI"); } void on_minute(numeric_system ns) { if (ns == numeric_system::standard) return write(minute(), 2); auto time = tm(); time.tm_min = minute(); format_localized(time, "%OM"); } void on_second(numeric_system ns) { if (ns == numeric_system::standard) { write(second(), 2); if (ms != std::chrono::milliseconds(0)) { *out++ = '.'; write(to_int(ms.count()), 3); } return; } auto time = tm(); time.tm_sec = second(); format_localized(time, "%OS"); } void on_12_hour_time() { format_localized(time(), "%r"); } void on_24_hour_time() { write(hour(), 2); *out++ = ':'; write(minute(), 2); } void on_iso_time() { on_24_hour_time(); *out++ = ':'; write(second(), 2); } void on_am_pm() { format_localized(time(), "%p"); } void on_duration_value() { out = format_chrono_duration_value(out, val, precision); } void on_duration_unit() { out = format_chrono_duration_unit(out); } }; } // namespace internal template struct formatter, Char> { private: align_spec spec; int precision; typedef internal::arg_ref arg_ref_type; arg_ref_type width_ref; arg_ref_type precision_ref; mutable basic_string_view format_str; typedef std::chrono::duration duration; struct spec_handler { formatter& f; basic_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); const auto str_val = internal::string_view_metadata(format_str, arg_id); return arg_ref_type(str_val); } FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) { return arg_ref_type(context.next_arg_id()); } void on_error(const char* msg) { throw format_error(msg); } void on_fill(Char fill) { f.spec.fill_ = fill; } void on_align(alignment align) { f.spec.align_ = align; } void on_width(unsigned width) { f.spec.width_ = width; } void on_precision(unsigned 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); } }; public: formatter() : spec(), precision(-1) {} FMT_CONSTEXPR auto parse(basic_parse_context& ctx) -> decltype(ctx.begin()) { auto begin = ctx.begin(), end = ctx.end(); if (begin == end) return begin; spec_handler handler{*this, ctx, format_str}; begin = internal::parse_align(begin, end, handler); if (begin == end) return begin; begin = internal::parse_width(begin, end, handler); if (begin == end) return begin; if (*begin == '.') { if (std::is_floating_point::value) begin = internal::parse_precision(begin, end, handler); else handler.on_error("precision not allowed for this argument type"); } end = parse_chrono_format(begin, end, internal::chrono_format_checker()); format_str = basic_string_view(&*begin, internal::to_unsigned(end - begin)); return 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); typedef output_range range; basic_writer w(range(ctx.out())); internal::handle_dynamic_spec( spec.width_, width_ref, ctx, format_str.begin()); internal::handle_dynamic_spec( precision, precision_ref, ctx, format_str.begin()); if (begin == end || *begin == '}') { out = internal::format_chrono_duration_value(out, d.count(), precision); internal::format_chrono_duration_unit(out); } else { internal::chrono_formatter f( ctx, out, d); f.precision = precision; parse_chrono_format(begin, end, f); } w.write(buf.data(), buf.size(), spec); return w.out(); } }; FMT_END_NAMESPACE #endif // FMT_CHRONO_H_