From d70ec75b9d20d6ffd082571c91bad0d59cd74014 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 17 Oct 2021 17:41:43 +0500 Subject: [PATCH 1/7] Fix unicode test --- test/unicode-test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unicode-test.cc b/test/unicode-test.cc index 73cac58c3438..63c828dd8f99 100644 --- a/test/unicode-test.cc +++ b/test/unicode-test.cc @@ -18,7 +18,7 @@ using testing::Contains; TEST(unicode_test, is_utf8) { EXPECT_TRUE(fmt::detail::is_utf8()); } TEST(unicode_test, legacy_locale) { - auto loc = get_locale("ru_RU.CP1251", "Russian.1251"); + auto loc = get_locale("ru_RU.CP1251", "Russian_Russia.1251"); if (loc == std::locale::classic()) return; auto s = std::string(); From 6b37e4e81a576d8c4b24b9b9469602183d8a5c7b Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Thu, 28 Oct 2021 19:36:57 +0500 Subject: [PATCH 2/7] Add xchar support to chrono formatter --- include/fmt/chrono.h | 133 ++++++++++++++++++++++++------------------- test/xchar-test.cc | 20 ++++++- 2 files changed, 92 insertions(+), 61 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 8495cebb75e0..459e7239db78 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -289,84 +289,95 @@ inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } +template inline auto do_write(const std::tm& time, const std::locale& loc, char format, - char modifier) -> std::string { - auto&& os = std::ostringstream(); + char modifier) -> std::basic_string { + auto&& os = std::basic_ostringstream(); os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, ' ', &time, format, modifier); + 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")); - auto str = os.str(); - if (!detail::is_utf8() || loc == std::locale::classic()) return str; + return std::move(os).str(); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto str = do_write(time, loc, format, modifier); + return std::copy(str.begin(), str.end(), out); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto str = do_write(time, loc, format, modifier); + if (detail::is_utf8() && loc != std::locale::classic()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. #if FMT_MSC_VER != 0 || \ (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)) - // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 - // and newer. - using code_unit = wchar_t; + // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 + // and newer. + using code_unit = wchar_t; #else - using code_unit = char32_t; + using code_unit = char32_t; #endif - using codecvt = std::codecvt; + using codecvt = std::codecvt; #if FMT_CLANG_VERSION # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated" - auto& f = std::use_facet(loc); + auto& f = std::use_facet(loc); # pragma clang diagnostic pop #else - auto& f = std::use_facet(loc); + 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, str.data(), str.data() + str.size(), from_next, buf, - buf + buf_size, to_next); - if (result != std::codecvt_base::ok) - FMT_THROW(format_error("failed to format time")); - str.clear(); - for (code_unit* p = buf; p != to_next; ++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) { + 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, str.data(), str.data() + str.size(), from_next, buf, + buf + buf_size, to_next); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); + str.clear(); + for (code_unit* p = buf; p != to_next; ++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) { + FMT_THROW(format_error("failed to format time")); + } + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + if (c < 0x80) { + str.push_back(static_cast(c)); + } else if (c < 0x800) { + str.push_back(static_cast(0xc0 | (c >> 6))); + str.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + str.push_back(static_cast(0xe0 | (c >> 12))); + str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + str.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + str.push_back(static_cast(0xf0 | (c >> 18))); + str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + str.push_back(static_cast(0x80 | (c & 0x3f))); + } else { FMT_THROW(format_error("failed to format time")); } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { - str.push_back(static_cast(c)); - } else if (c < 0x800) { - str.push_back(static_cast(0xc0 | (c >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - str.push_back(static_cast(0xe0 | (c >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - str.push_back(static_cast(0xf0 | (c >> 18))); - str.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - str.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - str.push_back(static_cast(0x80 | (c & 0x3f))); - } else { - FMT_THROW(format_error("failed to format time")); } } - return str; -} - -template -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto str = do_write(time, loc, format, modifier); return std::copy(str.begin(), str.end(), out); } + } // namespace detail FMT_MODULE_EXPORT_BEGIN @@ -1067,7 +1078,7 @@ struct chrono_formatter { if (isnan(val)) return write_nan(); const auto& loc = localized ? context.locale().template get() : std::locale::classic(); - out = detail::write(out, time, loc, format, modifier); + out = detail::write(out, time, loc, format, modifier); } void on_text(const char_type* begin, const char_type* end) { @@ -1215,12 +1226,13 @@ class year_month_day {}; #endif // A rudimentary weekday formatter. -template <> struct formatter { +template struct formatter { private: bool localized = false; public: - FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { auto begin = ctx.begin(), end = ctx.end(); if (begin != end && *begin == 'L') { ++begin; @@ -1229,12 +1241,13 @@ template <> struct formatter { return begin; } - auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) { + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); const auto& loc = localized ? ctx.locale().template get() : std::locale::classic(); - return detail::write(ctx.out(), time, loc, 'a'); + return detail::write(ctx.out(), time, loc, 'a'); } }; diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 67010aac7f45..7a1a452b2e2c 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -15,9 +15,11 @@ #include "fmt/color.h" #include "fmt/ostream.h" #include "fmt/ranges.h" -#include "gtest/gtest.h" +#include "gtest-extra.h" // Contains +#include "util.h" // get_locale using fmt::detail::max_value; +using testing::Contains; namespace test_ns { template class test_string { @@ -466,4 +468,20 @@ TEST(locale_test, complex) { EXPECT_EQ(fmt::format("{:8}", std::complex(1, 2)), " (1+2i)"); } +TEST(locale_test, chrono_weekday) { + auto loc = get_locale("ru_RU.UTF-8", "Russian_Russia.1251"); + auto loc_old = std::locale::global(loc); + auto mon = fmt::weekday(1); + EXPECT_EQ(fmt::format(L"{}", mon), L"Mon"); + if (loc != std::locale::classic()) { + // {L"\x43F\x43D", L"\x41F\x43D", L"\x43F\x43D\x434", L"\x41F\x43D\x434"} + // {L"пн", L"Пн", L"пнд", L"Пнд"} + EXPECT_THAT( + (std::vector{L"\x43F\x43D", L"\x41F\x43D", + L"\x43F\x43D\x434", L"\x41F\x43D\x434"}), + Contains(fmt::format(loc, L"{:L}", mon))); + } + std::locale::global(loc_old); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR From 22948841d6d88153e4bb50ce07b59264f6829364 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 17 Oct 2021 18:22:42 +0500 Subject: [PATCH 3/7] Replace strftime to std::time_put --- include/fmt/chrono.h | 51 ++++++-------------------------------------- test/chrono-test.cc | 13 ++++++----- test/xchar-test.cc | 13 ++++++----- 3 files changed, 22 insertions(+), 55 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 459e7239db78..f937e463b02b 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -475,25 +475,6 @@ inline std::tm gmtime( FMT_BEGIN_DETAIL_NAMESPACE -inline size_t strftime(char* str, size_t count, const char* format, - const std::tm* time) { - // Assign to a pointer to suppress GCCs -Wformat-nonliteral - // First assign the nullptr to suppress -Wsuggest-attribute=format - std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) = - nullptr; - strftime = std::strftime; - return strftime(str, count, format, time); -} - -inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, - const std::tm* time) { - // See above - std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*, - const std::tm*) = nullptr; - wcsftime = std::wcsftime; - return wcsftime(str, count, format, time); -} - // Writes two-digit numbers a, b and c separated by sep to buf. // The method by Pavel Novikov based on // https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. @@ -1416,6 +1397,7 @@ template class tm_writer { private: static constexpr int days_per_week = 7; + const std::locale& loc_; OutputIt out_; const std::tm& tm_; @@ -1527,34 +1509,12 @@ template class tm_writer { } void format_localized(char format, char modifier = 0) { - // By prepending an extra space we can distinguish an empty result that - // indicates insufficient buffer size from a guaranteed non-empty result - // https://github.com/fmtlib/fmt/issues/2238 - Char tm_format[5] = {' ', '%', 'x', '\0', '\0'}; - if (modifier) { - tm_format[2] = modifier; - tm_format[3] = format; - } else { - tm_format[2] = format; - } - - basic_memory_buffer buf; - for (;;) { - size_t size = buf.capacity(); - size_t count = detail::strftime(buf.data(), size, tm_format, &tm_); - if (count != 0) { - buf.resize(count); - break; - } - const size_t min_growth = 10; - buf.reserve(buf.capacity() + (size > min_growth ? size : min_growth)); - } - // Remove the extra space. - out_ = copy_str(buf.begin() + 1, buf.end(), out_); + out_ = write(out_, tm_, loc_, format, modifier); } public: - explicit tm_writer(OutputIt out, const std::tm& tm) : out_(out), tm_(tm) {} + explicit tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) + : loc_(loc), out_(out), tm_(tm) {} OutputIt out() const { return out_; } @@ -1784,7 +1744,8 @@ template struct formatter { template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { - auto w = detail::tm_writer(ctx.out(), tm); + const auto& loc = std::locale::classic(); + auto w = detail::tm_writer(loc, ctx.out(), tm); if (spec_ == spec::year_month_day) w.on_iso_date(); else if (spec_ == spec::hh_mm_ss) diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 84431f692d55..82d96c04e694 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -42,11 +42,14 @@ auto make_second(int s) -> std::tm { } std::string system_strftime(const std::string& format, const std::tm* timeptr, - size_t maxsize = 1024) { - std::vector output(maxsize); - auto size = - std::strftime(output.data(), output.size(), format.c_str(), timeptr); - return std::string(output.data(), size); + std::locale* locptr = nullptr) { + auto loc = locptr ? *locptr : std::locale::classic(); + auto& facet = std::use_facet>(loc); + std::ostringstream os; + os.imbue(loc); + facet.put(os, os, ' ', timeptr, format.c_str(), + format.c_str() + format.size()); + return os.str(); } FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min, diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 7a1a452b2e2c..4620ee250802 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -270,11 +270,14 @@ TEST(xchar_test, chrono) { } std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr, - size_t maxsize = 1024) { - std::vector output(maxsize); - auto size = - std::wcsftime(output.data(), output.size(), format.c_str(), timeptr); - return std::wstring(output.data(), size); + std::locale* locptr = nullptr) { + auto loc = locptr ? *locptr : std::locale::classic(); + auto& facet = std::use_facet>(loc); + std::wostringstream os; + os.imbue(loc); + facet.put(os, os, L' ', timeptr, format.c_str(), + format.c_str() + format.size()); + return os.str(); } TEST(chrono_test, time_point) { From 3b61933abe2b2f633ddf18b431fc55dd610f3dca Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 17 Oct 2021 19:11:58 +0500 Subject: [PATCH 4/7] Add std::locale support to std::tm formatter --- include/fmt/chrono.h | 4 +++- test/chrono-test.cc | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index f937e463b02b..0c38e02e4406 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1744,7 +1744,9 @@ template struct formatter { template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { - const auto& loc = std::locale::classic(); + const auto& loc_ref = ctx.locale(); + const auto& loc = + loc_ref ? loc_ref.template get() : std::locale::classic(); auto w = detail::tm_writer(loc, ctx.out(), tm); if (spec_ == spec::year_month_day) w.on_iso_date(); diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 82d96c04e694..6eaf1cfb5d1d 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -536,10 +536,18 @@ TEST(chrono_test, weekday) { auto loc = get_locale("ru_RU.UTF-8"); std::locale::global(loc); auto mon = fmt::weekday(1); + + auto tm = std::tm(); + tm.tm_wday = static_cast(mon.c_encoding()); + EXPECT_EQ(fmt::format("{}", mon), "Mon"); + EXPECT_EQ(fmt::format("{:%a}", tm), "Mon"); + if (loc != std::locale::classic()) { EXPECT_THAT((std::vector{"пн", "Пн", "пнд", "Пнд"}), Contains(fmt::format(loc, "{:L}", mon))); + EXPECT_THAT((std::vector{"пн", "Пн", "пнд", "Пнд"}), + Contains(fmt::format(loc, "{:%a}", tm))); } } From 18440ef81b20aa79926c4f634ddad78269096220 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Thu, 28 Oct 2021 21:11:27 +0500 Subject: [PATCH 5/7] Use predefined names and formats for C-locale --- include/fmt/chrono.h | 116 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 106 insertions(+), 10 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 0c38e02e4406..0ac173ff5f87 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1393,11 +1393,38 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_tz_name() {} }; +inline const char* tm_wday_full_name(int wday) { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; +} +inline const char* tm_wday_short_name(int wday) { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; +} + +inline const char* tm_mon_full_name(int mon) { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline const char* tm_mon_short_name(int mon) { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; +} + template class tm_writer { private: static constexpr int days_per_week = 7; const std::locale& loc_; + const bool is_classic_; OutputIt out_; const std::tm& tm_; @@ -1514,7 +1541,10 @@ template class tm_writer { public: explicit tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) - : loc_(loc), out_(out), tm_(tm) {} + : loc_(loc), + is_classic_(loc_ == std::locale::classic()), + out_(out), + tm_(tm) {} OutputIt out() const { return out_; } @@ -1522,8 +1552,18 @@ template class tm_writer { out_ = copy_str(begin, end, out_); } - void on_abbr_weekday() { format_localized('a'); } - void on_full_weekday() { format_localized('A'); } + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } void on_dec0_weekday(numeric_system ns) { if (ns != numeric_system::standard) return format_localized('w', 'O'); write1(tm_wday()); @@ -1534,17 +1574,51 @@ template class tm_writer { write1(wday == 0 ? days_per_week : wday); } - void on_abbr_month() { format_localized('b'); } - void on_full_month() { format_localized('B'); } + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } void on_datetime(numeric_system ns) { - format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + if (is_classic_) { +#ifdef _WIN32 + on_us_date(); + *out_++ = ' '; + on_iso_time(); +#else + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month_space(numeric_system::standard); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard); +#endif + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } } void on_loc_date(numeric_system ns) { - format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); } void on_loc_time(numeric_system ns) { - format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); } void on_us_date() { char buf[8]; @@ -1658,7 +1732,22 @@ template class tm_writer { write2(tm_sec()); } - void on_12_hour_time() { format_localized('r'); } + void on_12_hour_time() { + if (is_classic_) { +#ifdef _WIN32 + on_iso_time(); +#else + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); +#endif + } else { + format_localized('r'); + } + } void on_24_hour_time() { write2(tm_hour()); *out_++ = ':'; @@ -1671,7 +1760,14 @@ template class tm_writer { out_ = copy_str(std::begin(buf), std::end(buf), out_); } - void on_am_pm() { format_localized('p'); } + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } // These apply to chrono durations but not tm. void on_duration_value() {} From 91c454b7ff757feff03ad0253b7ef8ffa0096688 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Thu, 28 Oct 2021 21:15:41 +0500 Subject: [PATCH 6/7] Performance improvement --- include/fmt/chrono.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 0ac173ff5f87..874dc4d9f0f5 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1459,8 +1459,9 @@ template class tm_writer { } auto tm_hour12() const noexcept -> int { - auto hour = tm_hour() % 12; - return hour == 0 ? 12 : hour; + const auto h = tm_hour(); + const auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; } // POSIX and the C Standard are unclear or inconsistent about what %C and %y From a9df99f38504dcf85c5b3ff34ec71f77c4f46518 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sat, 30 Oct 2021 19:32:21 +0500 Subject: [PATCH 7/7] Make locale-independent and C locale formats consistent among platforms --- include/fmt/chrono.h | 10 ---------- test/chrono-test.cc | 18 ++++++++++++------ test/xchar-test.cc | 19 ++++++++++++------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 874dc4d9f0f5..997bb2d0dd94 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1590,11 +1590,6 @@ template class tm_writer { void on_datetime(numeric_system ns) { if (is_classic_) { -#ifdef _WIN32 - on_us_date(); - *out_++ = ' '; - on_iso_time(); -#else on_abbr_weekday(); *out_++ = ' '; on_abbr_month(); @@ -1604,7 +1599,6 @@ template class tm_writer { on_iso_time(); *out_++ = ' '; on_year(numeric_system::standard); -#endif } else { format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); } @@ -1735,16 +1729,12 @@ template class tm_writer { void on_12_hour_time() { if (is_classic_) { -#ifdef _WIN32 - on_iso_time(); -#else char buf[8]; write_digit2_separated(buf, to_unsigned(tm_hour12()), to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); out_ = copy_str(std::begin(buf), std::end(buf), out_); *out_++ = ' '; on_am_pm(); -#endif } else { format_localized('r'); } diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 6eaf1cfb5d1d..5019a402a3ee 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -248,13 +248,19 @@ TEST(chrono_test, time_point) { EXPECT_EQ(strftime_full(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2)); std::vector spec_list = { - "%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", "%EC", - "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U", "%OU", "%W", - "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", "%Oe", "%a", "%A", - "%w", "%Ow", "%u", "%Ou", "%H", "%OH", "%I", "%OI", "%M", "%OM", - "%S", "%OS", "%c", "%Ec", "%x", "%Ex", "%X", "%EX", "%D", "%F", - "%r", "%R", "%T", "%p", "%z", "%Z"}; + "%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C", + "%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U", + "%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e", + "%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH", + "%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X", + "%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"}; spec_list.push_back("%Y-%m-%d %H:%M:%S"); +#ifndef _WIN32 + // Disabled on Windows, because these formats is not consistent among + // platforms. + spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"}); +#endif + for (const auto& spec : spec_list) { auto t = std::chrono::system_clock::to_time_t(t1); auto tm = *std::localtime(&t); diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 4620ee250802..8f98c0504882 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -284,14 +284,19 @@ TEST(chrono_test, time_point) { auto t1 = std::chrono::system_clock::now(); std::vector spec_list = { - L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", - L"%C", L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", - L"%Om", L"%U", L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", - L"%d", L"%Od", L"%e", L"%Oe", L"%a", L"%A", L"%w", L"%Ow", - L"%u", L"%Ou", L"%H", L"%OH", L"%I", L"%OI", L"%M", L"%OM", - L"%S", L"%OS", L"%c", L"%Ec", L"%x", L"%Ex", L"%X", L"%EX", - L"%D", L"%F", L"%r", L"%R", L"%T", L"%p", L"%z", L"%Z"}; + L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C", + L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U", + L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e", + L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH", + L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X", + L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p", L"%z", L"%Z"}; spec_list.push_back(L"%Y-%m-%d %H:%M:%S"); +#ifndef _WIN32 + // Disabled on Windows, because these formats is not consistent among + // platforms. + spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"}); +#endif + for (const auto& spec : spec_list) { auto t = std::chrono::system_clock::to_time_t(t1); auto tm = *std::localtime(&t);