Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b8687df

Browse filesBrowse files
committed
#665194: Update email.utils.localtime to use astimezone, and fix bug.
The new code correctly handles historic changes in UTC offsets. A test for this should follow. Original patch by Alexander Belopolsky.
1 parent 17183a2 commit b8687df
Copy full SHA for b8687df

3 files changed

+33-31Lines changed: 33 additions & 31 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎Lib/email/utils.py‎

Copy file name to clipboardExpand all lines: Lib/email/utils.py
+22-29Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -386,33 +386,26 @@ def localtime(dt=None, isdst=-1):
386386
387387
"""
388388
if dt is None:
389-
seconds = time.time()
390-
else:
391-
if dt.tzinfo is None:
392-
# A naive datetime is given. Convert to a (localtime)
393-
# timetuple and pass to system mktime together with
394-
# the isdst hint. System mktime will return seconds
395-
# sysce epoch.
396-
tm = dt.timetuple()[:-1] + (isdst,)
397-
seconds = time.mktime(tm)
389+
dt = datetime.datetime.now(datetime.timezone.utc)
390+
if dt.tzinfo is not None:
391+
return dt.astimezone()
392+
# We have a naive datetime. Convert to a (localtime) timetuple and pass to
393+
# system mktime together with the isdst hint. System mktime will return
394+
# seconds since epoch.
395+
tm = dt.timetuple()[:-1] + (isdst,)
396+
seconds = time.mktime(tm)
397+
localtm = time.localtime(seconds)
398+
try:
399+
delta = datetime.timedelta(seconds=localtm.tm_gmtoff)
400+
tz = datetime.timezone(delta, localtm.tm_zone)
401+
except AttributeError:
402+
# Compute UTC offset and compare with the value implied by tm_isdst.
403+
# If the values match, use the zone name implied by tm_isdst.
404+
delta = dt - datetime.datetime(*time.gmtime(ts)[:6])
405+
dst = time.daylight and localtm.tm_isdst > 0
406+
gmtoff = -(time.altzone if dst else time.timezone)
407+
if delta == datetime.timedelta(seconds=gmtoff):
408+
tz = datetime.timezone(delta, time.tzname[dst])
398409
else:
399-
# An aware datetime is given. Use aware datetime
400-
# arithmetics to find seconds since epoch.
401-
delta = dt - datetime.datetime(1970, 1, 1,
402-
tzinfo=datetime.timezone.utc)
403-
seconds = delta.total_seconds()
404-
tm = time.localtime(seconds)
405-
406-
# XXX: The following logic may not work correctly if UTC
407-
# offset has changed since time provided in dt. This will be
408-
# corrected in C implementation for platforms that support
409-
# tm_gmtoff.
410-
if time.daylight and tm.tm_isdst:
411-
offset = time.altzone
412-
tzname = time.tzname[1]
413-
else:
414-
offset = time.timezone
415-
tzname = time.tzname[0]
416-
417-
tz = datetime.timezone(datetime.timedelta(seconds=-offset), tzname)
418-
return datetime.datetime.fromtimestamp(seconds, tz)
410+
tz = datetime.timezone(delta)
411+
return dt.replace(tzinfo=tz)
Collapse file

‎Lib/test/test_email/test_utils.py‎

Copy file name to clipboardExpand all lines: Lib/test/test_email/test_utils.py
+8-2Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,23 @@ def test_localtime_daylight_false_dst_true(self):
8787
t2 = utils.localtime(t1)
8888
self.assertEqual(t1, t2)
8989

90+
@test.support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
9091
def test_localtime_epoch_utc_daylight_true(self):
9192
test.support.patch(self, time, 'daylight', True)
9293
t0 = datetime.datetime(1970, 1, 1, tzinfo = datetime.timezone.utc)
9394
t1 = utils.localtime(t0)
94-
self.assertEqual(t0, t1)
95+
t2 = t0 - datetime.timedelta(hours=5)
96+
t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5)))
97+
self.assertEqual(t1, t2)
9598

99+
@test.support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
96100
def test_localtime_epoch_utc_daylight_false(self):
97101
test.support.patch(self, time, 'daylight', False)
98102
t0 = datetime.datetime(1970, 1, 1, tzinfo = datetime.timezone.utc)
99103
t1 = utils.localtime(t0)
100-
self.assertEqual(t0, t1)
104+
t2 = t0 - datetime.timedelta(hours=5)
105+
t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5)))
106+
self.assertEqual(t1, t2)
101107

102108
def test_localtime_epoch_notz_daylight_true(self):
103109
test.support.patch(self, time, 'daylight', True)
Collapse file

‎Misc/NEWS‎

Copy file name to clipboardExpand all lines: Misc/NEWS
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ Core and Builtins
2424
Library
2525
-------
2626

27+
- Issue ##665194: Update email.utils.localtime to use datetime.astimezone and
28+
correctly handle historic changes in UTC offsets.
29+
2730
- Issue #15199: Fix JavaScript's default MIME type to application/javascript.
2831
Patch by Bohuslav Kabrda.
2932

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.