Fix weekday and day of year when formatting year_month_day

The year_month_day formatter only filled in tm_year, tm_mon and tm_mday,
so specifiers that depend on the weekday or the day of the year (%a, %A,
%u, %w, %j, %U, %W, %V, %G, %g) were formatted from zero-initialized
fields, e.g. every date was printed as a Sunday. Compute tm_wday and
tm_yday from the date before passing it to the tm formatter.
This commit is contained in:
fhgffy 2026-10-09 00:03:03 +08:00
parent 10cda465ed
commit 1f0e6cf050
2 changed files with 40 additions and 4 deletions

View File

@ -926,6 +926,18 @@ inline auto tm_wday_short_name(int wday) -> const char* {
return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???";
}
// Returns the number of days from 1970-01-01 to the given date in the
// proleptic Gregorian calendar.
inline auto days_from_civil(int y, unsigned m, unsigned d) noexcept
-> long long {
if (m <= 2) --y;
long long era = (y >= 0 ? y : y - 399) / 400;
auto yoe = static_cast<unsigned>(y - era * 400);
unsigned doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1;
unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + static_cast<long long>(doe) - 719468;
}
inline auto tm_mon_full_name(int mon) -> const char* {
static constexpr const char* full_name_list[] = {
"January", "February", "March", "April", "May", "June",
@ -2035,10 +2047,19 @@ struct formatter<year_month_day, Char> : private formatter<std::tm, Char> {
auto format(year_month_day val, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto time = std::tm();
time.tm_year = static_cast<int>(val.year()) - 1900;
time.tm_mon = static_cast<int>(static_cast<unsigned>(val.month())) - 1;
time.tm_mday = static_cast<int>(static_cast<unsigned>(val.day()));
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
auto y = static_cast<int>(val.year());
auto m = static_cast<unsigned>(val.month());
auto d = static_cast<unsigned>(val.day());
time.tm_year = y - 1900;
time.tm_mon = static_cast<int>(m) - 1;
time.tm_mday = static_cast<int>(d);
if (use_tm_formatter_) {
// 1970-01-01 was a Thursday.
auto days = detail::days_from_civil(y, m, d);
time.tm_wday = static_cast<int>((days % 7 + 11) % 7);
time.tm_yday = static_cast<int>(days - detail::days_from_civil(y, 1, 1));
return formatter<std::tm, Char>::format(time, ctx);
}
auto w = detail::tm_writer<decltype(ctx.out()), Char>(locale_ref(), false,
ctx.out(), time);
w.on_iso_date();

View File

@ -1044,3 +1044,18 @@ TEST(chrono_test, year_month_day) {
EXPECT_THAT(months, Contains(fmt::format("{:L}", month)));
}
}
TEST(chrono_test, year_month_day_weekday_and_day_of_year) {
auto ymd = fmt::year_month_day(fmt::year(2024), fmt::month(3), fmt::day(1));
EXPECT_EQ(fmt::format("{:%a %A %u %w}", ymd), "Fri Friday 5 5");
EXPECT_EQ(fmt::format("{:%j %U %W}", ymd), "061 08 09");
EXPECT_EQ(fmt::format("{:%G-W%V}", ymd), "2024-W09");
ymd = fmt::year_month_day(fmt::year(2021), fmt::month(1), fmt::day(1));
EXPECT_EQ(fmt::format("{:%a %j %U %W}", ymd), "Fri 001 00 00");
EXPECT_EQ(fmt::format("{:%G-W%V-%u}", ymd), "2020-W53-5");
ymd = fmt::year_month_day(fmt::year(1969), fmt::month(12), fmt::day(31));
EXPECT_EQ(fmt::format("{:%A %j}", ymd), "Wednesday 365");
EXPECT_EQ(fmt::format("{:%G-W%V}", ymd), "1970-W01");
}