]> granicus.if.org Git - python/commitdiff
#665194: Update email.utils.localtime to use astimezone, and fix bug.
authorR David Murray <rdmurray@bitdance.com>
Thu, 23 Aug 2012 01:34:00 +0000 (21:34 -0400)
committerR David Murray <rdmurray@bitdance.com>
Thu, 23 Aug 2012 01:34:00 +0000 (21:34 -0400)
The new code correctly handles historic changes in UTC offsets.
A test for this should follow.

Original patch by Alexander Belopolsky.

Lib/email/utils.py
Lib/test/test_email/test_utils.py
Misc/NEWS

index 39f790364c0c1010d9e1ea801120f4a15395e9bc..c6204da27521f400c5a71edc20a8cf14b88de040 100644 (file)
@@ -386,33 +386,26 @@ def localtime(dt=None, isdst=-1):
 
     """
     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)
index d9c4d70c2f4032ca631e20e0e662748fa1702b51..7d0267e3adecd8d0b88c228e4dcb5fa13bbe79bd 100644 (file)
@@ -87,17 +87,23 @@ class LocaltimeTests(unittest.TestCase):
         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)
index ceab279431b2dacd2ffb9ce683dedd4f1c5a9336..16442cca911fe3c5032f168a68781c5887642886 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -24,6 +24,9 @@ Core and Builtins
 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.