从电子邮件中解析带有时区的日期?
- 2025-02-28 08:24:00
- admin 原创
- 65
问题描述:
我正在尝试从电子邮件中检索日期。起初这很容易:
message = email.parser.Parser().parse(file)
date = message['Date']
print date
我收到:
'Mon, 16 Nov 2009 13:32:02 +0100'
但我需要一个好的日期时间对象,所以我使用:
datetime.strptime('Mon, 16 Nov 2009 13:32:02 +0100', '%a, %d %b %Y %H:%M:%S %Z')
这引发了ValueError, since %Z isn't format for +0100
。但我在文档中找不到时区的正确格式,只有这个%Z
时区。有人能帮我吗?
解决方案 1:
email.utils
具有parsedate()
针对 RFC 2822 格式的函数,据我所知,该函数并未被弃用。
>>> import email.utils
>>> import time
>>> import datetime
>>> email.utils.parsedate('Mon, 16 Nov 2009 13:32:02 +0100')
(2009, 11, 16, 13, 32, 2, 0, 1, -1)
>>> time.mktime((2009, 11, 16, 13, 32, 2, 0, 1, -1))
1258378322.0
>>> datetime.datetime.fromtimestamp(1258378322.0)
datetime.datetime(2009, 11, 16, 13, 32, 2)
但请注意,该parsedate
方法不考虑时区,并且time.mktime
始终需要本地时间元组。
>>> (time.mktime(email.utils.parsedate('Mon, 16 Nov 2009 13:32:02 +0900')) ==
... time.mktime(email.utils.parsedate('Mon, 16 Nov 2009 13:32:02 +0100'))
True
因此,您仍然需要解析时区并考虑当地时差:
>>> REMOTE_TIME_ZONE_OFFSET = +9 * 60 * 60
>>> (time.mktime(email.utils.parsedate('Mon, 16 Nov 2009 13:32:02 +0900')) +
... time.timezone - REMOTE_TIME_ZONE_OFFSET)
1258410122.0
解决方案 2:
使用email.utils.parsedate_tz(date)
:
msg=email.message_from_file(open(file_name))
date=None
date_str=msg.get('date')
if date_str:
date_tuple=email.utils.parsedate_tz(date_str)
if date_tuple:
date=datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
if date:
... # valid date found
解决方案 3:
对于 python 3.3+,您可以使用parsedate_to_datetime函数:
>>> from email.utils import parsedate_to_datetime
>>> parsedate_to_datetime('Mon, 16 Nov 2009 13:32:02 +0100')
...
datetime.datetime(2009, 11, 16, 13, 32, 2, tzinfo=datetime.timezone(datetime.timedelta(0, 3600)))
官方文档:
format_datetime() 的逆函数。执行与 parsedate() 相同的功能,但成功时返回日期时间。如果输入日期的时区为 -0000,则日期时间将为简单日期时间,如果日期符合 RFC,它将表示 UTC 时间,但不指示日期来源消息的实际源时区。如果输入日期具有任何其他有效时区偏移,则日期时间将为具有相应时区 tzinfo 的感知日期时间。版本 3.3 中的新增功能。
解决方案 4:
在 Python 3.3+ 中,email
message 可以为您解析标题:
import email
import email.policy
headers = email.message_from_file(file, policy=email.policy.default)
print(headers.get('date').datetime)
# -> 2009-11-16 13:32:02+01:00
从 Python 3.2+ 开始,如果将其替换%Z
为%z
:
>>> from datetime import datetime
>>> datetime.strptime("Mon, 16 Nov 2009 13:32:02 +0100",
... "%a, %d %b %Y %H:%M:%S %z")
datetime.datetime(2009, 11, 16, 13, 32, 2,
tzinfo=datetime.timezone(datetime.timedelta(0, 3600)))
或者使用email
包(Python 3.3+):
>>> from email.utils import parsedate_to_datetime
>>> parsedate_to_datetime("Mon, 16 Nov 2009 13:32:02 +0100")
datetime.datetime(2009, 11, 16, 13, 32, 2,
tzinfo=datetime.timezone(datetime.timedelta(0, 3600)))
如果指定了 UTC 偏移量,-0000
则它将返回一个表示 UTC 时间的简单日期时间对象,否则它将返回具有相应tzinfo
集合的感知日期时间对象。
要在早期 Python 版本(2.6+)上解析rfc 5322 日期时间字符串:
from calendar import timegm
from datetime import datetime, timedelta, tzinfo
from email.utils import parsedate_tz
ZERO = timedelta(0)
time_string = 'Mon, 16 Nov 2009 13:32:02 +0100'
tt = parsedate_tz(time_string)
#NOTE: mktime_tz is broken on Python < 2.7.4,
# see https://bugs.python.org/issue21267
timestamp = timegm(tt) - tt[9] # local time - utc offset == utc time
naive_utc_dt = datetime(1970, 1, 1) + timedelta(seconds=timestamp)
aware_utc_dt = naive_utc_dt.replace(tzinfo=FixedOffset(ZERO, 'UTC'))
aware_dt = aware_utc_dt.astimezone(FixedOffset(timedelta(seconds=tt[9])))
print(aware_utc_dt)
print(aware_dt)
# -> 2009-11-16 12:32:02+00:00
# -> 2009-11-16 13:32:02+01:00
其中FixedOffset
基于文档tzinfo
中的子类datetime
:
class FixedOffset(tzinfo):
"""Fixed UTC offset: `time = utc_time + utc_offset`."""
def __init__(self, offset, name=None):
self.__offset = offset
if name is None:
seconds = abs(offset).seconds
assert abs(offset).days == 0
hours, seconds = divmod(seconds, 3600)
if offset < ZERO:
hours = -hours
minutes, seconds = divmod(seconds, 60)
assert seconds == 0
#NOTE: the last part is to remind about deprecated POSIX
# GMT+h timezones that have the opposite sign in the
# name; the corresponding numeric value is not used e.g.,
# no minutes
self.__name = '<%+03d%02d>GMT%+d' % (hours, minutes, -hours)
else:
self.__name = name
def utcoffset(self, dt=None):
return self.__offset
def tzname(self, dt=None):
return self.__name
def dst(self, dt=None):
return ZERO
def __repr__(self):
return 'FixedOffset(%r, %r)' % (self.utcoffset(), self.tzname())
解决方案 5:
你试过吗
rfc822.parsedate_tz(date) # ?
有关 RFC822 的更多信息,请参阅http://docs.python.org/library/rfc822.html
但它已被弃用(parsedate_tz 现在在email.utils.parsedate_tz
)。
但也许这些答案有帮助:
如何在 python 中解析带有 -0400 时区字符串的日期?
Python 时间年龄第 2 部分,时区
解决方案 6:
# Parses Nginx' format of "01/Jan/1999:13:59:59 +0400"
# Unfortunately, strptime doesn't support %z for the UTC offset (despite what
# the docs actually say), hence the need # for this function.
def parseDate(dateStr):
date = datetime.datetime.strptime(dateStr[:-6], "%d/%b/%Y:%H:%M:%S")
offsetDir = dateStr[-5]
offsetHours = int(dateStr[-4:-2])
offsetMins = int(dateStr[-2:])
if offsetDir == "-":
offsetHours = -offsetHours
offsetMins = -offsetMins
return date + datetime.timedelta(hours=offsetHours, minutes=offsetMins)
解决方案 7:
对于那些想要获得正确当地时间的人,我做了以下事情:
from datetime import datetime
from email.utils import parsedate_to_datetime
mail_time_str = 'Mon, 16 Nov 2009 13:32:02 +0100'
local_time_str = datetime.fromtimestamp(parsedate_to_datetime(mail_time_str).timestamp()).strftime('%Y-%m-%d %H:%M:%S')
print(local_time_str)
解决方案 8:
ValueError: 'z' is a bad directive in format...
(注意:在我的例子中我必须坚持使用 Python 2.7)
我在解析输出中的提交日期时遇到了类似的问题,git log --date=iso8601
其实际上不是 ISO8601 格式(因此--date=iso8601-strict
在更高版本中添加了)。
因为我正在使用,所以django
我可以利用那里的实用程序。
https://github.com/django/django/blob/master/django/utils/dateparse.py
>>> from django.utils.dateparse import parse_datetime
>>> parse_datetime('2013-07-23T15:10:59.342107+01:00')
datetime.datetime(2013, 7, 23, 15, 10, 59, 342107, tzinfo=+0100)
您也strptime
可以使用自己的正则表达式。
扫码咨询,免费领取项目管理大礼包!