From 2754546080a12490dfd4e2cac7e7ead8f320d529 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Thu, 14 Oct 2021 17:50:16 +0500 Subject: [PATCH] Fix errors in ISO week-base-year formatter --- include/fmt/chrono.h | 45 ++++++++++++++++++++++++-------------------- test/chrono-test.cc | 25 ++++++++++++++++-------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 24827714..abf0e45c 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1412,11 +1412,11 @@ template struct tm_formatter { // 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 { + struct split_year_result { int upper; int lower; }; - auto tm_split_year(int year) const -> split_year { + auto split_year(int year) const -> split_year_result { auto q = year / 100; auto r = year % 100; if (r < 0) { @@ -1428,23 +1428,28 @@ template struct tm_formatter { // Algorithm: // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date - struct iso_weak { + struct iso_week_result { int year; int woy; }; - auto tm_iso_year_weaks(int year) -> int { - int p = (year + year / 4 - year / 100 + year / 400) % daysperweek; - int year_1 = year - 1; - int p_1 = (year_1 + year_1 / 4 - year_1 / 100 + year_1 / 400) % daysperweek; - return 52 + ((p == 4 || p_1 == 3) ? 1 : 0); + auto iso_year_weeks(const int curr_year) -> int { + const int prev_year = curr_year - 1; + const int curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + daysperweek; + const int prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + daysperweek; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); } - auto tm_iso_weak() -> iso_weak { - auto year = tm_year(); - int w = (tm.tm_yday + 10 - (tm.tm_wday == 0 ? daysperweek : tm.tm_wday)) / - daysperweek; + auto tm_iso_week() -> iso_week_result { + const auto year = tm_year(); + const int w = + (tm.tm_yday + 11 - (tm.tm_wday == 0 ? daysperweek : tm.tm_wday)) / + daysperweek; if (w < 1) { - return {year - 1, tm_iso_year_weaks(year - 1)}; - } else if (w > tm_iso_year_weaks(year)) { + return {year - 1, iso_year_weeks(year - 1)}; + } else if (w > iso_year_weeks(year)) { return {year + 1, 1}; } else { return {year, w}; @@ -1537,7 +1542,7 @@ template struct tm_formatter { detail::write_digit2_separated( buf, detail::to_unsigned(tm.tm_mon + 1), detail::to_unsigned(tm.tm_mday), - detail::to_unsigned(tm_split_year(tm_year()).lower), '/'); + detail::to_unsigned(split_year(tm_year()).lower), '/'); out = detail::copy_str(std::begin(buf), std::end(buf), out); } void on_iso_date() { @@ -1568,7 +1573,7 @@ template struct tm_formatter { } void on_last2_year(numeric_system ns) { if (ns == numeric_system::standard) { - write2(detail::to_unsigned(tm_split_year(tm_year()).lower)); + write2(detail::to_unsigned(split_year(tm_year()).lower)); } else { format_localized('y', 'O'); } @@ -1576,7 +1581,7 @@ template struct tm_formatter { void on_offset_year() { format_localized('y', 'E'); } void on_base_year(numeric_system ns) { if (ns == numeric_system::standard) { - auto split = tm_split_year(tm_year()); + auto split = split_year(tm_year()); if (split.upper >= 0 && split.upper < 100) { write2(detail::to_unsigned(split.upper)); } else { @@ -1613,14 +1618,14 @@ template struct tm_formatter { } void on_iso_week_of_year(numeric_system ns) { if (ns == numeric_system::standard) { - write2(detail::to_unsigned(tm_iso_weak().woy)); + write2(detail::to_unsigned(tm_iso_week().woy)); } else { format_localized('V', 'O'); } } - void on_iso_week_based_year() { write_year(tm_iso_weak().year); } + void on_iso_week_based_year() { write_year(tm_iso_week().year); } void on_iso_week_based_year_last2() { - write2(detail::to_unsigned(tm_split_year(tm_iso_weak().year).lower)); + write2(detail::to_unsigned(split_year(tm_iso_week().year).lower)); } void on_day_of_year() { auto yday = tm.tm_yday + 1; diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 30a3e381..a25265b8 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -85,10 +85,10 @@ TEST(chrono_test, format_tm) { "2000-01-02", // W52 "2000-01-03", // W1 }; - std::vector spec_list = {"%G", "%g", "%V"}; + const std::string iso_week_spec = "%Y-%m-%d: %G %g %V"; + for (const auto& str_tm : str_tm_list) { tm = std::tm(); - // GCC 4 does not support std::get_time // MSVC dows not support POSIX strptime #ifdef _WIN32 @@ -102,12 +102,21 @@ TEST(chrono_test, format_tm) { std::time_t t = std::mktime(&tm); tm = *std::localtime(&t); - for (const auto& spec : spec_list) { - char output[256] = {}; - std::strftime(output, sizeof(output), spec.c_str(), &tm); - auto fmt_spec = std::string("{:").append(spec).append("}"); - EXPECT_EQ(output, fmt::format(fmt::runtime(fmt_spec), tm)); - } + char output[256] = {}; + std::strftime(output, sizeof(output), iso_week_spec.c_str(), &tm); + auto fmt_spec = std::string("{:").append(iso_week_spec).append("}"); + EXPECT_EQ(output, fmt::format(fmt::runtime(fmt_spec), tm)); + } + + // Every day from 1970-01-01 + std::time_t time_now = std::time(nullptr); + for (std::time_t t = 6 * 3600; t < time_now; t += 86400) { + tm = *std::localtime(&t); + + char output[256] = {}; + std::strftime(output, sizeof(output), iso_week_spec.c_str(), &tm); + auto fmt_spec = std::string("{:").append(iso_week_spec).append("}"); + EXPECT_EQ(output, fmt::format(fmt::runtime(fmt_spec), tm)); } }