summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Belopolsky <abalkin@users.noreply.github.com>2023-04-19 21:02:29 (GMT)
committerGitHub <noreply@github.com>2023-04-19 21:02:29 (GMT)
commit2b1260c55763a952c57b92fe0f274b6ee79efd05 (patch)
treef5d51ee35c77495a6ceee99fc4de216fdd9d2d06
parentd4aa8578b18d12380c841de96e8f80cac52bf61a (diff)
downloadcpython-2b1260c55763a952c57b92fe0f274b6ee79efd05.zip
cpython-2b1260c55763a952c57b92fe0f274b6ee79efd05.tar.gz
cpython-2b1260c55763a952c57b92fe0f274b6ee79efd05.tar.bz2
gh-83861: Fix datetime.astimezone() method (GH-101545)
-rw-r--r--Lib/datetime.py5
-rw-r--r--Lib/test/datetimetester.py4
-rw-r--r--Misc/NEWS.d/next/Library/2023-02-06-16-45-18.gh-issue-83861.mMbIU3.rst4
-rw-r--r--Modules/_datetimemodule.c18
4 files changed, 29 insertions, 2 deletions
diff --git a/Lib/datetime.py b/Lib/datetime.py
index 63714463..09a2d2d 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -1965,6 +1965,11 @@ class datetime(date):
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)
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 570f803..477f16f 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -6212,6 +6212,10 @@ class ZoneInfoTest(unittest.TestCase):
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']
diff --git a/Misc/NEWS.d/next/Library/2023-02-06-16-45-18.gh-issue-83861.mMbIU3.rst b/Misc/NEWS.d/next/Library/2023-02-06-16-45-18.gh-issue-83861.mMbIU3.rst
new file mode 100644
index 0000000..e85e7a4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-06-16-45-18.gh-issue-83861.mMbIU3.rst
@@ -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.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index eda8c56..f317dc1 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -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);