diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 8180598d9bb4..34dcac2a5afe 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -291,39 +291,41 @@ inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } -template -inline void do_write(buffer& buf, const std::tm& time, - const std::locale& loc, char format, char modifier) { - auto&& format_buf = - formatbuf, std::streamsize>(buf); - auto&& os = std::basic_ostream(&format_buf); - os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, Char(' '), &time, format, modifier); - if (end.failed()) FMT_THROW(format_error("failed to format time")); -} - -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto&& buffer = get_buffer(out); - do_write(buffer, time, loc, format, modifier); - return buffer.out(); -} - inline const std::locale& get_classic_locale() { static const auto& locale = std::locale::classic(); return locale; } -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto&& buffer = basic_memory_buffer(); - do_write(buffer, time, loc, format, modifier); +template struct codecvt_result { + CodeUnit buf[32]; + CodeUnit* end; +}; + +template +inline auto write_codecvt(string_view in_buf, const std::locale& loc) + -> codecvt_result { + using codecvt = std::codecvt; +#if FMT_CLANG_VERSION +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" + auto& f = std::use_facet(loc); +# pragma clang diagnostic pop +#else + auto& f = std::use_facet(loc); +#endif + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + codecvt_result out = {}; + auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, + std::begin(out.buf), std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); + return out; +} + +template +auto write_char_buffer(OutputIt out, buffer& buf, const std::locale& loc) + -> OutputIt { if (detail::is_utf8() && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. @@ -335,57 +337,88 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, #else using code_unit = char32_t; #endif - - using codecvt = std::codecvt; -#if FMT_CLANG_VERSION -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated" - auto& f = std::use_facet(loc); -# pragma clang diagnostic pop -#else - auto& f = std::use_facet(loc); -#endif - - auto mb = std::mbstate_t(); - const char* from_next = nullptr; - code_unit* to_next = nullptr; - constexpr size_t buf_size = 32; - code_unit buf[buf_size] = {}; - auto result = f.in(mb, buffer.data(), buffer.data() + buffer.size(), - from_next, buf, buf + buf_size, to_next); - if (result != std::codecvt_base::ok) - FMT_THROW(format_error("failed to format time")); - buffer.clear(); - for (code_unit* p = buf; p != to_next; ++p) { + auto unit = + write_codecvt(string_view(buf.data(), buf.size()), loc); + buf.clear(); + for (code_unit* p = unit.buf; p != unit.end; ++p) { uint32_t c = static_cast(*p); if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { // surrogate pair ++p; - if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (p == unit.end || (c & 0xfc00) != 0xd800 || + (*p & 0xfc00) != 0xdc00) { FMT_THROW(format_error("failed to format time")); } c = (c << 10) + static_cast(*p) - 0x35fdc00; } if (c < 0x80) { - buffer.push_back(static_cast(c)); + buf.push_back(static_cast(c)); } else if (c < 0x800) { - buffer.push_back(static_cast(0xc0 | (c >> 6))); - buffer.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - buffer.push_back(static_cast(0xe0 | (c >> 12))); - buffer.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buffer.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else if (c >= 0x10000 && c <= 0x10ffff) { - buffer.push_back(static_cast(0xf0 | (c >> 18))); - buffer.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - buffer.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buffer.push_back(static_cast(0x80 | (c & 0x3f))); + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); } else { FMT_THROW(format_error("failed to format time")); } } } - return copy_str(buffer.data(), buffer.data() + buffer.size(), out); + return copy_str(buf.data(), buf.data() + buf.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + auto unit = write_codecvt(sv, loc); + return copy_str(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + auto&& buf = basic_memory_buffer(); + buf.append(sv.begin(), sv.end()); + return write_char_buffer(out, buf, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = + formatbuf, std::streamsize>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + using iterator = std::ostreambuf_iterator; + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buffer = get_buffer(out); + do_write(buffer, time, loc, format, modifier); + return buffer.out(); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buffer = basic_memory_buffer(); + do_write(buffer, time, loc, format, modifier); + return write_char_buffer(out, buffer, loc); } } // namespace detail @@ -882,6 +915,12 @@ template struct has_member_data_tm_gmtoff> : std::true_type {}; +template +struct has_member_data_tm_zone : std::false_type {}; +template +struct has_member_data_tm_zone> + : std::true_type {}; + #if defined(_WIN32) inline void tzset_once() { static bool init = []() -> bool { @@ -1039,6 +1078,14 @@ template class tm_writer { #endif } + void format_tz_name_impl(std::true_type) { + if (is_classic_) + out_ = write_tm_str(out_, tm_.tm_zone, loc_); + else + format_localized('Z'); + } + void format_tz_name_impl(std::false_type) { format_localized('Z'); } + void format_localized(char format, char modifier = 0) { out_ = write(out_, tm_, loc_, format, modifier); } @@ -1148,7 +1195,7 @@ template class tm_writer { void on_utc_offset() { format_utc_offset_impl(has_member_data_tm_gmtoff{}); } - void on_tz_name() { format_localized('Z'); } + void on_tz_name() { format_tz_name_impl(has_member_data_tm_zone{}); } void on_year(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard)