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())
Has probado
rfc822.parsedate_tz(date) # ?
Más sobre RFC822, http://docs.python.org/library/rfc822.html
Sin embargo, está en desuso (parsedate_tz ahora está en email.utils.parsedate_tz
).
Pero tal vez estas respuestas ayuden:
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)