python datetime timezone format rfc5322

python - Fecha de análisis con la zona horaria de un correo electrónico?



datetime timezone (8)

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

(nota: tengo que apegarme a Python 2.7 en mi caso)

He tenido un problema similar al analizar las fechas de confirmación de la salida de git log --date=iso8601 que en realidad no es el formato ISO8601 (de ahí la adición de --date=iso8601-strict en una versión posterior).

Como uso django , puedo aprovechar las utilidades allí.

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)

En lugar de strptime puedes usar tu propia expresión regular.

Estoy tratando de recuperar la fecha de un correo electrónico. Al principio es fácil:

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

y yo recibo:

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

Pero necesito un buen objeto datetime, entonces uso:

datetime.strptime(''Mon, 16 Nov 2009 13:32:02 +0100'', ''%a, %d %b %Y %H:%M:%S %Z'')

que provoca ValueError, since %Z isn''t format for +0100 . Pero no puedo encontrar el formato adecuado para la zona horaria en la documentación, solo existe este %Z para la zona. ¿Alguien puede ayudarme con eso?


En Python 3.3+, el mensaje de email puede analizar los encabezados por usted:

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

Desde Python 3.2+, funciona si reemplazas %Z con %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)))

O usando el paquete de 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)))

si el desplazamiento UTC se especifica como -0000 entonces devuelve un objeto naive datetime que representa el tiempo en UTC; de lo contrario, devuelve un objeto datetime con el correspondiente conjunto tzinfo .

Para analizar cadena de fecha y hora rfc 5322 en versiones anteriores de 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

donde FixedOffset se basa en la subclase tzinfo de la documentación de datetime y 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())



Para aquellos que quieren obtener la hora local correcta, esto es lo que hice:

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)


Para python 3 puede usar la función 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)))


Use 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


email.utils tiene una función parsedate() para el formato RFC 2822, que hasta donde yo sé, no está en desuso.

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

Sin embargo, tenga en cuenta que el método de parsedate no tiene en cuenta la zona horaria y time.mktime siempre espera una tupla de tiempo local como se menciona 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

Por lo tanto, aún deberá analizar la zona horaria y tener en cuenta la diferencia horaria local:

>>> 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


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