Bring text parsing into compliance with the tzdata spec

* Fixes #836
This commit is contained in:
Howard Hinnant 2024-09-12 15:51:49 +01:00
parent 1ead6715de
commit 155c6b9e76

View File

@ -581,27 +581,57 @@ get_tzdb_list()
return tz_db; return tz_db;
} }
inline
static
char
tolower(char c)
{
return static_cast<char>(std::tolower(c));
}
inline
static
void
tolower(std::string& s)
{
for (auto& c : s)
c = tolower(c);
}
inline
static static
std::string std::string
parse3(std::istream& in) get_alpha_word(std::istream& in)
{ {
std::string r(3, ' '); std::string s;
ws(in); while (!in.eof() && std::isalpha(in.peek()))
r[0] = static_cast<char>(in.get()); s.push_back(static_cast<char>(in.get()));
r[1] = static_cast<char>(in.get()); return s;
r[2] = static_cast<char>(in.get()); }
return r;
inline
static
bool
is_prefix_of(std::string const& key, std::string const& value)
{
return key.compare(0, key.size(), value, 0, key.size()) == 0;
} }
static static
unsigned unsigned
parse_month(std::istream& in) parse_month(std::istream& in)
{ {
CONSTDATA char*const month_names[] = static std::string const month_names[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", {"january", "february", "march", "april", "may", "june",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; "july", "august", "september", "october", "november", "december"};
auto s = parse3(in); auto s = get_alpha_word(in);
auto m = std::find(std::begin(month_names), std::end(month_names), s) - month_names; tolower(s);
auto m = std::find_if(std::begin(month_names), std::end(month_names),
[&s](std::string const& m)
{
return is_prefix_of(s, m);
})
- month_names;
if (m >= std::end(month_names) - std::begin(month_names)) if (m >= std::end(month_names) - std::begin(month_names))
throw std::runtime_error("oops: bad month name: " + s); throw std::runtime_error("oops: bad month name: " + s);
return static_cast<unsigned>(++m); return static_cast<unsigned>(++m);
@ -817,10 +847,16 @@ static
unsigned unsigned
parse_dow(std::istream& in) parse_dow(std::istream& in)
{ {
CONSTDATA char*const dow_names[] = static std::string const dow_names[] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; {"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"};
auto s = parse3(in); auto s = get_alpha_word(in);
auto dow = std::find(std::begin(dow_names), std::end(dow_names), s) - dow_names; tolower(s);
auto dow = std::find_if(std::begin(dow_names), std::end(dow_names),
[&s](std::string const& dow)
{
return is_prefix_of(s, dow);
})
- dow_names;
if (dow >= std::end(dow_names) - std::begin(dow_names)) if (dow >= std::end(dow_names) - std::begin(dow_names))
throw std::runtime_error("oops: bad dow name: " + s); throw std::runtime_error("oops: bad dow name: " + s);
return static_cast<unsigned>(dow); return static_cast<unsigned>(dow);
@ -1081,7 +1117,7 @@ detail::operator>>(std::istream& is, MonthDayTime& x)
auto m = parse_month(is); auto m = parse_month(is);
if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#') if (!is.eof() && ws(is) && !is.eof() && is.peek() != '#')
{ {
if (is.peek() == 'l') if (tolower(is.peek()) == 'l')
{ {
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
is.get(); is.get();
@ -3642,22 +3678,23 @@ init_tzdb()
std::istringstream in(line); std::istringstream in(line);
std::string word; std::string word;
in >> word; in >> word;
if (word == "Rule") tolower(word);
if (is_prefix_of(word, "rule"))
{ {
db->rules.push_back(Rule(line)); db->rules.push_back(Rule(line));
continue_zone = false; continue_zone = false;
} }
else if (word == "Link") else if (is_prefix_of(word, "link"))
{ {
db->links.push_back(time_zone_link(line)); db->links.push_back(time_zone_link(line));
continue_zone = false; continue_zone = false;
} }
else if (word == "Leap") else if (is_prefix_of(word, "leap"))
{ {
db->leap_seconds.push_back(leap_second(line, detail::undocumented{})); db->leap_seconds.push_back(leap_second(line, detail::undocumented{}));
continue_zone = false; continue_zone = false;
} }
else if (word == "Zone") else if (is_prefix_of(word, "zone"))
{ {
db->zones.push_back(time_zone(line, detail::undocumented{})); db->zones.push_back(time_zone(line, detail::undocumented{}));
continue_zone = true; continue_zone = true;