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
20 changes: 20 additions & 0 deletions arrow/arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,26 @@ def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow":
else:
change_value = int(num_match.group())

# Reject explicitly-negative values such as "in -1 hours".
# The digit-only num_pattern above never captures a sign,
# so the minus was previously dropped silently and
# "in -1 hours" produced the same (wrong) result as
# "in 1 hours". ``humanize`` always expresses direction
# through the past/future templates ("... ago" / "in ...")
# with a positive magnitude, so a negative number here is
# malformed input rather than a second, conflicting
# direction signal. Detect the sign by inspecting the
# character that immediately precedes the matched digits in
# the original input string.
num_start = match.start() + num_match.start()
if num_start > 0 and input_string[num_start - 1] == "-":
raise ValueError(
f"Invalid input string. Negative time value "
f"'-{num_match.group()}' is not supported; express "
"direction using the past ('... ago') or future "
"('in ...') form with a positive value instead."
)

# No time to update if now is the unit
if unit == "now":
unit_visited[unit] = True
Expand Down
23 changes: 23 additions & 0 deletions tests/test_arrow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2888,6 +2888,29 @@ def test_require_relative_unit(self, locale_list_no_weeks: List[str]):
with pytest.raises(ValueError):
arw.dehumanize(second_future_string, locale=lang)

def test_negative_values(self):
# Negative numeric values are malformed input: humanize only ever
# emits positive magnitudes and expresses direction through the
# past/future templates. They were previously silently dropped, so
# "in -1 hours" returned the same (wrong) result as "in 1 hours".
arw = arrow.Arrow(2000, 6, 18, 5, 55, 0)

# Sanity check: the corresponding positive strings still work and the
# buggy result must not equal the positive result.
assert arw.dehumanize("in 1 hours") == arw.shift(hours=1)
assert arw.dehumanize("1 hours ago") == arw.shift(hours=-1)

negative_strings = [
"in -1 hours",
"in -5 days",
"-3 hours ago",
"in -2 years",
"-10 seconds ago",
]
for negative_string in negative_strings:
with pytest.raises(ValueError):
arw.dehumanize(negative_string)

# Test for scrambled input
def test_scrambled_input(self, locale_list_no_weeks: List[str]):
for lang in locale_list_no_weeks:
Expand Down