Skip to content

Commit a3af417

Browse files
committed
fix: handle OverflowError in timestamp fallbacks
1 parent 4365af0 commit a3af417

4 files changed

Lines changed: 39 additions & 10 deletions

File tree

src/pendulum/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,11 @@ def from_timestamp(timestamp: int | float, tz: str | Timezone = UTC) -> DateTime
288288
"""
289289
try:
290290
dt = _datetime.datetime.fromtimestamp(timestamp, tz=UTC)
291-
except OSError:
291+
except (OSError, OverflowError):
292292
# On some platforms (notably Windows), datetime.fromtimestamp
293-
# raises OSError for negative timestamps that are too far from
294-
# the Unix epoch. Fall back to computing the result from the
295-
# epoch and applying the offset manually.
293+
# raises OSError or OverflowError for negative timestamps that
294+
# are too far from the Unix epoch. Fall back to computing the
295+
# result from the epoch and applying the offset manually.
296296
epoch = _datetime.datetime(1970, 1, 1, tzinfo=UTC)
297297
dt = epoch + _datetime.timedelta(seconds=timestamp)
298298

src/pendulum/datetime.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,7 +1255,7 @@ def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
12551255

12561256
try:
12571257
dt = datetime.datetime.fromtimestamp(t, tz=tzinfo)
1258-
except OSError:
1258+
except (OSError, OverflowError):
12591259
dt = (cls._EPOCH + datetime.timedelta(seconds=t)).astimezone(tzinfo)
12601260

12611261
return cls.instance(dt, tz=tzinfo)
@@ -1264,7 +1264,9 @@ def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
12641264
def utcfromtimestamp(cls, t: float) -> Self:
12651265
try:
12661266
dt = datetime.datetime.utcfromtimestamp(t)
1267-
except OSError:
1267+
except (OSError, OverflowError):
1268+
# Match datetime.datetime.utcfromtimestamp(), which returns a
1269+
# naive datetime representing UTC.
12681270
dt = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=t)
12691271

12701272
return cls.instance(dt, tz=None)

tests/datetime/test_behavior.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import types
77
import zoneinfo
88

9+
import pytest
10+
911
from copy import deepcopy
1012
from datetime import date
1113
from datetime import datetime
@@ -110,14 +112,15 @@ def test_utcfromtimestamp():
110112
assert p == dt
111113

112114

113-
def test_fromtimestamp_falls_back_for_negative_timestamp(monkeypatch):
115+
@pytest.mark.parametrize("exception_type", [OSError, OverflowError])
116+
def test_fromtimestamp_falls_back_for_negative_timestamp(monkeypatch, exception_type):
114117
pendulum_datetime_module = importlib.import_module("pendulum.datetime")
115118

116119
class FakeDateTime(datetime_.datetime):
117120
@classmethod
118121
def fromtimestamp(cls, t: float, tz: datetime_.tzinfo | None = None):
119122
if t == -43201:
120-
raise OSError("Invalid argument")
123+
raise exception_type("Invalid argument")
121124

122125
return super().fromtimestamp(t, tz=tz)
123126

@@ -133,14 +136,15 @@ def fromtimestamp(cls, t: float, tz: datetime_.tzinfo | None = None):
133136
assert p.timezone_name == "UTC"
134137

135138

136-
def test_utcfromtimestamp_falls_back_for_negative_timestamp(monkeypatch):
139+
@pytest.mark.parametrize("exception_type", [OSError, OverflowError])
140+
def test_utcfromtimestamp_falls_back_for_negative_timestamp(monkeypatch, exception_type):
137141
pendulum_datetime_module = importlib.import_module("pendulum.datetime")
138142

139143
class FakeDateTime(datetime_.datetime):
140144
@classmethod
141145
def utcfromtimestamp(cls, t: float):
142146
if t == -43201:
143-
raise OSError("Invalid argument")
147+
raise exception_type("Invalid argument")
144148

145149
return super().utcfromtimestamp(t)
146150

tests/datetime/test_create_from_timestamp.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from __future__ import annotations
22

3+
import datetime as datetime_
4+
35
import pendulum
6+
import pytest
47

58
from pendulum import timezone
69
from tests.conftest import assert_datetime
@@ -50,3 +53,23 @@ def test_create_from_timestamp_negative_with_microseconds():
5053
d = pendulum.from_timestamp(-43201.5)
5154
assert_datetime(d, 1969, 12, 31, 11, 59, 58, 500000)
5255
assert d.timezone_name == "UTC"
56+
57+
58+
@pytest.mark.parametrize("exception_type", [OSError, OverflowError])
59+
def test_create_from_timestamp_falls_back_for_negative_timestamp(
60+
monkeypatch, exception_type
61+
):
62+
class FakeDateTime(datetime_.datetime):
63+
@classmethod
64+
def fromtimestamp(cls, timestamp: float, tz: datetime_.tzinfo | None = None):
65+
if timestamp == -43201:
66+
raise exception_type("Invalid argument")
67+
68+
return super().fromtimestamp(timestamp, tz=tz)
69+
70+
monkeypatch.setattr(pendulum._datetime, "datetime", FakeDateTime)
71+
72+
d = pendulum.from_timestamp(-43201)
73+
74+
assert_datetime(d, 1969, 12, 31, 11, 59, 59)
75+
assert d.timezone_name == "UTC"

0 commit comments

Comments
 (0)