From c7b46363019aac68b44829cebc33c97a2d4475e8 Mon Sep 17 00:00:00 2001 From: Manush Prajwal Date: Sun, 9 Aug 2026 12:07:53 +0530 Subject: [PATCH] fix: catch OverflowError for non-finite floats in time functions naturaldelta(), naturaltime(), and precisedelta() all raised an uncaught OverflowError for float('inf') / float('-inf') instead of returning the value unchanged, unlike every other numeric humanize function (which already treat non-finite input this way) and unlike how these same functions already handle float('nan'). The root cause is that int()/round() raise OverflowError (not ValueError or TypeError) for infinite floats, and that exception wasn't in the except clauses guarding the timedelta conversion in naturaldelta() and the shared _date_and_delta() helper used by naturaltime() and precisedelta(). Fixes #333. --- src/humanize/time.py | 12 +++++------- tests/test_time.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 4a07d528..37f930dc 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -89,7 +89,7 @@ def _date_and_delta( value = value if precise else round(value) delta = dt.timedelta(seconds=value) date = now - delta - except (ValueError, TypeError): + except (ValueError, TypeError, OverflowError): return None, value return date, _abs_timedelta(delta) @@ -114,11 +114,9 @@ def naturaldelta( Returns: str (str or `value`): A natural representation of the amount of time elapsed unless `value` is not datetime.timedelta or cannot be - converted to int (cannot be float due to 'inf' or 'nan'). - In that case, a `value` is returned unchanged. - - Raises: - OverflowError: If `value` is too large to convert to datetime.timedelta. + converted to int (cannot be float due to 'inf' or 'nan', or too + large to fit in a `datetime.timedelta`). In that case, `value` is + returned unchanged (via `str()`). Examples: Compare two timestamps in a custom local timezone:: @@ -151,7 +149,7 @@ def naturaldelta( int(value) # Explicitly don't support string such as "NaN" or "inf" value = float(value) delta = dt.timedelta(seconds=value) - except (ValueError, TypeError): + except (ValueError, TypeError, OverflowError): return str(value) use_months = months diff --git a/tests/test_time.py b/tests/test_time.py index 76997704..033a4e55 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -837,6 +837,45 @@ def test_time_unit() -> None: _ = years < "foo" +@pytest.mark.parametrize( + "value, expected", + [ + (float("inf"), "inf"), + (float("-inf"), "-inf"), + (float("nan"), "nan"), + ], +) +def test_naturaldelta_non_finite(value: float, expected: str) -> None: + # Regression test for #333: non-finite floats used to raise an uncaught + # OverflowError (or, for nan, were only handled when passed as a string) + # instead of being returned unchanged like other non-numeric input. + assert humanize.naturaldelta(value) == expected + + +@pytest.mark.parametrize( + "value, expected", + [ + (float("inf"), "inf"), + (float("-inf"), "-inf"), + (float("nan"), "nan"), + ], +) +def test_naturaltime_non_finite(value: float, expected: str) -> None: + assert humanize.naturaltime(value) == expected + + +@pytest.mark.parametrize( + "value, expected", + [ + (float("inf"), "inf"), + (float("-inf"), "-inf"), + (float("nan"), "nan"), + ], +) +def test_precisedelta_non_finite(value: float, expected: str) -> None: + assert humanize.precisedelta(value) == expected + + @pytest.mark.parametrize( "fmt, value, expected", [