diff --git a/arrow/util.py b/arrow/util.py index 7171d92c..1e341df0 100644 --- a/arrow/util.py +++ b/arrow/util.py @@ -1,6 +1,7 @@ """Helpful functions used internally within arrow.""" import datetime +import math from typing import Any, Optional from dateutil.rrule import WEEKLY, rrule @@ -43,16 +44,23 @@ def next_weekday( def is_timestamp(value: Any) -> bool: - """Check if value is a valid timestamp.""" + """Check if value is a valid timestamp. + + A value is considered a valid timestamp when it is an ``int``, ``float``, + or ``str`` that parses via :func:`float` to a finite real number. ``bool`` + is rejected (it is technically an ``int`` subclass). Non-finite floats — + ``inf``, ``-inf``, and ``nan`` — are rejected because they cannot be + converted into a real datetime. + """ if isinstance(value, bool): return False if not isinstance(value, (int, float, str)): return False try: - float(value) - return True + parsed = float(value) except ValueError: return False + return math.isfinite(parsed) def validate_ordinal(value: Any) -> None: diff --git a/tests/test_util.py b/tests/test_util.py index 2454dac5..fbb28edf 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -56,6 +56,21 @@ class InvalidTimestamp: full_datetime = "2019-06-23T13:12:42" assert not util.is_timestamp(full_datetime) + def test_is_timestamp_rejects_non_finite(self): + # Non-finite floats (inf/-inf/nan) cannot be turned into a real + # datetime, so is_timestamp must not treat them as a valid timestamp + # and let them reach the regex- or float()-driven downstream paths. + assert not util.is_timestamp(float("inf")) + assert not util.is_timestamp(float("-inf")) + assert not util.is_timestamp(float("nan")) + assert not util.is_timestamp("inf") + assert not util.is_timestamp("-inf") + assert not util.is_timestamp("nan") + assert not util.is_timestamp("Infinity") + assert not util.is_timestamp("NaN") + # A literal that overflows float() into +inf must be rejected too. + assert not util.is_timestamp("1e1000") + def test_validate_ordinal(self): timestamp_float = 1607066816.815537 timestamp_int = int(timestamp_float)