Skip to content

Commit 2b1260c

Browse files
authored
gh-83861: Fix datetime.astimezone() method (GH-101545)
1 parent d4aa857 commit 2b1260c

File tree

4 files changed

+29
-2
lines changed

4 files changed

+29
-2
lines changed

Lib/datetime.py

+5
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,11 @@ def replace(self, year=None, month=None, day=None, hour=None,
19651965
def _local_timezone(self):
19661966
if self.tzinfo is None:
19671967
ts = self._mktime()
1968+
# Detect gap
1969+
ts2 = self.replace(fold=1-self.fold)._mktime()
1970+
if ts2 != ts: # This happens in a gap or a fold
1971+
if (ts2 > ts) == self.fold:
1972+
ts = ts2
19681973
else:
19691974
ts = (self - _EPOCH) // timedelta(seconds=1)
19701975
localtm = _time.localtime(ts)

Lib/test/datetimetester.py

+4
Original file line numberDiff line numberDiff line change
@@ -6212,6 +6212,10 @@ def test_system_transitions(self):
62126212
ts1 = dt.replace(fold=1).timestamp()
62136213
self.assertEqual(ts0, s0 + ss / 2)
62146214
self.assertEqual(ts1, s0 - ss / 2)
6215+
# gh-83861
6216+
utc0 = dt.astimezone(timezone.utc)
6217+
utc1 = dt.replace(fold=1).astimezone(timezone.utc)
6218+
self.assertEqual(utc0, utc1 + timedelta(0, ss))
62156219
finally:
62166220
if TZ is None:
62176221
del os.environ['TZ']
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix datetime.astimezone method return value when invoked on a naive datetime
2+
instance that represents local time falling in a timezone transition gap.
3+
PEP 495 requires that instances with fold=1 produce earlier times than those
4+
with fold=0 in this case.

Modules/_datetimemodule.c

+16-2
Original file line numberDiff line numberDiff line change
@@ -6153,17 +6153,31 @@ local_to_seconds(int year, int month, int day,
61536153
static PyObject *
61546154
local_timezone_from_local(PyDateTime_DateTime *local_dt)
61556155
{
6156-
long long seconds;
6156+
long long seconds, seconds2;
61576157
time_t timestamp;
6158+
int fold = DATE_GET_FOLD(local_dt);
61586159
seconds = local_to_seconds(GET_YEAR(local_dt),
61596160
GET_MONTH(local_dt),
61606161
GET_DAY(local_dt),
61616162
DATE_GET_HOUR(local_dt),
61626163
DATE_GET_MINUTE(local_dt),
61636164
DATE_GET_SECOND(local_dt),
6164-
DATE_GET_FOLD(local_dt));
6165+
fold);
61656166
if (seconds == -1)
61666167
return NULL;
6168+
seconds2 = local_to_seconds(GET_YEAR(local_dt),
6169+
GET_MONTH(local_dt),
6170+
GET_DAY(local_dt),
6171+
DATE_GET_HOUR(local_dt),
6172+
DATE_GET_MINUTE(local_dt),
6173+
DATE_GET_SECOND(local_dt),
6174+
!fold);
6175+
if (seconds2 == -1)
6176+
return NULL;
6177+
/* Detect gap */
6178+
if (seconds2 != seconds && (seconds2 > seconds) == fold)
6179+
seconds = seconds2;
6180+
61676181
/* XXX: add bounds check */
61686182
timestamp = seconds - epoch;
61696183
return local_timezone_from_timestamp(timestamp);

0 commit comments

Comments
 (0)