https://gcc.gnu.org/g:dc38eaf935a9408707bb96d218fc3f99169d4d48
commit r16-4024-gdc38eaf935a9408707bb96d218fc3f99169d4d48 Author: Tomasz Kamiński <tkami...@redhat.com> Date: Thu Sep 11 11:11:06 2025 +0200 libstdc++: Rework handling of ISO week calendar and week index formatting. The handling of ISO week-calendar year specifiers (%G, %g) and ISO week number (%V) was merged into a single _M_g_G_V function, as the latter requires ISO year value, computed by the former. The values for %U and %W, which are based on the number of days since the first Sunday and Monday of the year respectively, are now expressed as an offset from the existing _M_day_of_year field. This reduces redundant computation. The required flags were also updated to only need _DayOfYear and _Weekday. The _M_g_G_V function uses _M_day_of_year to compute __idoy, the day of the year for the nearest Thursday. This value is used to determine if the ISO year is the previous year (__idoy <= 0), the current year (__idoy <= 365/366), next year (__idoy <= 730), or later year. This avoids an expensive conversion from local_days to year_month_day in most cases. If the ISO calendar year is current year, the __idoy value is reused for weekday index computation. libstdc++-v3/ChangeLog: * include/bits/chrono_io.h(__formatter_chrono::_M_parse): Update needed flags for %g, %G, %V, %U, %W. (__formatter_chrono::_M_format_to): Change how %V is handled. (__formatter_chrono::_M_g_G): Merged into _M_g_G_V. (__formatter_chrono::_M_g_G_V): Reworked from _M_g_G. (__formatter_chrono::_M_U_V_W): Changed into _M_U_V. (__formatter_chrono::_M_U_W): Reworked implementation. * testsuite/std/time/year_month_day/io.cc: New tests. Reviewed-by: Jonathan Wakely <jwak...@redhat.com> Signed-off-by: Tomasz Kamiński <tkami...@redhat.com> Diff: --- libstdc++-v3/include/bits/chrono_io.h | 97 ++++++++++++---------- .../testsuite/std/time/year_month_day/io.cc | 43 +++++++++- 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/libstdc++-v3/include/bits/chrono_io.h b/libstdc++-v3/include/bits/chrono_io.h index 593a927811a6..ff7e8cfe6b9f 100644 --- a/libstdc++-v3/include/bits/chrono_io.h +++ b/libstdc++-v3/include/bits/chrono_io.h @@ -572,12 +572,12 @@ namespace __format auto __finalize = [this, &__spec, &__def] { using enum _ChronoParts; - _ChronoParts __checked + _ChronoParts __checked = __spec._M_debug ? _YearMonthDay|_IndexedWeekday - : _Month|_Weekday; + : _Month|_Weekday; // n.b. for calendar types __def._M_needed contains only parts // copied from the input, remaining ones are computed, and thus ok - __spec._M_needs_ok_check + __spec._M_needs_ok_check = __spec._M_needs(__def._M_needed & __checked); _M_spec = __spec; }; @@ -693,7 +693,8 @@ namespace __format break; case 'g': case 'G': - __needed = _LocalDays|_Weekday; + case 'V': + __needed = _LocalDays|_Year|_DayOfYear|_Weekday; break; case 'H': case 'I': @@ -741,9 +742,8 @@ namespace __format __allowed_mods = _Mod_O; break; case 'U': - case 'V': case 'W': - __needed = _LocalDays|_Year|_DayOfYear|_Weekday; + __needed = _DayOfYear|_Weekday; __allowed_mods = _Mod_O; break; case 'x': @@ -1147,7 +1147,8 @@ namespace __format break; case 'g': case 'G': - __out = _M_g_G(__t, std::move(__out), __c == 'G'); + case 'V': + __out = _M_g_G_V(__t, std::move(__out), __c); break; case 'H': case 'I': @@ -1189,9 +1190,8 @@ namespace __format __out = _M_u_w(__t._M_weekday, std::move(__out), __c); break; case 'U': - case 'V': case 'W': - __out = _M_U_V_W(__t, std::move(__out), __c); + __out = _M_U_W(__t, std::move(__out), __c); break; case 'z': __out = _M_z(__t._M_zone_offset, std::move(__out), (bool)__mod); @@ -1441,18 +1441,47 @@ namespace __format template<typename _OutIter> _OutIter - _M_g_G(const _ChronoData<_CharT>& __t, _OutIter __out, - bool __full) const + _M_g_G_V(const _ChronoData<_CharT>& __t, _OutIter __out, + _CharT __conv) const { - // %g last two decimal digits of the ISO week-based year. - // %G ISO week-based year. + // %g last two decimal digits of the ISO week-based year. + // %G ISO week-based year. + // %V ISO week-based week number as a decimal number. + // %OV Locale's alternative numeric rep. + + // ISO week-based year of __t is the year that contains the nearest + // Thursday. The ISO week of __t is the number of weeks since + // January 1 of that year. + using namespace chrono; - auto __d = __t._M_ldays; - // Move to nearest Thursday: - __d -= (__t._M_weekday - Monday) - days(3); - // ISO week-based year is the year that contains that Thursday: - year __y = year_month_day(__d).year(); - return _M_C_y_Y(__y, std::move(__out), "yY"[__full]); + // Offset of the nearest Thursday: + const days __offset = (__t._M_weekday - Monday) - days(3); + // Nearest Thursday as local days: + const local_days __ild = __t._M_ldays - __offset; + // Day of year of nearest Thursday: + days __idoy = __t._M_day_of_year - __offset; + + // Year of nearest Thursday: + year __iyear; + if (__idoy <= days(0)) + __iyear = __t._M_year - years(1); + else if (__idoy <= days(365)) + __iyear = __t._M_year; + else if (__idoy == days(366) && __t._M_year.is_leap()) + __iyear = __t._M_year; + else if (__idoy <= days(730)) + __iyear = __t._M_year + years(1); + else [[unlikely]] + __iyear = year_month_day(__ild).year(); + + if (__conv != 'V') + return _M_C_y_Y(__iyear, std::move(__out), "yY"[__conv == 'G']); + + if (__iyear != __t._M_year) + __idoy = __ild - local_days(__iyear/January/0); + + const auto __wi = chrono::floor<weeks>(__idoy - days(1)).count() + 1; + return __format::__write(std::move(__out), _S_two_digits(__wi)); } template<typename _OutIter> @@ -1709,35 +1738,19 @@ namespace __format template<typename _OutIter> _OutIter - _M_U_V_W(const _ChronoData<_CharT>& __t, _OutIter __out, - _CharT __conv) const + _M_U_W(const _ChronoData<_CharT>& __t, _OutIter __out, + _CharT __conv) const { // %U Week number of the year as a decimal number, from first Sunday. // %OU Locale's alternative numeric rep. - // %V ISO week-based week number as a decimal number. - // %OV Locale's alternative numeric rep. // %W Week number of the year as a decimal number, from first Monday. // %OW Locale's alternative numeric rep. - using namespace chrono; - auto __d = __t._M_ldays; - local_days __first; // First day of week 1. - if (__conv == 'V') // W01 begins on Monday before first Thursday. - { - // Move to nearest Thursday: - __d -= (__t._M_weekday - Monday) - days(3); - // ISO week of __t is number of weeks since January 1 of the - // same year as that nearest Thursday. - __first = local_days(year_month_day(__d).year()/January/1); - } - else - { - const weekday __weekstart = __conv == 'U' ? Sunday : Monday; - __first = local_days(__t._M_year/January/__weekstart[1]); - } - auto __weeks = chrono::floor<weeks>(__d - __first); - __string_view __sv = _S_two_digits(__weeks.count() + 1); - return __format::__write(std::move(__out), __sv); + using namespace chrono; + const weekday __weekstart = __conv == 'U' ? Sunday : Monday; + const days __offset = __t._M_weekday - __weekstart; + auto __weeks = chrono::floor<weeks>(__t._M_day_of_year - __offset - days(1)); + return __format::__write(std::move(__out), _S_two_digits(__weeks.count() + 1)); } template<typename _OutIter> diff --git a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc index 7b09ff4b95af..5ce2794347a1 100644 --- a/libstdc++-v3/testsuite/std/time/year_month_day/io.cc +++ b/libstdc++-v3/testsuite/std/time/year_month_day/io.cc @@ -49,6 +49,12 @@ test_format() VERIFY( s == "Day 6 (Sat) of Week 00 of 2022" ); s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/January/2); VERIFY( s == "Day 0 (Sun) of Week 01 of 2022" ); + s = std::format("Day {:%w (%a) of Week %U of %Y}", 2024y/January/1); + VERIFY( s == "Day 1 (Mon) of Week 00 of 2024" ); + s = std::format("Day {:%w (%a) of Week %U of %Y}", 2024y/January/7); + VERIFY( s == "Day 0 (Sun) of Week 01 of 2024" ); + s = std::format("Day {:%w (%a) of Week %U of %Y}", 2024y/January/8); + VERIFY( s == "Day 1 (Mon) of Week 01 of 2024" ); s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/Quindecember/20); VERIFY( s == "Day 1 (Mon) of Week 73 of 2022" ); // %W: Week number for weeks starting on Monday @@ -56,7 +62,13 @@ test_format() VERIFY( s == "Day 7 (Sun) of Week 00 of 2022" ); s = std::format("Day {:%u (%a) of Week %W of %Y}", 2022y/January/3); VERIFY( s == "Day 1 (Mon) of Week 01 of 2022" ); - s = std::format("Day {:%w (%a) of Week %U of %Y}", 2022y/Quindecember/20); + s = std::format("Day {:%w (%a) of Week %W of %Y}", 2019y/January/1); + VERIFY( s == "Day 2 (Tue) of Week 00 of 2019" ); + s = std::format("Day {:%w (%a) of Week %W of %Y}", 2019y/January/7); + VERIFY( s == "Day 1 (Mon) of Week 01 of 2019" ); + s = std::format("Day {:%w (%a) of Week %W of %Y}", 2019y/January/8); + VERIFY( s == "Day 2 (Tue) of Week 01 of 2019" ); + s = std::format("Day {:%w (%a) of Week %W of %Y}", 2022y/Quindecember/20); VERIFY( s == "Day 1 (Mon) of Week 73 of 2022" ); // %G: ISO week-calendar year (ISO 8601) @@ -65,6 +77,8 @@ test_format() VERIFY( s == "1976-W53" ); s = std::format("{:%G-W%V}", 1977y/1/2); VERIFY( s == "1976-W53" ); + s = std::format("{:%G-W%V}", 1977y/1/3); + VERIFY( s == "1977-W01" ); s = std::format("{:%G-W%V}", 1977y/12/31); VERIFY( s == "1977-W52" ); s = std::format("{:%G-W%V}", 1978y/1/1); @@ -84,6 +98,33 @@ test_format() s = std::format("{:%G-W%V}", 1980y/18/20); VERIFY( s == "1981-W26" ); + // "Leap weak" on year starting on Thursday + s = std::format("{:%G-W%V}", 2009y/12/31); + VERIFY( s == "2009-W53" ); + s = std::format("{:%G-W%V}", 2010y/1/1); + VERIFY( s == "2009-W53" ); + s = std::format("{:%G-W%V}", 2010y/1/3); + VERIFY( s == "2009-W53" ); + s = std::format("{:%G-W%V}", 2010y/1/4); + VERIFY( s == "2010-W01" ); + + // "Leap weak" on leap year stating on Wednesday + // 2020/Dec/31 is Thurday, thus 366 day of year + s = std::format("{:%G-W%V}", 2020y/12/30); + VERIFY( s == "2020-W53" ); + s = std::format("{:%G-W%V}", 2020y/12/31); + VERIFY( s == "2020-W53" ); + s = std::format("{:%G-W%V}", 2021y/1/1); + VERIFY( s == "2020-W53" ); + s = std::format("{:%G-W%V}", 2021y/1/3); + VERIFY( s == "2020-W53" ); + s = std::format("{:%G-W%V}", 2021y/1/4); + VERIFY( s == "2021-W01" ); + s = std::format("{:%G-W%V}", 2021y/1/7); + VERIFY( s == "2021-W01" ); + s = std::format("{:%G-W%V}", 2021y/1/8); + VERIFY( s == "2021-W01" ); + s = std::format("{:%x}", 2022y/December/19); VERIFY( s == "12/19/22" ); s = std::format("{:L%x}", 2022y/December/19);