// 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{ 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 '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_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 struct chrono_formatter { FormatContext &context; OutputIt out; std::chrono::seconds s; std::chrono::milliseconds ms; typedef typename FormatContext::char_type char_type; explicit chrono_formatter(FormatContext &ctx, OutputIt o) : context(ctx), out(o) {} 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"); } }; } // 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"; } template struct formatter, Char> { private: align_spec spec; internal::arg_ref width_ref; mutable basic_string_view format_str; typedef std::chrono::duration duration; struct spec_handler { formatter &f; basic_parse_context &context; typedef internal::arg_ref arg_ref_type; 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(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; } template void on_dynamic_width(Id arg_id) { f.width_ref = make_arg_ref(arg_id); } }; public: formatter() : spec() {} 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}; begin = internal::parse_align(begin, end, handler); if (begin == end) return begin; begin = internal::parse_width(begin, end, handler); 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(); memory_buffer buf; typedef output_range range; basic_writer w(range(ctx.out())); if (begin == end || *begin == '}') { if (const char *unit = get_units()) format_to(buf, "{}{}", d.count(), unit); else if (Period::den == 1) format_to(buf, "{}[{}]s", d.count(), Period::num); else format_to(buf, "{}[{}/{}]s", d.count(), Period::num, Period::den); internal::handle_dynamic_spec( spec.width_, width_ref, ctx); } else { auto out = std::back_inserter(buf); internal::chrono_formatter f(ctx, out); f.s = std::chrono::duration_cast(d); f.ms = std::chrono::duration_cast(d - f.s); parse_chrono_format(begin, end, f); } w.write(buf.data(), buf.size(), spec); return w.out(); } }; FMT_END_NAMESPACE #endif // FMT_CHRONO_H_