From 79c00ad8f21f8b4b3ea8f64c8c761565036ea385 Mon Sep 17 00:00:00 2001 From: Vladislav Shchapov Date: Sun, 10 Oct 2021 19:52:02 +0500 Subject: [PATCH] Improve ISO week-base-year formatter --- include/fmt/chrono.h | 36 ++++++++++++++++++++++++++++++------ test/chrono-test.cc | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 9affd8a1..8125fd35 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1425,10 +1425,37 @@ template struct tm_formatter { } return {q, r}; } + + // 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 { + 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 tm_iso_weak() -> iso_weak { + auto year = tm_year(); + int w = (tm.tm_yday + 10 - (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, 1}; + } else { + return {year, w}; + } + } + auto tm_hour12() const -> decltype(tm.tm_hour) { auto hour = tm.tm_hour % 12; return hour == 0 ? 12 : hour; } + void write1(size_t value) { *out++ = detail::digits2(value)[1]; } void write2(size_t value) { out = std::copy_n(detail::digits2(value), 2, out); @@ -1575,19 +1602,16 @@ template struct tm_formatter { } void on_iso_week_of_year(numeric_system ns) { if (ns == numeric_system::standard) { - // TODO: Optimization - format_localized('V'); + write2(detail::to_unsigned(tm_iso_weak().woy)); } else { format_localized('V', 'O'); } } void on_iso_week_based_year() { - // TODO: Optimization - format_localized('G'); + out = detail::write(out, tm_iso_weak().year); } void on_iso_week_based_year_last2() { - // TODO: Optimization - format_localized('g'); + write2(detail::to_unsigned(tm_split_year(tm_iso_weak().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 4c0ef393..a9bd3053 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -7,6 +7,8 @@ #include "fmt/chrono.h" +#include + #include #include "gtest-extra.h" // EXPECT_THROW_MSG @@ -57,6 +59,42 @@ TEST(chrono_test, format_tm) { EXPECT_EQ(fmt::format("{:%D}", tm), "04/25/16"); EXPECT_EQ(fmt::format("{:%F}", tm), "2016-04-25"); EXPECT_EQ(fmt::format("{:%T}", tm), "11:22:33"); + + // for week on the year + // https://www.cl.cam.ac.uk/~mgk25/iso-time.html + std::vector str_tm_list = { + "1975-12-29", // W01 + "1977-01-02", // W53 + "1999-12-27", // W52 + "1999-12-31", // W52 + "2000-01-01", // W52 + "2000-01-02", // W52 + "2000-01-03", // W1 + }; + std::vector spec_list = {"%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 + std::istringstream ss(str_tm); + ss >> std::get_time(&tm, "%Y-%m-%d"); +#else + strptime(str_tm.c_str(), "%Y-%m-%d", &tm); +#endif + // Because std::get_time doesn't calculate tm_yday, tm_wday, etc. + tm.tm_isdst = 0; + auto 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)); + } + } } // MSVC: