如何在Python中解析HTTP日期字符串?

52
有没有一种简单的方法在Python中解析HTTP日期字符串?根据标准,有几种格式化HTTP日期字符串的方法;该方法应该能够处理这个问题。
换句话说,我想将类似于“Wed, 23 Sep 2009 22:15:29 GMT”这样的字符串转换为Python时间结构。
4个回答

61
>>> import email.utils, datetime
>>> email.utils.parsedate('Wed, 23 Sep 2009 22:15:29 GMT')
(2009, 9, 23, 22, 15, 29, 0, 1, -1)

如果您想要一个datetime.datetime对象,您可以执行以下操作:
# Python <3.3
def my_parsedate(text):
    return datetime.datetime(*eut.parsedate(text)[:6])

# Python ≥3.3
def my_parsedate(text):
    return email.utils.parsedate_to_datetime(text)

email.utils.parsedate

尝试按照RFC 2822规则解析日期。但是,有些邮件程序不遵循该规定的格式,因此parsedate()在这种情况下会尝试正确地猜测。日期是一个包含RFC 2822日期的字符串,例如"Mon, 20 Nov 1995 19:12:08 -0500"。如果成功解析日期,parsedate()将返回一个9元组,可以直接传递给time.mktime();否则将返回None。请注意,结果元组的索引6、7和8不可用。

email.utils.parsedate_to_datetime

逆转format_datetime()。执行与parsedate()相同的功能,但成功时返回datetime;否则,如果日期包含无效值(如大于23的小时或时区偏移量不在-24和24小时之间),则引发ValueError。如果输入日期的时区为-0000,则datetime将是naive datetime;如果日期符合RFCs,则它将表示UTC中的时间,但没有关于消息日期来源实际时区的指示。如果输入日期具有任何其他有效的时区偏移量,则datetime将是一个aware datetime,并带有相应的时区tzinfo。

5
是的,parsedate可能是最好的折衷方案,虽然它的“宽容RFC 2822解析”与RFC 2616'2的“MUST”并不完全兼容。例如,在具有两位数年份的RFC 850格式(如Sunday, 06-Nov-94 08:49:37 GMT)上会出现严重失败,但2616表示客户端必须能够解析RFC 850日期(叹息)。 - Alex Martelli
3
请注意,email.util.parsedate()返回一个元组,可以直接传递给time.mktime()(这会给你一个整数,表示从计算机纪元开始算起的秒数,使用的是本地时间而不是UTC时间)。 - driax
2
@driax:自纪元以来的秒数不依赖于本地时区,例如 0 表示 1970-01-01T00:00:00Z -- 这是全球相同的时间点(本地时钟显示不同的值,但时间戳完全相同)。除非输入的时间字符串是在 UTC(GMT)中;否则你应该使用 mktime_tz(parsedate_tz()) 代替 -- 否则时区信息将丢失。 - jfs
8
在Python的更新版本中,您可以使用email.utils.parsedate_to_datetime函数。该函数可将电子邮件日期字符串解析为datetime.datetime对象,并在处理电子邮件等方面非常有用。 - mgilbert
1
另请参见 https://dev59.com/FnI-5IYBdhLWcg3wkJMA#8339750,其中包括时区解析的版本。 - andrewdotn
显示剩余5条评论

16

自从Python 3.3版本开始,有一个名为email.utils.parsedate_to_datetime的函数可以解析RFC 5322时间戳(也称为IMF-fixdate,即Internet消息格式中固定长度的格式,是RFC 7231中的HTTP-date子集)。

>>> from email.utils import parsedate_to_datetime
... 
... s = 'Sun, 06 Nov 1994 08:49:37 GMT'
... parsedate_to_datetime(s)
0: datetime.datetime(1994, 11, 6, 8, 49, 37, tzinfo=datetime.timezone.utc)

还有一个未经记录的http.cookiejar.http2time函数,可以实现相同的功能,方法如下:

>>> from datetime import datetime, timezone
... from http.cookiejar import http2time
... 
... s = 'Sun, 06 Nov 1994 08:49:37 GMT'
... datetime.utcfromtimestamp(http2time(s)).replace(tzinfo=timezone.utc)
1: datetime.datetime(1994, 11, 6, 8, 49, 37, tzinfo=datetime.timezone.utc)

它在Python 2.4中作为cookielib.http2time引入,用于处理以相同格式表示的Cookie Expires指令。


8
>>> import datetime
>>> datetime.datetime.strptime('Wed, 23 Sep 2009 22:15:29 GMT', '%a, %d %b %Y %H:%M:%S GMT')
datetime.datetime(2009, 9, 23, 22, 15, 29)

是的,它很容易扩展以处理任何格式。而email.utils.parse更健壮,但也不够透明。 - SilentGhost
1
+1并感谢。因为它建议避免这样的评论。比“utils”命名的模块更清晰。 - user237419

2
httplib.HTTPMessage(filehandle).getdate(headername)
httplib.HTTPMessage(filehandle).getdate_tz(headername)
mimetools.Message(filehandle).getdate()
rfc822.parsedate(datestr)
rfc822.parsedate_tz(datestr)
  • 如果您有原始数据流,可以从中构建一个HTTPMessage或mimetools.Message。在查询响应对象以获取信息时,它可能提供额外的帮助。
  • 如果您正在使用urllib2,则已经在urlopen返回的filehandler中隐藏了一个HTTPMessage对象。
  • 它可能可以解析许多日期格式。
  • httplib位于核心中。

注意:

  • 看了一下实现,HTTPMessage继承自mimetools.Message,后者又继承自rfc822.Message。也许您会对两个浮动定义感兴趣,即parsedate和parsedate_tz(在后者中)。
  • 来自email.utils的parsedate(_tz)具有不同的实现,尽管它看起来有点相似。

如果您只有那个字符串片段并想要解析它,您可以这样做:

>>> from rfc822 import parsedate, parsedate_tz
>>> parsedate('Wed, 23 Sep 2009 22:15:29 GMT')
(2009, 9, 23, 22, 15, 29, 0, 1, 0)
>>> 

但是让我通过MIME消息举例说明:
import mimetools
import StringIO
message = mimetools.Message(
    StringIO.StringIO('Date:Wed, 23 Sep 2009 22:15:29 GMT\r\n\r\n'))
>>> m
<mimetools.Message instance at 0x7fc259146710>
>>> m.getdate('Date')
(2009, 9, 23, 22, 15, 29, 0, 1, 0)

或通过HTTP消息(响应)

>>> from httplib import HTTPMessage
>>> from StringIO import StringIO
>>> http_response = HTTPMessage(StringIO('Date:Wed, 23 Sep 2009 22:15:29 GMT\r\n\r\n'))
>>> #http_response can be grabbed via urllib2.urlopen(url).info(), right?
>>> http_response.getdate('Date')
(2009, 9, 23, 22, 15, 29, 0, 1, 0)

没错吧?

>>> import urllib2
>>> urllib2.urlopen('https://fw.io/').info().getdate('Date')
(2014, 2, 19, 18, 53, 26, 0, 1, 0)

现在我们对日期格式、MIME消息、MIME工具及其Python实现有了更多的了解 ;-)

无论如何,这比使用email.utils来解析HTTP头要好。


2
目前看来(2016年12月),RFC 822已被弃用,文档建议使用电子邮件包作为首选方法。 https://docs.python.org/2/library/rfc822.html - StanleyZ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接