Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion arrow/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,19 @@ def _generate_choice_re(
:returns: A compiled regular expression pattern that matches any of the choices.
:rtype: re.Pattern[str]
"""
return re.compile(r"({})".format("|".join(choices)), flags=flags)
# The choices are locale text, not patterns, so escape them: several
# locales abbreviate with a trailing "." and an unescaped one matches
# any character. Longest first, because the alternation takes the first
# branch that matches and some choices are prefixes of others.
return re.compile(
r"({})".format(
"|".join(
re.escape(choice)
for choice in sorted(choices, key=len, reverse=True)
)
),
flags=flags,
)


class TzinfoParser:
Expand Down
41 changes: 41 additions & 0 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,47 @@ def test_localized_month_abbreviation(self):
assert parser_.parse("2013-Gen-01", "YYYY-MMM-DD") == datetime(2013, 1, 1)


class TestDateTimeParserLocaleChoices:
def test_month_name_sharing_a_prefix(self):
# Vietnamese October is a prefix of both November and December. The
# alternation matches the first branch that fits, so with nothing after
# the token to force the regex to backtrack, the shorter name used to
# win and the rest of the input was left unconsumed.
parser_ = parser.DateTimeParser("vi")

assert parser_.parse("Tháng Mười", "MMMM").month == 10
assert parser_.parse("Tháng Mười Một", "MMMM").month == 11
assert parser_.parse("Tháng Mười Hai", "MMMM").month == 12
assert parser_.parse("2013 Tháng Mười Hai", "YYYY MMMM") == datetime(
2013, 12, 1
)

def test_day_abbreviation_ending_in_a_dot(self):
# Thai abbreviates with a trailing ".", which is locale text and not
# a wildcard: "พ." must not match the "พฤ" of "พฤ.".
parser_ = parser.DateTimeParser("th")

for abbreviation, isoweekday in [
("จ.", 1),
("อ.", 2),
("พ.", 3),
("พฤ.", 4),
("ศ.", 5),
("ส.", 6),
("อา.", 7),
]:
assert parser_.parse(abbreviation, "ddd").isoweekday() == isoweekday

def test_day_abbreviation_rejects_a_different_character(self):
# The "." must not stand in for an arbitrary character, which used to
# match and then fail the name lookup with ValueError.
for locale_name, text in [("th", "พX"), ("ca", "dlX"), ("hy", "երկX")]:
parser_ = parser.DateTimeParser(locale_name)

with pytest.raises(ParserError):
parser_.parse(text, "ddd")


@pytest.mark.usefixtures("dt_parser")
class TestDateTimeParserMeridians:
def test_meridians_lowercase(self):
Expand Down
Loading