[PATCH v2] libstdc++: Make chrono::tzdb parser case-insensive [PR123510]

Jonathan Wakely jwakely@redhat.com
Thu Aug 27 12:04:57 GMT 2026


On Thu, 27 Aug 2026 at 11:35, Tomasz Kamiński <tkaminsk@redhat.com> wrote:
>
> 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.
> ---
> v2:
> * explains why type is updated in commit message
> * reduces test cases to prefix/word upper/lower case, the time of test
>   exacutable is 0.006 on my machine

Ah I think I included the time to run dejagnu and compile the test ...
so 6s is not a fair number.

v2 is OK for trunk and gcc-16, thanks.

> * for years/line simply list four combinations, instead of generating
>   4 for each argument
> * changed switch back to if in abbrev_month matching
> * added uppercase letters after lower case letters in days_chars
>
>  libstdc++-v3/src/c++20/tzdb.cc                |  52 ++-
>  .../testsuite/std/time/tzdb/name_matching.cc  | 318 ++++++++++++++++++
>  2 files changed, 364 insertions(+), 6 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/std/time/tzdb/name_matching.cc
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc
> index 0d004a8a6fe..0ffb3c014d1 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 00000000000..4c70e986a01
> --- /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 foreve
> +  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();
> +}
> --
> 2.55.0
>



More information about the Libstdc++ mailing list