diff --git a/src/humanize/time.py b/src/humanize/time.py index 975cc03..4b8d32e 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -5,6 +5,8 @@ from __future__ import annotations +import datetime + __lazy_modules__ = {"humanize.i18n", "humanize.number"} from enum import Enum @@ -546,6 +548,13 @@ def precisedelta( ``` """ + if isinstance(value, datetime.timedelta): + negative = value < datetime.timedelta(0) + elif isinstance(value, (int, float)): + negative = value < 0 + else: + negative = False + date, delta = _date_and_delta(value, precise=True) if date is None: return str(value) @@ -673,12 +682,13 @@ def precisedelta( break if len(texts) == 1: - return texts[0] - - head = ", ".join(texts[:-1]) - tail = texts[-1] + result = texts[0] + else: + head = ", ".join(texts[:-1]) + tail = texts[-1] + result = _("%s and %s") % (head, tail) - return _("%s and %s") % (head, tail) + return f"-{result}" if negative else result def _rounding_by_fmt(format: str, value: float) -> float | int: diff --git a/tests/test_time.py b/tests/test_time.py index c3743bb..10004d0 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -837,6 +837,23 @@ def test_precisedelta_suppress_units( def test_precisedelta_bogus_call() -> None: assert humanize.precisedelta(None) == "None" + +def test_precisedelta_negative() -> None: + # regression: negative timedeltas must keep their sign + # https://github.com/python-humanize/humanize/issues/379 + assert ( + humanize.precisedelta(dt.timedelta(seconds=-3661)) + == "-1 hour, 1 minute and 1 second" + ) + assert ( + humanize.precisedelta(dt.timedelta(seconds=3661)) + == "1 hour, 1 minute and 1 second" + ) + assert ( + humanize.precisedelta(dt.timedelta(seconds=-3661), minimum_unit="minutes") + == "-1 hour and 1.02 minutes" + ) + with pytest.raises( ValueError, match="Minimum unit is suppressed and no suitable replacement was found",