From a345b270cc7dda5bb1784196a55e65bac5b7d72d Mon Sep 17 00:00:00 2001 From: Amandeep vishwkarma Date: Sun, 17 May 2026 22:56:55 +0530 Subject: [PATCH] Reject negative time values in dehumanize() instead of silently returning wrong results --- arrow/arrow.py | 12 ++++++++++++ tests/test_arrow.py | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/arrow/arrow.py b/arrow/arrow.py index eecf2326..75f52010 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -1380,6 +1380,18 @@ def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": current_time = self.fromdatetime(self._datetime) + # Reject negative time values early. The number-extraction regex + # (\d+) only matches unsigned digits, so an input like "in -1 hours" + # would silently drop the minus sign and return the *opposite* + # direction. Raising here gives the caller a clear message instead + # of a quietly wrong result. + if re.search(r"(?:^|\s)-\d", input_string): + raise ValueError( + f"Negative time values are not supported in dehumanize " + f"(got {input_string!r}). " + f"Use a positive value with 'ago' or 'in' to indicate direction." + ) + # Create an object containing the relative time info time_object_info = dict.fromkeys( ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0 diff --git a/tests/test_arrow.py b/tests/test_arrow.py index b595e4e2..4a80157c 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2987,6 +2987,33 @@ def test_czech_slovak(self): assert arw.dehumanize(past_string, locale=lang) == past assert arw.dehumanize(future_string, locale=lang) == future + def test_negative_values(self): + arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) + + # Negative time values should raise ValueError instead of + # silently dropping the sign and returning a wrong result. + # See: https://github.com/arrow-py/arrow/issues/1278 + negative_inputs = [ + "in -1 hours", + "in -2 days", + "-3 minutes ago", + "in -30 seconds", + ] + + for s in negative_inputs: + with pytest.raises(ValueError, match="Negative time values"): + arw.dehumanize(s) + + # Positive time values should continue to succeed + assert arw.dehumanize("in 1 hours") == arw.shift(hours=1) + assert arw.dehumanize("2 days ago") == arw.shift(days=-2) + + # Hyphenated strings without spaces preceding the minus/hyphen should not + # trigger the negative validation error (they will raise ValueError for unrecognized units). + with pytest.raises(ValueError) as excinfo: + arw.dehumanize("some-2nd-hour") + assert "Negative time values" not in str(excinfo.value) + class TestArrowIsBetween: def test_start_before_end(self):