From 93d74d3759ed79be01085e2f83936475f3d42dca Mon Sep 17 00:00:00 2001 From: 9Kun <91123978+9Kun@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:11:53 +0800 Subject: [PATCH] Preserve fractional seconds in naturaltime with subsecond units --- src/humanize/time.py | 4 +++- tests/test_time.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 82a80424..ad02c730 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -295,7 +295,9 @@ def naturaltime( now = when or _now() - date, delta = _date_and_delta(value, now=now) + date, delta = _date_and_delta( + value, now=now, precise=minimum_unit.upper() != "SECONDS" + ) if date is None: return str(value) # determine tense by value only if datetime/timedelta were passed diff --git a/tests/test_time.py b/tests/test_time.py index f63b5cb0..e2829723 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -485,6 +485,46 @@ def test_naturaltime_minimum_unit_explicit( # Act / Assert assert humanize.naturaltime(datetime, minimum_unit=minimum_unit) == expected + assert humanize.naturaltime(seconds, minimum_unit=minimum_unit) == expected + assert ( + humanize.naturaltime(dt.timedelta(seconds=seconds), minimum_unit=minimum_unit) + == expected + ) + + +@pytest.mark.parametrize( + "minimum_unit, seconds, expected", + [ + ("milliseconds", 0.999, "999 milliseconds"), + ("MILLISECONDS", ONE_MILLISECOND, "1 millisecond"), + ("microseconds", 0.000999, "999 microseconds"), + ("MICROSECONDS", ONE_MICROSECOND, "1 microsecond"), + ], +) +@pytest.mark.parametrize("sign", [1, -1]) +@pytest.mark.parametrize("future", [False, True]) +def test_naturaltime_float_subsecond( + minimum_unit: str, seconds: float, expected: str, sign: int, future: bool +) -> None: + suffix = "from now" if future else "ago" + assert ( + humanize.naturaltime( + sign * seconds, future=future, minimum_unit=minimum_unit, when=NOW + ) + == f"{expected} {suffix}" + ) + + +@pytest.mark.parametrize("minimum_unit", ["seconds", "SECONDS"]) +@pytest.mark.parametrize( + "seconds, expected", + [(0.1, "now"), (0.6, "a second ago"), (1.9, "2 seconds ago")], +) +def test_naturaltime_float_seconds_rounding( + minimum_unit: str, seconds: float, expected: str +) -> None: + assert humanize.naturaltime(seconds) == expected + assert humanize.naturaltime(seconds, minimum_unit=minimum_unit) == expected @freeze_time(FROZEN_DATE)