From cde44ddb72cee531cb2458784e55f2e8dbee1b35 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 10 Oct 2021 13:50:43 +0500 Subject: [PATCH] Improve year formatter --- include/fmt/chrono.h | 49 +++++++++++++++++++++++++++++--------------- test/chrono-test.cc | 15 ++++++++------ 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 3f7d7959..55c33880 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1406,12 +1406,29 @@ template struct tm_formatter { OutputIt out; const std::tm& tm; - auto tm_year() const -> decltype(tm.tm_year) { return 1900 + tm.tm_year; } + auto tm_year() const -> int { return 1900 + tm.tm_year; } + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 bytes, with more only if necessary. + struct split_year { + int upper; + int lower; + }; + auto tm_split_year() const -> split_year { + auto year = tm_year(); + auto q = year / 100; + auto r = year % 100; + if (r < 0) { + // r in [0, 99] + r = -r; + } + return {q, r}; + } auto tm_hour12() const -> decltype(tm.tm_hour) { auto hour = tm.tm_hour % 12; return hour == 0 ? 12 : hour; } - static constexpr char digits1(size_t value) { return detail::digits2(value)[1]; } @@ -1478,12 +1495,11 @@ template struct tm_formatter { format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); } void on_us_date() { - auto year = tm_year(); char buf[8]; - detail::write_digit2_separated( - buf, detail::to_unsigned(tm.tm_mon + 1), - detail::to_unsigned(tm.tm_mday), - detail::to_unsigned((year < 0 ? -year : year) % 100), '/'); + detail::write_digit2_separated(buf, detail::to_unsigned(tm.tm_mon + 1), + detail::to_unsigned(tm.tm_mday), + detail::to_unsigned(tm_split_year().lower), + '/'); out = std::copy_n(buf, sizeof(buf), out); } void on_iso_date() { @@ -1513,9 +1529,8 @@ template struct tm_formatter { } void on_last2_year(numeric_system ns) { if (ns == numeric_system::standard) { - // TODO: Negative years? - out = std::copy_n(detail::digits2(detail::to_unsigned(tm_year() % 100)), - 2, out); + out = std::copy_n( + detail::digits2(detail::to_unsigned(tm_split_year().lower)), 2, out); } else { format_localized('y', 'O'); } @@ -1523,13 +1538,13 @@ template struct tm_formatter { void on_offset_year() { format_localized('y', 'E'); } void on_base_year(numeric_system ns) { if (ns == numeric_system::standard) { - // TODO: Negative years? - auto format_specs = basic_format_specs(); - format_specs.width = 2; - format_specs.align = align::numeric; - format_specs.fill[0] = char_type('0'); - auto year = tm_year(); - out = detail::write(out, year / 100, format_specs, {}); + auto split = tm_split_year(); + if (split.upper >= 0 && split.upper < 100) { + out = std::copy_n(detail::digits2(detail::to_unsigned(split.upper)), 2, + out); + } else { + out = detail::write(out, split.upper); + } } else { format_localized('C', 'E'); } diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 0c8ee866..4c0ef393 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -52,6 +52,7 @@ TEST(chrono_test, format_tm) { "The date is 2016-04-25 11:22:33."); EXPECT_EQ(fmt::format("{:%Y}", tm), "2016"); EXPECT_EQ(fmt::format("{:%C}", tm), "20"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); EXPECT_EQ(fmt::format("{:%e}", tm), "25"); EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16"); EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25"); @@ -74,6 +75,7 @@ TEST(chrono_test, format_tm_future) { "The date is 12345-04-25 11:22:33."); EXPECT_EQ(fmt::format("{:%Y}", tm), "12345"); EXPECT_EQ(fmt::format("{:%C}", tm), "123"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/45"); EXPECT_EQ(fmt::format("{:%F}", tm), "12345-04-25"); EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33"); @@ -91,14 +93,15 @@ TEST(chrono_test, format_tm_past) { "The date is -101-04-25 11:22:33."); EXPECT_EQ(fmt::format("{:%Y}", tm), "-101"); - // macOS %C - "-1" - // Linux %C - "-2" - // Simple impl %C - "-1" + // macOS %C - "-1" + // Linux %C - "-2" + // fmt %C - "-1" EXPECT_EQ(fmt::format("{:%C}", tm), "-1"); + EXPECT_EQ(fmt::format("{:%C%y}", tm), fmt::format("{:%Y}", tm)); - // macOS %D - "04/25/01" (%y) - // Linux %D - "04/25/99" (%y) - // Simple impl %D - "04/25/01" (%y) + // macOS %D - "04/25/01" (%y) + // Linux %D - "04/25/99" (%y) + // fmt %D - "04/25/01" (%y) EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/01"); EXPECT_EQ(fmt::format("{:%F}", tm), "-101-04-25");