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