2009-11-24 3 views
25

Я пытаюсь получить дату из письма. Сначала это легко:Дата составления с часовым поясом от электронной почты?

message = email.parser.Parser().parse(file) 
date = message['Date'] 
print date 

и я получаю:

'Mon, 16 Nov 2009 13:32:02 +0100' 

Но мне нужен хороший объект DATETIME, поэтому я использую:

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 для зоны. Может ли кто-нибудь помочь мне в этом?

ответ

24

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 всегда ожидает местное время кортежа, как упоминалось here.

>>> (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 
+0

Да, эти функции, похоже, были перемещены в utils, и электронная почта подходит для использования. Благодарю. – gruszczy

+0

Это не даст точной стоимости. 'time.mktime' принимает локальный кортеж, а функция parsedate не учитывает часовой пояс:' time.mktime (email.utils.parsedate ('Mon, 16 Nov 2009 13:32:02 +0900')) == time.mktime (email.utils.parsedate ('Mon, 16 ноября 2009 13:32:02 +0100')) 'возвращает' True'. Пометка @gruszczy в том случае, если он полагается на этот метод. –

+3

'mktime + timezone' может порождать неправильные значения для прошлых дат или если часовой пояс имеет переходы DST:' time.timezone! = Time.altzone'. Используйте 'tt = parsedate_tz (date_str); timestamp = calendar.timegm (tt) - tt [9] '. – jfs

2

Вы пробовали

rfc822.parsedate_tz(date) # ? 

Подробнее о RFC822, http://docs.python.org/library/rfc822.html

Это осуждается (parsedate_tz теперь в email.utils.parsedate_tz), хотя.

Но, возможно, эти ответы помогут:

+0

Да, я видел его, но это не рекомендуется. – gruszczy

+0

Есть ли смысл использовать его? – gruszczy

+0

Эта функция теперь известна как email.utils.parsedate_tz(), FWIW. – SamB

24

Использование 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 
+0

'mktime_tz' может выйти из строя на Python до 2.7.4, если локальный часовой пояс имел разное UTC-смещение на' date_tuple'. [Используйте 'calendar.timegm()' непосредственно в этом случае.] (Http://stackoverflow.com/questions/1790795/python-parsing-date-with-timezone-from-an-email#comment35349147_1790885) – jfs

3

В Python 3.3 +, email сообщение может разобрать заголовки для вас:

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 то возвращает наивный объект datetime, который представляет время в формате UTC, иначе он возвращает известный объект datetime с соответствующим набором tzinfo.

Для разбора rfc 5322 date-time string на более ранних версиях Python (2.6+):

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 is based on tzinfo subclass from the datetime documentation:

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()) 
-1

ValueError: 'z' is a bad directive in format...

(примечание: я должен придерживаться Python 2.7 в моей случае)

У меня был схожий про blem parsing commit датируется с вывода 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 вы можете использовать свои собственные регулярные выражения.

+1

это не Ответ на вопрос. Вы используете * разный * формат времени. Примечание. Формат времени в вопросе определен в rfc 5322 (и его предвестниках) - он может быть проанализирован с использованием 'email.utils.parsedate_tz' на Python 2.7. Ваш формат выглядит как rfc 3339. Оба могут быть проанализированы с использованием 'dateutil.parser.parse()' на Python 2. См. [Преобразование временных меток со смещением в datetime obj с использованием strptime] (http://stackoverflow.com/q/12281975/ 4279) – jfs

+0

@JFSebastian, если бы вы не удалили мой ответ по одному из дублирующих вопросов, я бы не отправил свой ответ здесь. Моя проблема заключалась в том, что 'strptime не обрабатывает% z format', я считаю, что это та же проблема. – dnozay

+0

Я * не могу * удалить кого-то другого самостоятельно. Не могли бы вы ссылаться на соответствующий вопрос? – jfs

1
# 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) 
0

Для питона 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))) 
Смежные вопросы