=== modified file 'Doc/library/email.util.rst' --- Doc/library/email.util.rst 2010-02-04 16:41:57 +0000 +++ Doc/library/email.util.rst 2010-09-28 00:32:09 +0000 @@ -55,33 +55,13 @@ .. function:: parsedate(date) - Attempts to parse a date according to the rules in :rfc:`2822`. however, some + Attempts to parse a date according to the rules in :rfc:`2822`. However, some mailers don't follow that format as specified, so :func:`parsedate` tries to guess correctly in such cases. *date* is a string containing an :rfc:`2822` date, such as ``"Mon, 20 Nov 1995 19:12:08 -0500"``. If it succeeds in parsing - the date, :func:`parsedate` returns a 9-tuple that can be passed directly to - :func:`time.mktime`; otherwise ``None`` will be returned. Note that indexes 6, - 7, and 8 of the result tuple are not usable. - - -.. function:: parsedate_tz(date) - - Performs the same function as :func:`parsedate`, but returns either ``None`` or - a 10-tuple; the first 9 elements make up a tuple that can be passed directly to - :func:`time.mktime`, and the tenth is the offset of the date's timezone from UTC - (which is the official term for Greenwich Mean Time) [#]_. If the input string - has no timezone, the last element of the tuple returned is ``None``. Note that - indexes 6, 7, and 8 of the result tuple are not usable. - - -.. function:: mktime_tz(tuple) - - Turn a 10-tuple as returned by :func:`parsedate_tz` into a UTC timestamp. It - the timezone item in the tuple is ``None``, assume local time. Minor - deficiency: :func:`mktime_tz` interprets the first 8 elements of *tuple* as a - local time and then compensates for the timezone difference. This may yield a - slight error around changes in daylight savings time, though not worth worrying - about for common use. + the date, :func:`parsedate` returns a :class:`datetime.datetime`` object, + with the ``tzinfo`` attribute set appropriately if parseable from the input + string. If parsing fails, this function returns ``None``. .. function:: formatdate(timeval=None, localtime=False, usegmt=False) === modified file 'Lib/email/_parseaddr.py' --- Lib/email/_parseaddr.py 2010-08-25 00:45:55 +0000 +++ Lib/email/_parseaddr.py 2010-09-27 23:37:09 +0000 @@ -7,12 +7,11 @@ """ __all__ = [ - 'mktime_tz', 'parsedate', - 'parsedate_tz', 'quote', ] +import datetime import time SPACE = ' ' @@ -42,11 +41,17 @@ } -def parsedate_tz(data): - """Convert a date string to a time tuple. - - Accounts for military timezones. +def parsedate(data): + """Returns a datetime.datetime object corresponding to the date and time + parsed from the specified string. + + This function returns None if it is unable to parse a date from the + specified input string. + + This function also accounts for military timezones. """ + if len(data) == 0: + return None data = data.split() # The FWS after the comma after the day-of-week is optional, so search and # adjust for this. @@ -128,35 +133,20 @@ tzoffset = int(tz) except ValueError: pass - # Convert a timezone offset into seconds ; -0500 -> -18000 - if tzoffset: + # Determine the timezone, if it exists. Note tzoffset might be 0. + tz = None + if tzoffset is not None: if tzoffset < 0: tzsign = -1 tzoffset = -tzoffset else: tzsign = 1 - tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60) - # Daylight Saving Time flag is set to -1, since DST is unknown. - return yy, mm, dd, thh, tmm, tss, 0, 1, -1, tzoffset - - -def parsedate(data): - """Convert a time string to a time tuple.""" - t = parsedate_tz(data) - if isinstance(t, tuple): - return t[:9] - else: - return t - - -def mktime_tz(data): - """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp.""" - if data[9] is None: - # No zone info, so localtime is better assumption than GMT - return time.mktime(data[:8] + (-1,)) - else: - t = time.mktime(data[:8] + (0,)) - return t - data[9] - time.timezone + # Compute the timezone offset in minutes. + tzoffset = tzsign * ((tzoffset // 100) * 60 + (tzoffset % 100)) + # Create the timezone object given the number of seconds offset. + tz = datetime.timezone(datetime.timedelta(minutes=tzoffset)) + # Set microseconds to 0. + return datetime.datetime(yy, mm, dd, thh, tmm, tss, 0, tz) def quote(str): === modified file 'Lib/email/test/test_email.py' --- Lib/email/test/test_email.py 2010-09-20 14:04:53 +0000 +++ Lib/email/test/test_email.py 2010-09-28 00:19:02 +0000 @@ -2,6 +2,7 @@ # Contact: email-sig@python.org # email package unit tests +import datetime import os import sys import time @@ -2187,14 +2188,27 @@ def test_formatdate(self): now = time.time() - self.assertEqual(utils.parsedate(utils.formatdate(now))[:6], - time.gmtime(now)[:6]) + # The expected datetime is the current time in the UTC timezone. + timezone = datetime.timezone(datetime.timedelta()) + expected_datetime = datetime.datetime.fromtimestamp(now, timezone) + # utils.parsedate() doesn't account for microseconds. + expected_datetime = expected_datetime.replace(microsecond=0) + parsed_datetime = utils.parsedate(utils.formatdate(now)) + self.assertEqual(parsed_datetime, expected_datetime) def test_formatdate_localtime(self): now = time.time() - self.assertEqual( - utils.parsedate(utils.formatdate(now, localtime=True))[:6], - time.localtime(now)[:6]) + # Compute the timezone offset in seconds, accounting for DST. + seconds = -time.timezone + if time.daylight: + seconds -= 3600 + timezone = datetime.timezone(datetime.timedelta(seconds=seconds)) + expected_datetime = datetime.datetime.fromtimestamp(now, timezone) + # utils.parsedate() doesn't account for microseconds. + expected_datetime = expected_datetime.replace(microsecond=0) + parsed_datetime = utils.parsedate(utils.formatdate(now, + localtime=True)) + self.assertEqual(parsed_datetime, expected_datetime) def test_formatdate_usegmt(self): now = time.time() @@ -2215,24 +2229,15 @@ def test_parsedate_no_dayofweek(self): eq = self.assertEqual - eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'), - (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800)) + tz = datetime.timezone(datetime.timedelta(hours=-8)) + eq(utils.parsedate('25 Feb 2003 13:47:26 -0800'), + datetime.datetime(2003, 2, 25, 13, 47, 26, 0, tz)) def test_parsedate_compact_no_dayofweek(self): eq = self.assertEqual - eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'), - (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800)) - - def test_parsedate_acceptable_to_time_functions(self): - eq = self.assertEqual - timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800') - t = int(time.mktime(timetup)) - eq(time.localtime(t)[:6], timetup[:6]) - eq(int(time.strftime('%Y', timetup)), 2003) - timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800') - t = int(time.mktime(timetup[:9])) - eq(time.localtime(t)[:6], timetup[:6]) - eq(int(time.strftime('%Y', timetup[:9])), 2003) + tz = datetime.timezone(datetime.timedelta(hours=-8)) + eq(utils.parsedate('5 Feb 2003 13:47:26 -0800'), + datetime.datetime(2003, 2, 5, 13, 47, 26, 0, tz)) def test_parsedate_y2k(self): """Test for parsing a date with a two-digit year. @@ -2242,10 +2247,10 @@ obsoletes RFC822) requires four-digit years. """ - self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'), - utils.parsedate_tz('25 Feb 2003 13:47:26 -0800')) - self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'), - utils.parsedate_tz('25 Feb 1971 13:47:26 -0800')) + self.assertEqual(utils.parsedate('25 Feb 03 13:47:26 -0800'), + utils.parsedate('25 Feb 2003 13:47:26 -0800')) + self.assertEqual(utils.parsedate('25 Feb 71 13:47:26 -0800'), + utils.parsedate('25 Feb 1971 13:47:26 -0800')) def test_parseaddr_empty(self): self.assertEqual(utils.parseaddr('<>'), ('', '')) === modified file 'Lib/email/utils.py' --- Lib/email/utils.py 2010-07-01 21:39:58 +0000 +++ Lib/email/utils.py 2010-09-27 18:12:55 +0000 @@ -13,10 +13,8 @@ 'formatdate', 'getaddresses', 'make_msgid', - 'mktime_tz', 'parseaddr', 'parsedate', - 'parsedate_tz', 'unquote', ] @@ -32,11 +30,8 @@ from email._parseaddr import quote from email._parseaddr import AddressList as _AddressList -from email._parseaddr import mktime_tz -# We need wormarounds for bugs in these methods in older Pythons (see below) -from email._parseaddr import parsedate as _parsedate -from email._parseaddr import parsedate_tz as _parsedate_tz +from email._parseaddr import parsedate from quopri import decodestring as _qdecode @@ -173,18 +168,6 @@ # These functions are in the standalone mimelib version only because they've # subsequently been fixed in the latest Python versions. We use this to worm # around broken older Pythons. -def parsedate(data): - if not data: - return None - return _parsedate(data) - - -def parsedate_tz(data): - if not data: - return None - return _parsedate_tz(data) - - def parseaddr(addr): addrs = _AddressList(addr).addresslist if not addrs: