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

Tomasz Kaminski tkaminsk@redhat.com
Thu Aug 27 10:05:57 GMT 2026


On Thu, Aug 27, 2026 at 11:30 AM Jonathan Wakely <jwakely@redhat.com> wrote:

> On Thu, 27 Aug 2026 at 08:32, Tomasz Kamiński <tkaminsk@redhat.com> wrote:
> >
> > Expand switch and if-check to check both lowercase and uppercase
> > letter.
> >
> > For testing, we use test_combinations, that checks minimum prefix
> > (few characters) in all combination of letter cases, and whole
> > world and midpoint length, by combining above with all lowercase
> > or uppercase suffix.
> >
> > 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. Replace if with switch for matching 'a' in
> >         March/May for consistency.
> >         (operator>>(istream&, on_month_day&)): Check for 'L'.
> >         * testsuite/std/time/tzdb/name_matching.cc: New test.
> > ---
> > Knowing the file format by hearth (now), made writting the test checking
> > if each name (and prefix) is interpreted correctly (instead of checking
> > only if it is not causing errors) much faster than I expected.
> >
> > Testing on x86_64-linux. new test already passed in all standard modes.
> > OK for trunk? This is non invasive change, so I would backport it to
> > GCC-16 (when C++20 is stable).
> >
> >  libstdc++-v3/src/c++20/tzdb.cc                |  56 ++-
> >  .../testsuite/std/time/tzdb/name_matching.cc  | 345 ++++++++++++++++++
> >  2 files changed, 395 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..1421cd85b3a 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';
>
> Is this necessary?
> We never inspect the type variable, it's just used to extract a word.
>
Not for the rule and link at this point, but it is necessary for Zone, as
default branch
validates that the type is zone, and I think it is better to write to the
memory location
we just overwrote than do comparison against two letters on each line.

And I just made it applied consistently, as I do not think that this will
have measureable
impact.


> > +
> >                 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,80 @@ 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]]
> > +       switch (s[1])
> > +       {
> > +       case 'a':
> > +       case 'A':
>
> I don't really like changing from 'if' to a 'switch' with only one
> case (well, two, but they have the same statements following the case
> labels).
>
> I would find `if (s[1] == 'a' || s[1] == 'A') easier to parse.
>
OK, will change that.

>
> >           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 +2571,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 +2623,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 = "oOnNdDaAyYeEsSrRiItTuU";
>
> Since we expect the characters to be lower case the find(c) call below
> should be faster if we use:
>
> day_chars = "ondayesrituONDAYESRITU";
>
> because we won't have to search as far to find the most likely characters.
>
> We could take that to the extreme and sort them by frequency, so maybe
> something like "daysneroitu" ... but on the other hand, this code will
> probably never run at all for real tzdata.zi files. There are no
> redundant chars in the names of weekdays, so after the switch matches
> an unambiguous prefix, we will always have c == eof for the real
> files.
>
> I think putting the uppercase ones last is worth it though.
>
>
> >         auto is_day_char = [&day_chars](int c) {
> >           return c != char_traits<char>::eof()
> >                    && day_chars.find((char)c) != day_chars.npos;
> > @@ -2613,7 +2657,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..ccd6f6b400e
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/std/time/tzdb/name_matching.cc
> > @@ -0,0 +1,345 @@
> > +// { 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* } }
>
> This new test takes over 6s on my laptop, it will be much slower on
> some low-power targets.
>
> I still think exhaustively testing every combination is overkill
> because testing "monday" and "MONDAY" exercises every code path in the
> current implementation, there's no need to test "mOnDaY" and "MONday"
> etc.
>
It does not check if we properly handle seeing just 'm' or "M". Also the
loop
It does not check all letter combinations, only the prefix, so we get
m
M
mONDAY
MONDAY
monday
mONDAY
mON
MON
mon
mON


I can reduce above to check only for, minimum prefix and the whole word,
so something like:
m
M
monday
MONDAY



> If we ever change the structure of the parsing implementation, we
> could add more exhaustive tests then.
>
> > +
> > +#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)
> > +  {
> > +    const size_t mid = (min + txt.size()) / 2;
> > +    const unsigned mask = (1u << min) - 1;
> > +
> > +    std::string lower(txt);
> > +    std::string upper(txt);
> > +    for (char& c : upper)
> > +      c = std::toupper(c);
> > +
> > +    unsigned pattern = 0;
> > +    do
> > +    {
> > +      for (size_t i = 0; i < min; ++i)
> > +       lower[i] = upper[i]
> > +         = (pattern & (1u << i)) ? std::toupper(txt[i]) : txt[i];
> > +
> > +      func(lower, args...);
> > +      if (min < txt.size())
> > +      {
> > +       func(lower.substr(0, min), args...);
> > +       func(upper, args...);
> > +      }
> > +      if (mid != min)
> > +      {
> > +       func(lower.substr(0, mid), args...);
> > +       func(upper.substr(0, mid), args...);
> > +      }
> > +      ++pattern;
> > +    }
> > +    while (pattern & mask);
>
> Maybe this loop could break after just two iterations for simulator
> targets (using a macro like in
> testsuite/25_algorithms/partial_sort/random_test.cc and other slow
> tests).
>
I will reduce it only to 4 calls, which should help here.

>
> > +  }
> > +
> > +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");
> > +  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()
> > +{
> > +  auto test_min  = [](std::string_view name) { test_year(name, "max",
> "only"); };
> > +  auto test_max  = [](std::string_view name) { test_year("MIN", name,
> "ONLY"); };
> > +  auto test_only = [](std::string_view name) { test_year("Min", "Max",
> name); };
> > +
> > +  test_combinations(test_min, "min", 1);
> > +  test_combinations(test_max, "max", 1);
> > +  test_combinations(test_only, "only", 1);
> > +}
> > +
> > +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()
> > +{
> > +  auto test_rule = [](std::string_view name) { test_line(name, "Zone",
> "Link"); };
> > +  auto test_zone = [](std::string_view name) { test_line("rule", name,
> "link"); };
> > +  auto test_link = [](std::string_view name) { test_line("RULE",
> "ZONE", name); };
> > +
> > +  test_combinations(test_rule, "rule", 1);
> > +  test_combinations(test_zone, "zone", 1);
> > +  test_combinations(test_link, "link", 1);
> > +}
> > +
> > +int main()
> > +{
> > +  test_days();
> > +  test_months();
> > +  test_years();
> > +  test_times();
> > +  test_lines();
> > +}
> > --
> > 2.55.0
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260827/3ea15fb9/attachment-0001.htm>


More information about the Libstdc++ mailing list