From d2feb60cd51038ff0b737d88680c3dcff24b49c6 Mon Sep 17 00:00:00 2001 From: Mei Date: Sun, 2 Aug 2026 17:17:21 +0800 Subject: [PATCH 1/4] fix(time): catch OverflowError for non-finite floats in naturaldelta and naturaltime (#333) --- src/humanize/time.py | 7 ++----- tests/test_time.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 4a07d528..116425eb 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) @@ -117,9 +117,6 @@ def naturaldelta( 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. - Examples: Compare two timestamps in a custom local timezone:: @@ -151,7 +148,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..4574f8f1 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -3,6 +3,7 @@ from __future__ import annotations import datetime as dt +import math import typing import pytest @@ -71,6 +72,8 @@ def test_date_and_delta() -> None: assert_equal_datetime(date, result[0]) assert_equal_timedelta(d, result[1]) assert time._date_and_delta("NaN") == (None, "NaN") + assert time._date_and_delta(float("inf")) == (None, float("inf")) + assert time._date_and_delta(float("-inf")) == (None, float("-inf")) # Tests for the public interface of humanize.time @@ -128,13 +131,15 @@ def test_naturaldelta_nomonths(test_input: dt.timedelta, expected: str) -> None: (dt.timedelta(days=365), "a year"), (dt.timedelta(days=365 * 1_141), "1,141 years"), ("NaN", "NaN"), # Returns non-numbers unchanged. + (float("inf"), "inf"), + (float("-inf"), "-inf"), # largest possible timedelta (dt.timedelta(days=999_999_999), "2,739,726 years"), ], ) def test_naturaldelta(test_input: float | dt.timedelta, expected: str) -> None: assert humanize.naturaldelta(test_input) == expected - if not isinstance(test_input, str): + if not isinstance(test_input, str) and not (isinstance(test_input, float) and math.isinf(test_input)): assert humanize.naturaldelta(-test_input) == expected @@ -179,6 +184,8 @@ def test_naturaldelta(test_input: float | dt.timedelta, expected: str) -> None: (NOW - dt.timedelta(days=365 * 2 + 65), "2 years ago"), (NOW - dt.timedelta(days=365 + 4), "1 year, 4 days ago"), ("NaN", "NaN"), + (float("inf"), "inf"), + (float("-inf"), "-inf"), ], ) def test_naturaltime( @@ -817,6 +824,8 @@ def test_precisedelta_suppress_units( def test_precisedelta_bogus_call() -> None: assert humanize.precisedelta(None) == "None" + assert humanize.precisedelta(float("inf")) == "inf" + assert humanize.precisedelta(float("-inf")) == "-inf" with pytest.raises( ValueError, From 235ae8eed54eecadf88a5dc5aca4df1c14ac96e9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 09:25:07 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_time.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_time.py b/tests/test_time.py index 4574f8f1..bd4e2700 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -139,7 +139,9 @@ def test_naturaldelta_nomonths(test_input: dt.timedelta, expected: str) -> None: ) def test_naturaldelta(test_input: float | dt.timedelta, expected: str) -> None: assert humanize.naturaldelta(test_input) == expected - if not isinstance(test_input, str) and not (isinstance(test_input, float) and math.isinf(test_input)): + if not isinstance(test_input, str) and not ( + isinstance(test_input, float) and math.isinf(test_input) + ): assert humanize.naturaldelta(-test_input) == expected From 0c5223a3a63cadce5e8013b9910f06abf87f06f7 Mon Sep 17 00:00:00 2001 From: Nefelibata <124799179+MeiSiristhebest@users.noreply.github.com> Date: Mon, 3 Aug 2026 01:34:23 +0800 Subject: [PATCH 3/4] fix(time): guard OverflowError with math.isfinite and cover _date_and_delta --- src/humanize/time.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 116425eb..37332ee9 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -7,6 +7,7 @@ __lazy_modules__ = {"humanize.i18n", "humanize.number"} +import math from enum import Enum from functools import total_ordering @@ -89,8 +90,12 @@ def _date_and_delta( value = value if precise else round(value) delta = dt.timedelta(seconds=value) date = now - delta - except (ValueError, TypeError, OverflowError): + except (ValueError, TypeError): return None, value + except OverflowError: + if not math.isfinite(value): + return None, value + raise return date, _abs_timedelta(delta) @@ -117,6 +122,9 @@ def naturaldelta( 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. + Examples: Compare two timestamps in a custom local timezone:: @@ -148,8 +156,12 @@ 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, OverflowError): + except (ValueError, TypeError): return str(value) + except OverflowError: + if not math.isfinite(value): + return str(value) + raise use_months = months From 6876505386a62048e1632288355a34497d51f853 Mon Sep 17 00:00:00 2001 From: Nefelibata <124799179+MeiSiristhebest@users.noreply.github.com> Date: Mon, 3 Aug 2026 01:34:52 +0800 Subject: [PATCH 4/4] test(time): add test_naturaldelta_too_large_value_raises --- tests/test_time.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_time.py b/tests/test_time.py index bd4e2700..f1f44d73 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -92,6 +92,12 @@ def test_naturaldelta_nomonths(test_input: dt.timedelta, expected: str) -> None: assert humanize.naturaldelta(test_input, months=False) == expected +def test_naturaldelta_too_large_value_raises() -> None: + """A too-large finite value still raises OverflowError (unlike inf).""" + with pytest.raises(OverflowError): + humanize.naturaldelta(1e30) + + @pytest.mark.parametrize( "test_input, expected", [