Skip to content

Commit f8ca0f3

Browse files
Remove call-stack inspection from DateTime.__add__ (#1032)
1 parent b8ebebe commit f8ca0f3

3 files changed

Lines changed: 48 additions & 6 deletions

File tree

‎src/pendulum/datetime.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import calendar
44
import datetime
5-
import traceback
65

76
from typing import TYPE_CHECKING
87
from typing import Any
@@ -1238,10 +1237,6 @@ def __add__(self, other: datetime.timedelta) -> Self:
12381237
if not isinstance(other, datetime.timedelta):
12391238
return NotImplemented
12401239

1241-
caller = traceback.extract_stack(limit=2)[0].name
1242-
if caller == "astimezone":
1243-
return super().__add__(other)
1244-
12451240
return self._add_timedelta_(other)
12461241

12471242
def __radd__(self, other: datetime.timedelta) -> Self:
@@ -1278,7 +1273,20 @@ def combine(
12781273
return cls.instance(datetime.datetime.combine(date, time), tz=tzinfo)
12791274

12801275
def astimezone(self, tz: datetime.tzinfo | None = None) -> Self:
1281-
dt = super().astimezone(tz)
1276+
# Convert a plain datetime: tzinfo.fromutc() implementations add
1277+
# timedeltas to the value they're given and expect the standard
1278+
# library's arithmetic, not the overridden __add__.
1279+
dt = datetime.datetime(
1280+
self.year,
1281+
self.month,
1282+
self.day,
1283+
self.hour,
1284+
self.minute,
1285+
self.second,
1286+
self.microsecond,
1287+
tzinfo=self.tzinfo,
1288+
fold=self.fold,
1289+
).astimezone(tz)
12821290

12831291
return self.__class__(
12841292
dt.year,

‎tests/datetime/test_add.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ def test_add_timedelta():
108108
assert d.microsecond == 777777
109109

110110

111+
def test_add_timedelta_does_not_depend_on_caller_name():
112+
def astimezone(d):
113+
return d + timedelta(hours=24)
114+
115+
d = pendulum.datetime(2013, 3, 30, 12, tz="Europe/Paris")
116+
117+
assert_datetime(astimezone(d), 2013, 3, 31, 13)
118+
119+
111120
def test_add_duration():
112121
duration = pendulum.duration(
113122
years=2, months=3, days=6, seconds=45, microseconds=123456

‎tests/datetime/test_timezone.py‎

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

3+
from datetime import datetime
4+
5+
import pytest
6+
7+
from dateutil import tz
8+
39
import pendulum
410

511
from tests.conftest import assert_datetime
@@ -36,3 +42,22 @@ def test_astimezone():
3642
d = d.astimezone(pendulum.timezone("Europe/Paris"))
3743
assert d.timezone_name == "Europe/Paris"
3844
assert_datetime(d, now.year, now.month, now.day, now.hour + 1, now.minute)
45+
46+
47+
def test_astimezone_with_tzinfo_doing_arithmetic_in_fromutc():
48+
# dateutil's tzoffset.fromutc() adds its offset to the datetime it's given
49+
cest = tz.tzoffset("CEST", 7200)
50+
d = pendulum.datetime(2024, 7, 1, 12)
51+
52+
d = d.astimezone(cest)
53+
assert isinstance(d, pendulum.DateTime)
54+
assert d.tzinfo is cest
55+
assert_datetime(d, 2024, 7, 1, 14)
56+
57+
58+
@pytest.mark.parametrize("fold, hour", [(0, 0), (1, 1)])
59+
def test_astimezone_respects_fold(fold, hour):
60+
d = pendulum.datetime(2024, 10, 27, 2, 30, tz="Europe/Paris", fold=fold)
61+
62+
d = d.astimezone(pendulum.UTC)
63+
assert d == datetime(2024, 10, 27, hour, 30, tzinfo=pendulum.UTC)

0 commit comments

Comments
 (0)