Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Lib/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,11 @@ def replace(self, year=None, month=None, day=None, hour=None,
def _local_timezone(self):
if self.tzinfo is None:
ts = self._mktime()
# Detect gap
ts2 = self.replace(fold=1-self.fold)._mktime()
if ts2 != ts: # This happens in a gap or a fold
if (ts2 > ts) == self.fold:
ts = ts2
else:
ts = (self - _EPOCH) // timedelta(seconds=1)
localtm = _time.localtime(ts)
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -6212,6 +6212,10 @@ def test_system_transitions(self):
ts1 = dt.replace(fold=1).timestamp()
self.assertEqual(ts0, s0 + ss / 2)
self.assertEqual(ts1, s0 - ss / 2)
# gh-83861
utc0 = dt.astimezone(timezone.utc)
utc1 = dt.replace(fold=1).astimezone(timezone.utc)
self.assertEqual(utc0, utc1 + timedelta(0, ss))
finally:
if TZ is None:
del os.environ['TZ']
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix datetime.astimezone method return value when invoked on a naive datetime
instance that represents local time falling in a timezone transition gap.
PEP 495 requires that instances with fold=1 produce earlier times than those
with fold=0 in this case.
18 changes: 16 additions & 2 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -6153,17 +6153,31 @@ local_to_seconds(int year, int month, int day,
static PyObject *
local_timezone_from_local(PyDateTime_DateTime *local_dt)
{
long long seconds;
long long seconds, seconds2;
time_t timestamp;
int fold = DATE_GET_FOLD(local_dt);
seconds = local_to_seconds(GET_YEAR(local_dt),
GET_MONTH(local_dt),
GET_DAY(local_dt),
DATE_GET_HOUR(local_dt),
DATE_GET_MINUTE(local_dt),
DATE_GET_SECOND(local_dt),
DATE_GET_FOLD(local_dt));
fold);
if (seconds == -1)
return NULL;
seconds2 = local_to_seconds(GET_YEAR(local_dt),
GET_MONTH(local_dt),
GET_DAY(local_dt),
DATE_GET_HOUR(local_dt),
DATE_GET_MINUTE(local_dt),
DATE_GET_SECOND(local_dt),
!fold);
if (seconds2 == -1)
return NULL;
/* Detect gap */
if (seconds2 != seconds && (seconds2 > seconds) == fold)
seconds = seconds2;

/* XXX: add bounds check */
timestamp = seconds - epoch;
return local_timezone_from_timestamp(timestamp);
Expand Down