From eae86f3b3b2ce2b66115759d9eac8a6ec080903f Mon Sep 17 00:00:00 2001 From: Mukller Date: Mon, 24 Aug 2026 20:56:54 +0300 Subject: [PATCH 1/3] fix(time): precisedelta keeps the sign of negative timedeltas (Closes #379) --- src/humanize/time.py | 17 +++++++++++++++-- tests/test_time.py | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 4a07d528..7a0307af 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -536,10 +536,23 @@ def precisedelta( ``` """ + # Sign captured from the ORIGINAL value: _date_and_delta returns + # _abs_timedelta(delta), which would silently drop it (#379). + import datetime as dt + + if isinstance(value, dt.timedelta): + negative = value < dt.timedelta(0) + else: + try: + negative = value < 0 + except TypeError: + negative = False date, delta = _date_and_delta(value, precise=True) if date is None: return str(value) + import datetime as dt + sign = "-" if delta < dt.timedelta(0) else "" suppress_set = {Unit[s.upper()] for s in suppress} # Find a suitable minimum unit (it can be greater than the one that the @@ -663,12 +676,12 @@ def precisedelta( break if len(texts) == 1: - return texts[0] + return ("-" if negative else "") + texts[0] head = ", ".join(texts[:-1]) tail = texts[-1] - return _("%s and %s") % (head, tail) + return ("-" if negative else "") + _("%s and %s") % (head, tail) def _rounding_by_fmt(format: str, value: float) -> float | int: diff --git a/tests/test_time.py b/tests/test_time.py index 76997704..8d378a3d 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -852,3 +852,29 @@ def test_time_unit() -> None: ) def test_rounding_by_fmt(fmt: str, value: float, expected: float) -> None: assert time._rounding_by_fmt(fmt, value) == pytest.approx(expected) + + +def test_precisedelta_negative_multi_unit(): + """issue #379: precisedelta silently dropped the sign of negative timedeltas.""" + assert ( + humanize.precisedelta(dt.timedelta(seconds=-3661)) + == "-1 hour, 1 minute and 1 second" + ) + + +def test_precisedelta_negative_single_unit(): + assert ( + humanize.precisedelta(dt.timedelta(seconds=-3661), minimum_unit="minutes") + == "-1 hour and 1.02 minutes" + ) + + +def test_precisedelta_zero_stays_unsigned(): + assert humanize.precisedelta(dt.timedelta(0), minimum_unit="minutes") == "0 minutes" + + +def test_precisedelta_positive_unchanged(): + assert ( + humanize.precisedelta(dt.timedelta(seconds=3661)) + == "1 hour, 1 minute and 1 second" + ) \ No newline at end of file From 8cfff7dc0611bb7e96191a5b7a4efe8eb8d5b60a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:59:26 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_time.py b/tests/test_time.py index 8d378a3d..823b8d3b 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -877,4 +877,4 @@ def test_precisedelta_positive_unchanged(): assert ( humanize.precisedelta(dt.timedelta(seconds=3661)) == "1 hour, 1 minute and 1 second" - ) \ No newline at end of file + ) From 17ab3b7f03d0a037bf7e0c3b5016fb24f08b03f0 Mon Sep 17 00:00:00 2001 From: Mukller Date: Tue, 25 Aug 2026 13:37:03 +0300 Subject: [PATCH 3/3] fix(time): precisedelta keeps the sign of negative timedeltas (Closes #379) --- src/humanize/time.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 7a0307af..fbd4da3d 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -552,7 +552,6 @@ def precisedelta( return str(value) import datetime as dt - sign = "-" if delta < dt.timedelta(0) else "" suppress_set = {Unit[s.upper()] for s in suppress} # Find a suitable minimum unit (it can be greater than the one that the