diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 65e6c7e03230..2b2543527eca 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,46 +2086,75 @@ class year { constexpr explicit operator int() const noexcept { return value_; } }; -class year_month_day {}; +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::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 fmt::year year() const noexcept { return year_; } + constexpr fmt::month month() const noexcept { return month_; } + constexpr fmt::day day() const noexcept { return day_; } +}; #endif -// A rudimentary weekday formatter. -template struct formatter { +template +struct formatter : private formatter { private: - bool localized = false; + 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; + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } - return begin; + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } 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(localized_, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); } }; -template struct formatter { +template +struct formatter : private formatter { + private: + bool use_tm_formatter_{false}; + + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } detail::get_locale loc(false, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_day_of_month(detail::numeric_system::standard); @@ -2132,51 +2162,96 @@ template struct formatter { } }; -template struct formatter { +template +struct formatter : private formatter { private: - bool localized = false; + 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; + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } - return begin; + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } 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(localized_, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_month(); return w.out(); } }; -template struct formatter { +template +struct formatter : private formatter { + private: + bool use_tm_formatter_{false}; + + public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - return ctx.begin(); + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } 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; - detail::get_locale loc(true, ctx.locale()); + if (use_tm_formatter_) { + return formatter::format(time, ctx); + } + detail::get_locale loc(false, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_year(detail::numeric_system::standard); return w.out(); } }; +template +struct formatter : private formatter { + private: + bool use_tm_formatter_{false}; + + public: + FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) + -> decltype(ctx.begin()) { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + 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..191adccec907 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -751,18 +751,17 @@ TEST(chrono_test, unsigned_duration) { TEST(chrono_test, weekday) { auto loc = get_locale("es_ES.UTF-8"); std::locale::global(loc); - auto sat = fmt::weekday(6); - auto tm = std::tm(); - tm.tm_wday = static_cast(sat.c_encoding()); + auto sat = fmt::weekday(6); 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"); 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,33 @@ 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 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"); + + auto loc = get_locale("es_ES.UTF-8"); + std::locale::global(loc); + if (loc != std::locale::classic()) { + auto months = std::vector{"ene.", "ene"}; + EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month))); + EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month))); + } }