Skip to content

Commit 4365af0

Browse files
committed
fix: handle negative timestamps in DateTime constructors
1 parent e5f5167 commit 4365af0

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

src/pendulum/datetime.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,11 +1253,21 @@ def __radd__(self, other: datetime.timedelta) -> Self:
12531253
def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
12541254
tzinfo = pendulum._safe_timezone(tz)
12551255

1256-
return cls.instance(datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo)
1256+
try:
1257+
dt = datetime.datetime.fromtimestamp(t, tz=tzinfo)
1258+
except OSError:
1259+
dt = (cls._EPOCH + datetime.timedelta(seconds=t)).astimezone(tzinfo)
1260+
1261+
return cls.instance(dt, tz=tzinfo)
12571262

12581263
@classmethod
12591264
def utcfromtimestamp(cls, t: float) -> Self:
1260-
return cls.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
1265+
try:
1266+
dt = datetime.datetime.utcfromtimestamp(t)
1267+
except OSError:
1268+
dt = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=t)
1269+
1270+
return cls.instance(dt, tz=None)
12611271

12621272
@classmethod
12631273
def fromordinal(cls, n: int) -> Self:

tests/datetime/test_behavior.py

Lines changed: 49 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+
import importlib
35
import pickle
6+
import types
47
import zoneinfo
58

69
from copy import deepcopy
@@ -107,6 +110,52 @@ def test_utcfromtimestamp():
107110
assert p == dt
108111

109112

113+
def test_fromtimestamp_falls_back_for_negative_timestamp(monkeypatch):
114+
pendulum_datetime_module = importlib.import_module("pendulum.datetime")
115+
116+
class FakeDateTime(datetime_.datetime):
117+
@classmethod
118+
def fromtimestamp(cls, t: float, tz: datetime_.tzinfo | None = None):
119+
if t == -43201:
120+
raise OSError("Invalid argument")
121+
122+
return super().fromtimestamp(t, tz=tz)
123+
124+
monkeypatch.setattr(
125+
pendulum_datetime_module,
126+
"datetime",
127+
types.SimpleNamespace(datetime=FakeDateTime, timedelta=datetime_.timedelta),
128+
)
129+
130+
p = pendulum.DateTime.fromtimestamp(-43201, pendulum.UTC)
131+
132+
assert p == pendulum.datetime(1969, 12, 31, 11, 59, 59)
133+
assert p.timezone_name == "UTC"
134+
135+
136+
def test_utcfromtimestamp_falls_back_for_negative_timestamp(monkeypatch):
137+
pendulum_datetime_module = importlib.import_module("pendulum.datetime")
138+
139+
class FakeDateTime(datetime_.datetime):
140+
@classmethod
141+
def utcfromtimestamp(cls, t: float):
142+
if t == -43201:
143+
raise OSError("Invalid argument")
144+
145+
return super().utcfromtimestamp(t)
146+
147+
monkeypatch.setattr(
148+
pendulum_datetime_module,
149+
"datetime",
150+
types.SimpleNamespace(datetime=FakeDateTime, timedelta=datetime_.timedelta),
151+
)
152+
153+
p = pendulum.DateTime.utcfromtimestamp(-43201)
154+
155+
assert p == pendulum.naive(1969, 12, 31, 11, 59, 59)
156+
assert p.tzinfo is None
157+
158+
110159
def test_fromordinal():
111160
assert datetime.fromordinal(730120) == pendulum.DateTime.fromordinal(730120)
112161

0 commit comments

Comments
 (0)