]> granicus.if.org Git - python/commitdiff
#665194: Add a localtime function to email.utils.
authorR David Murray <rdmurray@bitdance.com>
Sat, 26 May 2012 03:22:59 +0000 (23:22 -0400)
committerR David Murray <rdmurray@bitdance.com>
Sat, 26 May 2012 03:22:59 +0000 (23:22 -0400)
Without this function people would be tempted to use the other date functions
in email.utils to compute an aware localtime, and those functions are not as
good for that purpose as this code.  The code is Alexander Belopolsy's from
his proposed patch for issue 9527, with a fix (and additional tests) by Brian
K. Jones.

Doc/library/email.util.rst
Lib/email/utils.py
Lib/test/test_email/test_utils.py
Misc/ACKS
Misc/NEWS

index 2f9ef89380cbe2e2d9537c0994bde5556534107f..1383104b1b7cb5c076719688f0068e341e7cdd36 100644 (file)
@@ -93,8 +93,6 @@ There are several useful utilities provided in the :mod:`email.utils` module:
    corresponding a :class:`~datetime.timezone` :class:`~datetime.tzinfo`.
 
    .. versionadded:: 3.3
-
-
 .. function:: mktime_tz(tuple)
 
    Turn a 10-tuple as returned by :func:`parsedate_tz` into a UTC timestamp.  It
@@ -140,6 +138,22 @@ There are several useful utilities provided in the :mod:`email.utils` module:
    .. versionadded:: 3.3
 
 
+.. function:: localtime(dt=None)
+
+    Return local time as an aware datetime object.  If called without
+    arguments, return current time.  Otherwise *dt* argument should be a
+    :class:`~datetime.datetime` instance, and it is converted to the local time
+    zone according to the system time zone database.  If *dt* is naive (that
+    is, ``dt.tzinfo`` is ``None``), it is assumed to be in local time.  In this
+    case, a positive or zero value for *isdst* causes ``localtime`` to presume
+    initially that summer time (for example, Daylight Saving Time) is or is not
+    (respectively) in effect for the specified time.  A negative value for
+    *isdst* causes the ``localtime`` to attempt to divine whether summer time
+    is in effect for the specified time.
+
+    .. versionadded:: 3.3
+
+
 .. function:: make_msgid(idstring=None, domain=None)
 
    Returns a string suitable for an :rfc:`2822`\ -compliant
index b7e1bb99f4cc7d9d53af3df8250fa8c74c5af7af..39f790364c0c1010d9e1ea801120f4a15395e9bc 100644 (file)
@@ -363,3 +363,56 @@ def collapse_rfc2231_value(value, errors='replace',
     except LookupError:
         # charset is not a known codec.
         return unquote(text)
+
+
+#
+# datetime doesn't provide a localtime function yet, so provide one.  Code
+# adapted from the patch in issue 9527.  This may not be perfect, but it is
+# better than not having it.
+#
+
+def localtime(dt=None, isdst=-1):
+    """Return local time as an aware datetime object.
+
+    If called without arguments, return current time.  Otherwise *dt*
+    argument should be a datetime instance, and it is converted to the
+    local time zone according to the system time zone database.  If *dt* is
+    naive (that is, dt.tzinfo is None), it is assumed to be in local time.
+    In this case, a positive or zero value for *isdst* causes localtime to
+    presume initially that summer time (for example, Daylight Saving Time)
+    is or is not (respectively) in effect for the specified time.  A
+    negative value for *isdst* causes the localtime() function to attempt
+    to divine whether summer time is in effect for the specified time.
+
+    """
+    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)
+        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)
index e003a64895fb5f1438302ff3bbb7c04b9dd17f74..d9c4d70c2f4032ca631e20e0e662748fa1702b51 100644 (file)
@@ -1,5 +1,7 @@
 import datetime
 from email import utils
+import test.support
+import time
 import unittest
 
 class DateTimeTests(unittest.TestCase):
@@ -43,3 +45,74 @@ class DateTimeTests(unittest.TestCase):
         self.assertEqual(
             utils.parsedate_to_datetime(self.datestring + ' -0000'),
             self.naive_dt)
+
+
+class LocaltimeTests(unittest.TestCase):
+
+    def test_localtime_is_tz_aware_daylight_true(self):
+        test.support.patch(self, time, 'daylight', True)
+        t = utils.localtime()
+        self.assertIsNot(t.tzinfo, None)
+
+    def test_localtime_is_tz_aware_daylight_false(self):
+        test.support.patch(self, time, 'daylight', False)
+        t = utils.localtime()
+        self.assertIsNot(t.tzinfo, None)
+
+    def test_localtime_daylight_true_dst_false(self):
+        test.support.patch(self, time, 'daylight', True)
+        t0 = datetime.datetime(2012, 3, 12, 1, 1)
+        t1 = utils.localtime(t0, isdst=-1)
+        t2 = utils.localtime(t1)
+        self.assertEqual(t1, t2)
+
+    def test_localtime_daylight_false_dst_false(self):
+        test.support.patch(self, time, 'daylight', False)
+        t0 = datetime.datetime(2012, 3, 12, 1, 1)
+        t1 = utils.localtime(t0, isdst=-1)
+        t2 = utils.localtime(t1)
+        self.assertEqual(t1, t2)
+
+    def test_localtime_daylight_true_dst_true(self):
+        test.support.patch(self, time, 'daylight', True)
+        t0 = datetime.datetime(2012, 3, 12, 1, 1)
+        t1 = utils.localtime(t0, isdst=1)
+        t2 = utils.localtime(t1)
+        self.assertEqual(t1, t2)
+
+    def test_localtime_daylight_false_dst_true(self):
+        test.support.patch(self, time, 'daylight', False)
+        t0 = datetime.datetime(2012, 3, 12, 1, 1)
+        t1 = utils.localtime(t0, isdst=1)
+        t2 = utils.localtime(t1)
+        self.assertEqual(t1, t2)
+
+    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)
+
+    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)
+
+    def test_localtime_epoch_notz_daylight_true(self):
+        test.support.patch(self, time, 'daylight', True)
+        t0 = datetime.datetime(1970, 1, 1)
+        t1 = utils.localtime(t0)
+        t2 = utils.localtime(t0.replace(tzinfo=None))
+        self.assertEqual(t1, t2)
+
+    def test_localtime_epoch_notz_daylight_false(self):
+        test.support.patch(self, time, 'daylight', False)
+        t0 = datetime.datetime(1970, 1, 1)
+        t1 = utils.localtime(t0)
+        t2 = utils.localtime(t0.replace(tzinfo=None))
+        self.assertEqual(t1, t2)
+
+
+if __name__ == '__main__':
+    unittest.main()
index 265cb1784e7b94996acf35a9e1c41a775f67ad90..16f55ea50e59910ff9538aecbccaa920ac7900c5 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -506,6 +506,7 @@ Simon Johnston
 Matt Joiner
 Thomas Jollans
 Nicolas Joly
+Brian K. Jones
 Evan Jones
 Jeremy Jones
 Richard Jones
index 369b1a51b25c49028369b1d9b694582d316bdacb..4f20446f2f980a8a7ad77b43cb443d169f29006a 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -46,6 +46,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #665194: Added a localtime function to email.utils to provide an
+  aware local datetime for use in setting Date headers.
+
 - Issue #12586: Added new provisional policies that implement convenient
   unicode support for email headers.  See What's New for details.