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

Jonathan Wakely jwakely@redhat.com
Thu Aug 27 11:16:23 GMT 2026


On Thu, 27 Aug 2026 at 11:06, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> 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.

OK, fair enough.

>
>>
>> > +
>> >                 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.

Thanks. Looking at the v2 patch now ...


>>
>>
>> > +  }
>> > +
>> > +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
>> >
>>



More information about the Libstdc++ mailing list