[Python-checkins] cpython: #665194: Update email.utils.localtime to use astimezone, and fix bug.

r.david.murray python-checkins at python.org
Thu Aug 23 03:34:29 CEST 2012


http://hg.python.org/cpython/rev/71b9cca81598
changeset:   78709:71b9cca81598
user:        R David Murray <rdmurray at bitdance.com>
date:        Wed Aug 22 21:34:00 2012 -0400
summary:
  #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.

files:
  Lib/email/utils.py                |  51 ++++++++----------
  Lib/test/test_email/test_utils.py |  10 ++-
  Misc/NEWS                         |   3 +
  3 files changed, 33 insertions(+), 31 deletions(-)


diff --git a/Lib/email/utils.py b/Lib/email/utils.py
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -386,33 +386,26 @@
 
     """
     if dt is None:
-        seconds = time.time()
-    else:
-        if dt.tzinfo is None:
-            # A naive datetime is given.  Convert to a (localtime)
-            # timetuple and pass to system mktime together with
-            # the isdst hint.  System mktime will return seconds
-            # sysce epoch.
-            tm = dt.timetuple()[:-1] + (isdst,)
-            seconds = time.mktime(tm)
+        dt = datetime.datetime.now(datetime.timezone.utc)
+    if dt.tzinfo is not None:
+        return dt.astimezone()
+    # We have a naive datetime.  Convert to a (localtime) timetuple and pass to
+    # system mktime together with the isdst hint.  System mktime will return
+    # seconds since epoch.
+    tm = dt.timetuple()[:-1] + (isdst,)
+    seconds = time.mktime(tm)
+    localtm = time.localtime(seconds)
+    try:
+        delta = datetime.timedelta(seconds=localtm.tm_gmtoff)
+        tz = datetime.timezone(delta, localtm.tm_zone)
+    except AttributeError:
+        # Compute UTC offset and compare with the value implied by tm_isdst.
+        # If the values match, use the zone name implied by tm_isdst.
+        delta = dt - datetime.datetime(*time.gmtime(ts)[:6])
+        dst = time.daylight and localtm.tm_isdst > 0
+        gmtoff = -(time.altzone if dst else time.timezone)
+        if delta == datetime.timedelta(seconds=gmtoff):
+            tz = datetime.timezone(delta, time.tzname[dst])
         else:
-            # An aware datetime is given.  Use aware datetime
-            # arithmetics to find seconds since epoch.
-            delta = dt - datetime.datetime(1970, 1, 1,
-                                           tzinfo=datetime.timezone.utc)
-            seconds = delta.total_seconds()
-    tm = time.localtime(seconds)
-
-    # XXX: The following logic may not work correctly if UTC
-    # offset has changed since time provided in dt.  This will be
-    # corrected in C implementation for platforms that support
-    # tm_gmtoff.
-    if time.daylight and tm.tm_isdst:
-        offset = time.altzone
-        tzname = time.tzname[1]
-    else:
-        offset = time.timezone
-        tzname = time.tzname[0]
-
-    tz = datetime.timezone(datetime.timedelta(seconds=-offset), tzname)
-    return datetime.datetime.fromtimestamp(seconds, tz)
+            tz = datetime.timezone(delta)
+    return dt.replace(tzinfo=tz)
diff --git a/Lib/test/test_email/test_utils.py b/Lib/test/test_email/test_utils.py
--- a/Lib/test/test_email/test_utils.py
+++ b/Lib/test/test_email/test_utils.py
@@ -87,17 +87,23 @@
         t2 = utils.localtime(t1)
         self.assertEqual(t1, t2)
 
+    @test.support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
     def test_localtime_epoch_utc_daylight_true(self):
         test.support.patch(self, time, 'daylight', True)
         t0 = datetime.datetime(1970, 1, 1, tzinfo = datetime.timezone.utc)
         t1 = utils.localtime(t0)
-        self.assertEqual(t0, t1)
+        t2 = t0 - datetime.timedelta(hours=5)
+        t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5)))
+        self.assertEqual(t1, t2)
 
+    @test.support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
     def test_localtime_epoch_utc_daylight_false(self):
         test.support.patch(self, time, 'daylight', False)
         t0 = datetime.datetime(1970, 1, 1, tzinfo = datetime.timezone.utc)
         t1 = utils.localtime(t0)
-        self.assertEqual(t0, t1)
+        t2 = t0 - datetime.timedelta(hours=5)
+        t2 = t2.replace(tzinfo = datetime.timezone(datetime.timedelta(hours=-5)))
+        self.assertEqual(t1, t2)
 
     def test_localtime_epoch_notz_daylight_true(self):
         test.support.patch(self, time, 'daylight', True)
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,6 +24,9 @@
 Library
 -------
 
+- Issue ##665194: Update email.utils.localtime to use datetime.astimezone and
+  correctly handle historic changes in UTC offsets.
+
 - Issue #15199: Fix JavaScript's default MIME type to application/javascript.
   Patch by Bohuslav Kabrda.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list