[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