Skip to content

Commit

Permalink
gh-83861: Fix datetime.astimezone() method (GH-101545)
Browse files Browse the repository at this point in the history
  • Loading branch information
abalkin authored Apr 19, 2023
1 parent d4aa857 commit 2b1260c
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 2 deletions.
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

0 comments on commit 2b1260c

Please sign in to comment.