diff --git a/src/humanize/time.py b/src/humanize/time.py index 7c4a28eb..c2fea172 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -552,6 +552,16 @@ def precisedelta( if date is None: return str(value) + import datetime as dt + + # _date_and_delta() returns an absolute delta; remember the original sign so + # negative inputs are not rendered identically to positive ones (#379). + is_negative = ( + value < dt.timedelta(0) + if isinstance(value, dt.timedelta) + else isinstance(value, (int, float)) and value < 0 + ) + suppress_set = {Unit[s.upper()] for s in suppress} # Find a suitable minimum unit (it can be greater than the one that the @@ -652,6 +662,8 @@ def precisedelta( ("%d microsecond", "%d microseconds", usecs), ] + sign = "-" if is_negative and any(f[2] for f in fmts) else "" + import math texts: list[str] = [] @@ -675,12 +687,12 @@ def precisedelta( break if len(texts) == 1: - return texts[0] + return sign + texts[0] head = ", ".join(texts[:-1]) tail = texts[-1] - return _("%s and %s") % (head, tail) + return sign + _("%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 887a3fc5..b8ef4a42 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -875,3 +875,21 @@ 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) + + +@pytest.mark.parametrize( + "val, min_unit, expected", + [ + (dt.timedelta(seconds=-3661), "seconds", "-1 hour, 1 minute and 1 second"), + (dt.timedelta(days=-1), "seconds", "-1 day"), + (-90, "seconds", "-1 minute and 30 seconds"), + (dt.timedelta(seconds=-3661), "minutes", "-1 hour and 1.02 minutes"), + (dt.timedelta(seconds=3661), "seconds", "1 hour, 1 minute and 1 second"), + (dt.timedelta(0), "seconds", "0 seconds"), + ], +) +def test_precisedelta_negative_keeps_sign( + val: dt.timedelta | int, min_unit: str, expected: str +) -> None: + """Regression for #379: negative deltas must not look like positive ones.""" + assert humanize.precisedelta(val, minimum_unit=min_unit) == expected