diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 65e6c7e03230..ea9848d7b3eb 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -2040,6 +2040,7 @@ using weekday = std::chrono::weekday; using day = std::chrono::day; using month = std::chrono::month; using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; #else // A fallback version of weekday. class weekday { @@ -2085,91 +2086,119 @@ class year { constexpr explicit operator int() const noexcept { return value_; } }; -class year_month_day {}; +class year_month_day { + private: + year year_; + month month_; + day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr year year() const noexcept { return year_; } + constexpr month month() const noexcept { return month_; } + constexpr day day() const noexcept { return day_; } +}; #endif -// A rudimentary weekday formatter. -template struct formatter { +template +struct formatter : formatter { private: - bool localized = false; + bool use_tm_formatter_{false}; public: 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; - localized = true; - } - return begin; + use_tm_formatter_ = ctx.begin() != nullptr; + return formatter::parse(ctx); } template auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); - detail::get_locale loc(localized, ctx.locale()); + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } + detail::get_locale loc(true, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); } }; -template struct formatter { +template +struct formatter : formatter { + private: + bool use_tm_formatter_{false}; + + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + use_tm_formatter_ = ctx.begin() != nullptr; + return formatter::parse(ctx); } template auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_mday = static_cast(static_cast(d)); - detail::get_locale loc(false, ctx.locale()); + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } + detail::get_locale loc(true, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_day_of_month(detail::numeric_system::standard); return w.out(); } }; -template struct formatter { +template +struct formatter : formatter { private: - bool localized = false; + bool use_tm_formatter_{false}; public: 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; - localized = true; - } - return begin; + use_tm_formatter_ = ctx.begin() != nullptr; + return formatter::parse(ctx); } template auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); - // std::chrono::month has a range of 1-12, std::tm requires 0-11 time.tm_mon = static_cast(static_cast(m)) - 1; - detail::get_locale loc(localized, ctx.locale()); + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } + detail::get_locale loc(true, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_month(); return w.out(); } }; -template struct formatter { +template +struct formatter : formatter { + private: + bool use_tm_formatter_{false}; + + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + use_tm_formatter_ = ctx.begin() != nullptr; + return formatter::parse(ctx); } template auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); - // std::tm::tm_year is years since 1900 time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } detail::get_locale loc(true, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_year(detail::numeric_system::standard); @@ -2177,6 +2206,35 @@ template struct formatter { } }; +template +struct formatter : formatter { + private: + bool use_tm_formatter_{false}; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + use_tm_formatter_ = ctx.begin() != nullptr; + return formatter::parse(ctx); + } + + template + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + template struct formatter, Char> { private: diff --git a/test/chrono-test.cc b/test/chrono-test.cc index d692e6c7b19a..2eec03c6ca20 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -749,20 +749,19 @@ TEST(chrono_test, unsigned_duration) { } TEST(chrono_test, weekday) { - auto loc = get_locale("es_ES.UTF-8"); + auto loc = get_locale("en_US.UTF-8"); std::locale::global(loc); auto sat = fmt::weekday(6); - auto tm = std::tm(); - tm.tm_wday = static_cast(sat.c_encoding()); - EXPECT_EQ(fmt::format("{}", sat), "Sat"); - EXPECT_EQ(fmt::format("{:%a}", tm), "Sat"); + EXPECT_EQ(fmt::format("{:%a}", sat), "Sat"); + EXPECT_EQ(fmt::format("{:%A}", sat), "Saturday"); + loc = get_locale("es_ES.UTF-8"); + std::locale::global(loc); if (loc != std::locale::classic()) { auto saturdays = std::vector{"sáb", "sá."}; - EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat))); - EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm))); + EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat))); } } @@ -1014,13 +1013,34 @@ TEST(chrono_test, out_of_range) { } TEST(chrono_test, year_month_day) { - auto loc = get_locale("es_ES.UTF-8"); - std::locale::global(loc); + auto loc = get_locale("en_US.UTF-8"); + std::locale::global(loc); + auto year = fmt::year(2024); auto month = fmt::month(1); auto day = fmt::day(1); + auto ymd = fmt::year_month_day(year, month, day); EXPECT_EQ(fmt::format("{}", year), "2024"); + EXPECT_EQ(fmt::format("{:%Y}", year), "2024"); + EXPECT_EQ(fmt::format("{:%y}", year), "24"); + EXPECT_EQ(fmt::format("{}", month), "Jan"); + EXPECT_EQ(fmt::format("{:%m}", month), "01"); + EXPECT_EQ(fmt::format("{:%b}", month), "Jan"); + EXPECT_EQ(fmt::format("{:%B}", month), "January"); + EXPECT_EQ(fmt::format("{}", day), "01"); + EXPECT_EQ(fmt::format("{:%d}", day), "01"); + + EXPECT_EQ(fmt::format("{}", ymd), "2024-01-01"); + EXPECT_EQ(fmt::format("{:%Y-%m-%d}", ymd), "2024-01-01"); + EXPECT_EQ(fmt::format("{:%Y-%b-%d}", ymd), "2024-Jan-01"); + EXPECT_EQ(fmt::format("{:%Y-%B-%d}", ymd), "2024-January-01"); + + loc = get_locale("es_ES.UTF-8"); + std::locale::global(loc); + if (loc != std::locale::classic()) { + EXPECT_EQ(fmt::format(loc, "{:%b}", month), "ene."); + } }