From d467951a375274c674f7dfd6b58430e3896a5e89 Mon Sep 17 00:00:00 2001 From: whoami Date: Mon, 8 Jun 2026 14:18:28 +0300 Subject: [PATCH] Raise ValueError in dehumanize() for negative numbers The number regex only matches unsigned digits, so a negative value like "in -1 hours" had its sign dropped and produced the same result as "in 1 hours". Direction in a humanized string comes from the locale's ago/in wording, not a sign, so reject negative numbers explicitly, consistent with how other invalid input is handled. Closes #1278 --- arrow/arrow.py | 10 ++++++++++ tests/test_arrow.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/arrow/arrow.py b/arrow/arrow.py index eecf2326..52e3c79c 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -1380,6 +1380,16 @@ def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": current_time = self.fromdatetime(self._datetime) + # Humanized strings encode direction with the locale's past/future + # wording ("ago"/"in"), not a sign on the number. A negative value such + # as "in -1 hours" would otherwise have its sign dropped by the number + # regex below and be parsed as positive, so reject it explicitly. + if re.search(r"-\d", input_string): + raise ValueError( + "Invalid input string: negative numbers are not supported. " + "Use the past or future wording instead, e.g. '1 hour ago' or 'in 1 hour'." + ) + # 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..c94ed5ed 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -2930,6 +2930,17 @@ def test_no_units_modified(self, locale_list_no_weeks: List[str]): with pytest.raises(ValueError): arw.dehumanize(empty_future_string, locale=lang) + # Negative numbers are not valid: direction comes from "ago"/"in" wording, + # not a sign, and the sign would otherwise be silently dropped. + def test_negative_number(self): + arw = arrow.Arrow(2000, 6, 18, 5, 55, 0) + + with pytest.raises(ValueError): + arw.dehumanize("in -1 hours") + + with pytest.raises(ValueError): + arw.dehumanize("-2 days ago") + def test_slavic_locales(self, slavic_locales: List[str]): # Relevant units for Slavic locale plural logic units = [