[gcc r17-3705] libstdc++: Make chrono::tzdb parser case-insensive [PR123510]
Tomasz Kaminski
tkaminsk@gcc.gnu.org
Thu Aug 27 13:31:46 GMT 2026
https://gcc.gnu.org/g:2509f241a097ef247fc4814c5614e3c4e3cc70d2
commit r17-3705-g2509f241a097ef247fc4814c5614e3c4e3cc70d2
Author: Tomasz Kamiński <tkaminsk@redhat.com>
Date: Thu Aug 27 07:44:59 2026 +0200
libstdc++: Make chrono::tzdb parser case-insensive [PR123510]
Expand switch and if-check to check both lowercase and uppercase letter.
For line parsing in chrono::reload_tzdb, the type is inspected by default
branch (check for 'Z'), this patch normalizes it for all branches
consistently.
For testing, we check minimum unambigous prefix and whole world as both
lowercase and uppercase. This covers all code paths.
libstdc++-v3/ChangeLog:
PR libstdc++/123510
* src/c++20/tzdb.cc (at_time::is_indicator): Add case labels
for uppercase letters.
(operator>>(istream&, years_from_to)): Check for 'M' and 'O',
(chrono::reload_tzdb): Add case labels for lowercase letters,
and normalize first letter of type.
(operator>>(istream&, abbrev_month&)): Match all cases in
switch and days_chars.
(operator>>(istream&, on_month_day&)): Check for 'L'.
* testsuite/std/time/tzdb/name_matching.cc: New test.
Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
Diff:
---
libstdc++-v3/src/c++20/tzdb.cc | 52 +++-
.../testsuite/std/time/tzdb/name_matching.cc | 318 +++++++++++++++++++++
2 files changed, 364 insertions(+), 6 deletions(-)
diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
index 0d004a8a6fea..0ffb3c014d19 100644
--- a/libstdc++-v3/src/c++20/tzdb.cc
+++ b/libstdc++-v3/src/c++20/tzdb.cc
@@ -272,14 +272,20 @@ namespace std::chrono
switch (c)
{
case 's':
+ case 'S':
return {Standard, true};
case 'u':
+ case 'U':
case 'g':
+ case 'G':
case 'z':
+ case 'Z':
return {Universal, true};
case 'w':
+ case 'W':
return {Wall, true};
case 'd':
+ case 'D':
return {Daylight, true};
default:
return {Wall, false};
@@ -399,7 +405,7 @@ namespace std::chrono
{
string s;
auto c = ws(in).peek();
- if (c == 'm') [[unlikely]] // keyword "minimum"
+ if (c == 'm' || c == 'M') [[unlikely]] // keyword "minimum"
{
in >> s; // extract the rest of the word
yy.from = year(1900);
@@ -408,12 +414,12 @@ namespace std::chrono
yy.from = year{num};
c = ws(in).peek();
- if (c == 'm') // keyword "maximum"
+ if (c == 'm' || c == 'M') // keyword "maximum"
{
in >> s; // extract the rest of the word
yy.to = year::max();
}
- else if (c == 'o') // keyword "only"
+ else if (c == 'o' || c == 'O') // keyword "only"
{
in >> s; // extract the rest of the word
yy.to = yy.from;
@@ -1969,28 +1975,37 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
{
case '#':
break;
+ case 'r':
case 'R':
{
// Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
is >> type; // extract the "Rule" or "R" marker
+ type[0] = 'R';
+
Rule rule;
is >> rule;
node->rules.push_back(std::move(rule));
break;
}
+ case 'l':
case 'L':
{
// Link TARGET LINK-NAME
is >> type; // extract the "Link" or "L" marker
+ type[0] = 'L';
+
time_zone_link link(nullptr);
is >> quoted(link._M_target) >> quoted(link._M_name);
node->db.links.push_back(std::move(link));
break;
}
+ case 'z':
case 'Z':
{
// Zone NAME STDOFF RULES FORMAT [UNTIL]
is >> type; // extract the "Zone" or "Z" marker
+ type[0] = 'Z';
+
time_zone tz(std::make_unique<time_zone::_Impl>(node));
is >> quoted(tz._M_name);
node->db.zones.push_back(time_zone(std::move(tz)));
@@ -2459,60 +2474,76 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
in >> s;
switch (s[0])
{
+ case 'j':
case 'J':
switch (s[1])
{
case 'a':
+ case 'A':
am.m = January;
return in;
case 'u':
+ case 'U':
switch (s[2])
{
case 'n':
+ case 'N':
am.m = June;
return in;
case 'l':
+ case 'L':
am.m = July;
return in;
}
break;
}
break;
+ case 'f':
case 'F':
am.m = February;
return in;
+ case 'm':
case 'M':
- if (s[1] == 'a') [[likely]]
+ if (s[1] == 'a' || s[1] == 'A') [[likely]]
switch (s[2])
{
case 'r':
+ case 'R':
am.m = March;
return in;
case 'y':
+ case 'Y':
am.m = May;
return in;
}
break;
+ case 'a':
case 'A':
switch (s[1])
{
case 'p':
+ case 'P':
am.m = April;
return in;
case 'u':
+ case 'U':
am.m = August;
return in;
}
break;
+ case 's':
case 'S':
am.m = September;
return in;
+ case 'o':
case 'O':
am.m = October;
return in;
+ case 'n':
case 'N':
am.m = November;
return in;
+ case 'd':
case 'D':
am.m = December;
return in;
@@ -2536,37 +2567,46 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
// Just peek at one char at a time.
switch (in.peek())
{
+ case 'm':
case 'M':
aw.wd = Monday;
break;
+ case 't':
case 'T':
in.ignore(1); // Discard the 'T'
switch (in.peek())
{
case 'u':
+ case 'U':
aw.wd = Tuesday;
break;
case 'h':
+ case 'H':
aw.wd = Thursday;
break;
default:
in.setstate(ios::failbit);
}
break;
+ case 'w':
case 'W':
aw.wd = Wednesday;
break;
+ case 'f':
case 'F':
aw.wd = Friday;
break;
+ case 's':
case 'S':
in.ignore(1); // Discard the 'S'
switch (in.peek())
{
case 'a':
+ case 'A':
aw.wd = Saturday;
break;
case 'u':
+ case 'U':
aw.wd = Sunday;
break;
default:
@@ -2579,7 +2619,7 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
in.ignore(1); // Discard whichever char we just looked at.
// Discard any remaining chars from weekday, e.g. "onday".
- string_view day_chars = "ondayesritu";
+ string_view day_chars = "ondayesrituONDAYESRITU";
auto is_day_char = [&day_chars](int c) {
return c != char_traits<char>::eof()
&& day_chars.find((char)c) != day_chars.npos;
@@ -2613,7 +2653,7 @@ constinit tzdb_list::_Node::NumLeapSeconds tzdb_list::_Node::num_leap_seconds;
return in;
}
}
- else if (c == 'l') // lastSunday, lastWed, ...
+ else if (c == 'l' || c == 'L') // lastSunday, lastWed, ...
{
in.ignore(4);
if (abbrev_weekday w{}; in >> w) [[likely]]
diff --git a/libstdc++-v3/testsuite/std/time/tzdb/name_matching.cc b/libstdc++-v3/testsuite/std/time/tzdb/name_matching.cc
new file mode 100644
index 000000000000..8d845ab94b97
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/time/tzdb/name_matching.cc
@@ -0,0 +1,318 @@
+// { dg-do run { target c++20 } }
+// { dg-require-effective-target tzdb }
+// { dg-require-effective-target cxx11_abi }
+// { dg-xfail-run-if "no weak override on AIX" { powerpc-ibm-aix* } }
+
+#include <chrono>
+#include <fstream>
+#include <format>
+#include <testsuite_hooks.h>
+#include <iostream>
+
+static bool override_used = false;
+
+namespace __gnu_cxx
+{
+ const char* zoneinfo_dir_override() {
+ override_used = true;
+ return "./";
+ }
+}
+
+using namespace std::chrono;
+
+template<typename Functor, typename... Args>
+ void
+ test_combinations(Functor&& func, std::string_view txt, size_t min, Args&&... args)
+ {
+ std::string lower(txt);
+ std::string upper(txt);
+ for (char& c : upper)
+ c = std::toupper(c);
+
+ func(lower.substr(0, min), args...);
+ func(upper.substr(0, min), args...);
+ func(lower, args...);
+ func(upper, args...);
+ }
+
+void
+test_day(std::string_view name, weekday wd)
+{
+ constexpr const char* templ = R"(# version test_day_{0}
+ # Zone NAME STDOFF RULES FORMAT [UNTIL]
+ Zone Test 0 - Z1 2000 May last{0}
+ 0 - Z2 2010 May {0}>=10
+ 0 - Z3
+ )";
+
+ std::string input = std::format(templ, name);
+ std::ofstream("tzdata.zi") << input;
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used ); // If this fails then XFAIL for the target.
+
+ std::string_view ver = db.version;
+ VERIFY( ver.starts_with("test_day_") );
+ ver.remove_prefix(9);
+ VERIFY( ver == name );
+
+ sys_info info = locate_zone("Test")->get_info(sys_days(2005y/January/1));
+ VERIFY( info.begin == sys_days(2000y/May/wd[last]) );
+ year_month_day ymd(ceil<days>(info.end));
+ VERIFY( ymd.year() == 2010y );
+ VERIFY( ymd.month() == May );
+ VERIFY( ymd.day() >= day(10) );
+ VERIFY( weekday(ymd) == wd );
+}
+
+void
+test_last(std::string_view name)
+{
+ constexpr const char* templ = R"(# version test_{0}
+ # Zone NAME STDOFF RULES FORMAT [UNTIL]
+ Zone Test 0 - Z1 2000 July {0}Mon
+ 0 - Z3
+ )";
+
+ std::string input = std::format(templ, name);
+ std::ofstream("tzdata.zi") << input;
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used ); // If this fails then XFAIL for the target.
+
+ std::string_view ver = db.version;
+ VERIFY( ver.starts_with("test_") );
+ ver.remove_prefix(5);
+ VERIFY( ver == name );
+
+ sys_info info = locate_zone("Test")->get_info(sys_days(2005y/January/1));
+ VERIFY( info.begin == sys_days(2000y/July/Monday[last]) );
+}
+
+void
+test_days()
+{
+ test_combinations(test_day, "monday", 1, Monday);
+ test_combinations(test_day, "tuesday", 2, Tuesday);
+ test_combinations(test_day, "wednesday", 1, Wednesday);
+ test_combinations(test_day, "thursday", 2, Thursday);
+ test_combinations(test_day, "friday", 1, Friday);
+ test_combinations(test_day, "saturday", 2, Saturday);
+ test_combinations(test_day, "sunday", 2, Sunday);
+
+ test_last("last");
+ test_last("LAST");
+}
+
+void
+test_month(std::string_view name, month m)
+{
+ constexpr const char* templ = R"(# version test_month_{0}
+ # Zone NAME STDOFF RULES FORMAT [UNTIL]
+ Zone Test 0 - Z1 2000 {0} 13
+ 0 - Z2 2010 {0}
+ 0 - Z3
+ )";
+
+ std::string input = std::format(templ, name);
+ std::ofstream("tzdata.zi") << input;
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used ); // If this fails then XFAIL for the target.
+
+ std::string_view ver = db.version;
+ VERIFY( ver.starts_with("test_month_") );
+ ver.remove_prefix(11);
+ VERIFY( ver == name );
+
+ sys_info info = locate_zone("Test")->get_info(sys_days(2005y/January/1));
+ VERIFY( info.begin == sys_days(2000y/m/13) );
+ VERIFY( info.end == sys_days(2010y/m/1) );
+}
+
+void
+test_months()
+{
+ test_combinations(test_month, "january", 2, January);
+ test_combinations(test_month, "february", 1, February);
+ test_combinations(test_month, "march", 3, March);
+ test_combinations(test_month, "april", 2, April);
+ test_combinations(test_month, "may", 3, May);
+ test_combinations(test_month, "june", 3, June);
+ test_combinations(test_month, "july", 3, July);
+ test_combinations(test_month, "august", 3, August);
+ test_combinations(test_month, "september", 1, September);
+ test_combinations(test_month, "october", 1, October);
+ test_combinations(test_month, "november", 1, November);
+ test_combinations(test_month, "december", 1, December);
+}
+
+void
+test_year(std::string_view min, std::string_view max, std::string_view only)
+{
+ constexpr const char* templ = R"(# version {0}
+ # Rule NAME FROM TO - IN ON AT SAVE LETTER/S
+ Rule Rule {1} 2020 - Jan 10 0u 0 S
+ Rule Rule 2000 {2} - Nov 12 0u 1 D
+ Rule Rule 2010 {3} - Aug 11 0u 2 O
+ Rule Rule 2020 {2} - Jan 13 0u 0 S
+ # Zone NAME STDOFF RULES FORMAT [UNTIL]
+ Zone Test 0 - Z1 1800 Jan 1 0u
+ 0 Rule Z%s 3001 Jan 1 0u
+ 0 - Zl
+ )";
+
+ const std::string ver = std::format("test_{}_{}_{}", min, max, only);
+ std::string input = std::format(templ, ver, min, max, only);
+ std::ofstream("tzdata.zi") << input;
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used ); // If this fails then XFAIL for the target.
+ VERIFY( db.version == ver );
+
+ const time_zone* zone = locate_zone("Test");
+
+ // min applies since 1900, check first transition
+ sys_info info = zone->get_info(sys_days(1900y/January/1));
+ VERIFY( info.begin == sys_days(1800y/January/1) );
+ VERIFY( info.end == sys_days(1900y/January/10) );
+
+ // Check 2010 only rule does not apply in 2009
+ info = zone->get_info(sys_days(2009y/February/1));
+ VERIFY( info.begin == sys_days(2009y/January/10) );
+ VERIFY( info.end == sys_days(2009y/November/12) );
+
+ // Check 2010 only rule in 2010
+ info = zone->get_info(sys_days(2010y/February/1));
+ VERIFY( info.begin == sys_days(2010y/January/10) );
+ VERIFY( info.end == sys_days(2010y/August/11) );
+
+ info = zone->get_info(sys_days(2010y/October/1));
+ VERIFY( info.begin == sys_days(2010y/August/11) );
+ VERIFY( info.end == sys_days(2010y/November/12) );
+
+ // Check 2010 only rule does not apply in 2011
+ info = zone->get_info(sys_days(2011y/February/1));
+ VERIFY( info.begin == sys_days(2011y/January/10) );
+ VERIFY( info.end == sys_days(2011y/November/12) );
+
+ // max rule applies forever
+ info = zone->get_info(sys_days(2500y/February/1));
+ VERIFY( info.begin == sys_days(2500y/January/13) );
+ VERIFY( info.end == sys_days(2500y/November/12) );
+
+ info = zone->get_info(sys_days(3000y/December/1));
+ VERIFY( info.begin == sys_days(3000y/November/12) );
+ VERIFY( info.end == sys_days(3001y/January/1) );
+}
+
+void
+test_years()
+{
+ test_year("min", "max", "only");
+ test_year("m", "m", "o");
+ test_year("MIN", "MAX", "ONLY");
+ test_year("M", "M", "O");
+}
+
+void
+test_time(char s, char u, char w, char d)
+{
+ constexpr const char* templ = R"(# version test_time_{0}{1}{2}{3}
+ # Zone NAME STDOFF RULES FORMAT [UNTIL]
+ Zone Test 1 0 Zs 2000 Jan 11 10{0}
+ 3 1 Zu 2005 Feb 12 11{1}
+ 5 1 Zw 2010 Mar 13 12{2}
+ 7 1 Zd 2015 Apr 14 13{3}
+ 9 0 Su 2020 May 15 14{1}
+ 10 0 Sw 2025 Jun 16 15{2}
+ 11 - Zl
+ )";
+
+ std::string input = std::format(templ, s, u, w, d);
+ std::ofstream("tzdata.zi") << input;
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used ); // If this fails then XFAIL for the target.
+
+ std::string_view ver = db.version;
+ VERIFY( ver.starts_with("test_time_") );
+ VERIFY( ver[10] == s );
+ VERIFY( ver[11] == u );
+ VERIFY( ver[12] == w );
+ VERIFY( ver[13] == d );
+
+ const time_zone* zone = locate_zone("Test");
+ sys_info info = zone->get_info(sys_days(2002y/January/1));
+ VERIFY( info.begin == sys_days(2000y/January/11) + 10h - 1h );
+ VERIFY( info.end == sys_days(2005y/February/12) + 11h );
+
+ info = zone->get_info(sys_days(2012y/January/1));
+ VERIFY( info.begin == sys_days(2010y/March/13) + 12h - 6h );
+ VERIFY( info.end == sys_days(2015y/April/14) + 13h - 8h );
+
+ info = zone->get_info(sys_days(2022y/January/1));
+ VERIFY( info.begin == sys_days(2020y/May/15) + 14h );
+ VERIFY( info.end == sys_days(2025y/June/16) + 15h - 10h );
+}
+
+void
+test_times()
+{
+ test_time('s', 'u', 'w', 'd');
+ test_time('S', 'U', 'W', 'D');
+ // Alternate spelling for Universal
+ test_time('s', 'g', 'w', 'd');
+ test_time('s', 'G', 'w', 'd');
+ test_time('s', 'z', 'w', 'd');
+ test_time('s', 'Z', 'w', 'd');
+}
+
+void
+test_line(std::string_view rule, std::string_view zone, std::string_view link)
+{
+ constexpr const char* templ = R"(# version {0}
+ # Rule NAME FROM TO - IN ON AT SAVE LETTER/S
+ {1} Rule min max - Jan 13 0u 0 D
+ {1} Rule min max - Oct 12 0u 1 S
+ # Zone NAME STDOFF RULES FORMAT [UNTIL]
+ {2} Test 0 - Z1 2000 Jan 13
+ 0 Rule Z%s
+ # Link TARGET LINK-NAME
+ {3} Test Link
+ )";
+
+ const std::string ver = std::format("test_{}_{}_{}", rule, zone, link);
+ std::string input = std::format(templ, ver, rule, zone, link);
+ std::ofstream("tzdata.zi") << input;
+
+ const auto& db = reload_tzdb();
+ VERIFY( override_used ); // If this fails then XFAIL for the target.
+ VERIFY( db.version == ver );
+
+ sys_info info = locate_zone("Test")->get_info(sys_days(2005y/February/1));
+ VERIFY( info.begin == sys_days(2005y/January/13) );
+ VERIFY( info.end == sys_days(2005y/October/12) );
+
+ const time_zone* target = locate_zone("Link");
+ VERIFY( target->name() == "Test" );
+}
+
+void
+test_lines()
+{
+ test_line("rule", "zone", "link");
+ test_line("r", "z", "l");
+ test_line("RULE", "ZONE", "LINK");
+ test_line("R", "Z", "L");
+}
+
+int main()
+{
+ test_days();
+ test_months();
+ test_years();
+ test_times();
+ test_lines();
+}
More information about the Libstdc++-cvs
mailing list