Commit d2d521ea authored by R David Murray's avatar R David Murray

#665194: Add a localtime function to email.utils.

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.
parent dcaf2ece
...@@ -93,8 +93,6 @@ There are several useful utilities provided in the :mod:`email.utils` module: ...@@ -93,8 +93,6 @@ There are several useful utilities provided in the :mod:`email.utils` module:
corresponding a :class:`~datetime.timezone` :class:`~datetime.tzinfo`. corresponding a :class:`~datetime.timezone` :class:`~datetime.tzinfo`.
.. versionadded:: 3.3 .. versionadded:: 3.3
.. function:: mktime_tz(tuple) .. function:: mktime_tz(tuple)
Turn a 10-tuple as returned by :func:`parsedate_tz` into a UTC timestamp. It 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: ...@@ -140,6 +138,22 @@ There are several useful utilities provided in the :mod:`email.utils` module:
.. versionadded:: 3.3 .. 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) .. function:: make_msgid(idstring=None, domain=None)
Returns a string suitable for an :rfc:`2822`\ -compliant Returns a string suitable for an :rfc:`2822`\ -compliant
......
...@@ -363,3 +363,56 @@ def collapse_rfc2231_value(value, errors='replace', ...@@ -363,3 +363,56 @@ def collapse_rfc2231_value(value, errors='replace',
except LookupError: except LookupError:
# charset is not a known codec. # charset is not a known codec.
return unquote(text) 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)
import datetime import datetime
from email import utils from email import utils
import test.support
import time
import unittest import unittest
class DateTimeTests(unittest.TestCase): class DateTimeTests(unittest.TestCase):
...@@ -43,3 +45,74 @@ class DateTimeTests(unittest.TestCase): ...@@ -43,3 +45,74 @@ class DateTimeTests(unittest.TestCase):
self.assertEqual( self.assertEqual(
utils.parsedate_to_datetime(self.datestring + ' -0000'), utils.parsedate_to_datetime(self.datestring + ' -0000'),
self.naive_dt) 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()
...@@ -506,6 +506,7 @@ Simon Johnston ...@@ -506,6 +506,7 @@ Simon Johnston
Matt Joiner Matt Joiner
Thomas Jollans Thomas Jollans
Nicolas Joly Nicolas Joly
Brian K. Jones
Evan Jones Evan Jones
Jeremy Jones Jeremy Jones
Richard Jones Richard Jones
......
...@@ -46,6 +46,9 @@ Core and Builtins ...@@ -46,6 +46,9 @@ Core and Builtins
Library 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 - Issue #12586: Added new provisional policies that implement convenient
unicode support for email headers. See What's New for details. unicode support for email headers. See What's New for details.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment