diff --git a/arrow/arrow.py b/arrow/arrow.py index eecf23266..69910b3d7 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -12,6 +12,7 @@ from datetime import time as dt_time from datetime import timedelta, timezone from datetime import tzinfo as dt_tzinfo +from decimal import Decimal from math import trunc from time import struct_time from typing import ( @@ -238,17 +239,21 @@ def utcnow(cls) -> "Arrow": @classmethod def fromtimestamp( cls, - timestamp: Union[int, float, str], + timestamp: Union[int, float, str, Decimal], tzinfo: Optional[TZ_EXPR] = None, ) -> "Arrow": """Constructs an :class:`Arrow ` object from a timestamp, converted to the given timezone. - :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. + :param timestamp: an ``int``, ``float`` or ``Decimal`` timestamp, or a ``str`` that + converts to one of those. :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. """ + if isinstance(timestamp, Decimal): + timestamp = float(timestamp) + if tzinfo is None: tzinfo = dt_datetime.now().astimezone().tzinfo elif isinstance(tzinfo, str): @@ -273,13 +278,17 @@ def fromtimestamp( ) @classmethod - def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow": + def utcfromtimestamp(cls, timestamp: Union[int, float, str, Decimal]) -> "Arrow": """Constructs an :class:`Arrow ` object from a timestamp, in UTC time. - :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. + :param timestamp: an ``int``, ``float`` or ``Decimal`` timestamp, or a ``str`` that + converts to one of those. """ + if isinstance(timestamp, Decimal): + timestamp = float(timestamp) + if not util.is_timestamp(timestamp): raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") diff --git a/tests/test_arrow.py b/tests/test_arrow.py index b595e4e21..03e576a8b 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -7,6 +7,7 @@ import sys import time from datetime import date, datetime, timedelta, timezone +from decimal import Decimal from typing import List import dateutil @@ -140,6 +141,18 @@ def test_fromtimestamp(self): with pytest.raises(ValueError): arrow.Arrow.fromtimestamp("invalid timestamp") + def test_fromtimestamp_decimal(self): + # arrow.get() accepts a Decimal timestamp, so the constructor it stands + # in for has to accept one too. + timestamp = Decimal("1591328104.308505") + + result = arrow.Arrow.fromtimestamp(timestamp, tzinfo="Europe/Paris") + + assert_datetime_equality( + result._datetime, + datetime.fromtimestamp(float(timestamp), ZoneInfo("Europe/Paris")), + ) + def test_utcfromtimestamp(self): timestamp = time.time() @@ -151,6 +164,15 @@ def test_utcfromtimestamp(self): with pytest.raises(ValueError): arrow.Arrow.utcfromtimestamp("invalid timestamp") + def test_utcfromtimestamp_decimal(self): + timestamp = Decimal("1591328104.308505") + + result = arrow.Arrow.utcfromtimestamp(timestamp) + + assert result._datetime == datetime( + 2020, 6, 5, 3, 35, 4, 308505, tzinfo=timezone.utc + ) + def test_fromdatetime(self): dt = datetime(2013, 2, 3, 12, 30, 45, 1)