python - create - ¿Cómo puedo analizar una fecha con formato ISO 8601?
python strftime format (24)
Nuevo en Python 3.7+
La biblioteca estándar de datetime
introdujo una función para invertir datetime.isoformat()
.
classmethod
datetime.fromisoformat(date_string)
:Devuelva un
datetime
correspondiente a unadate_string
en uno de los formatos emitidos pordate.isoformat()
ydatetime.isoformat()
.Específicamente, esta función admite cadenas en el (los) formato (s):
YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]
donde
*
puede coincidir con cualquier carácter individual.Precaución : Esto no es compatible con el análisis de cadenas arbitrarias de ISO 8601, solo se pretende que sea la operación inversa de
datetime.isoformat()
.
Ejemplo de uso:
from datetime import datetime
date = datetime.fromisoformat(''2017-01-01T12:30:59.000000'')
Esta pregunta ya tiene una respuesta aquí:
- Convierte Python datetime a epoch con strftime 6 respuestas
Necesito analizar las cadenas RFC 3339 como "2008-09-03T20:56:35.450686Z"
en el tipo de datetime
y datetime
de Python.
He encontrado strptime
en la biblioteca estándar de Python, pero no es muy conveniente.
¿Cuál es la mejor manera de hacer esto?
¿Cuál es el error exacto que recibe? ¿Es como el siguiente?
>>> datetime.datetime.strptime("2008-08-12T12:20:30.656234Z", "%Y-%m-%dT%H:%M:%S.Z")
ValueError: time data did not match format: data=2008-08-12T12:20:30.656234Z fmt=%Y-%m-%dT%H:%M:%S.Z
Si es así, puede dividir la cadena de entrada en "." Y luego agregar los microsegundos a la fecha y hora que obtuvo.
Prueba esto:
>>> def gt(dt_str):
dt, _, us= dt_str.partition(".")
dt= datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S")
us= int(us.rstrip("Z"), 10)
return dt + datetime.timedelta(microseconds=us)
>>> gt("2008-08-12T12:20:30.656234Z")
datetime.datetime(2008, 8, 12, 12, 20, 30, 656234)
A partir de Python 3.7, strptime admite delimitadores de dos puntos en las compensaciones UTC ( source ). Entonces puedes usar:
import datetime
datetime.datetime.strptime(''2018-01-31T09:24:31.488670+00:00'', ''%Y-%m-%dT%H:%M:%S.%f%z'')
Debido a que ISO 8601 permite que existan muchas variaciones de dos puntos y guiones opcionales, básicamente CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
. Si desea utilizar strptime, primero debe eliminar esas variaciones.
El objetivo es generar un objeto utc datetime.
2016-06-29T19:36:29.3453Z
: datetime.datetime.strptime(timestamp.translate(None, '':-''), "%Y%m%dT%H%M%S.%fZ")
Si desea manejar las compensaciones de zona horaria como 2016-06-29T19:36:29.3453-0400
o 2008-09-03T20:56:35.450686+05:00
use lo siguiente. Estos convertirán todas las variaciones en algo sin delimitadores variables como 20080903T205635.450686+0500
lo que hace que sea más consistente / más fácil de analizar.
import re
# this regex removes all colons and all
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((/d{2}[:]/d{2})|(/d{4}))$))", '''', timestamp)
datetime.datetime.strptime(conformed_timestamp, "%Y%m%dT%H%M%S.%f%z" )
Si su sistema no admite la directiva %z
strptime (ve algo como ValueError: ''z'' is a bad directive in format ''%Y%m%dT%H%M%S.%f%z''
), entonces necesita para desplazar manualmente el tiempo desde Z
(UTC). Es posible que la nota %z
no funcione en su sistema en las versiones de python <3, ya que depende del soporte de la biblioteca c que varía según el tipo de compilación del sistema / python (es decir, Jython, Cython, etc.).
import re
import datetime
# this regex removes all colons and all
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((/d{2}[:]/d{2})|(/d{4}))$))", '''', timestamp)
# split on the offset to remove it. use a capture group to keep the delimiter
split_timestamp = re.split(r"[+|-]",conformed_timestamp)
main_timestamp = split_timestamp[0]
if len(split_timestamp) == 3:
sign = split_timestamp[1]
offset = split_timestamp[2]
else:
sign = None
offset = None
# generate the datetime object without the offset at UTC time
output_datetime = datetime.datetime.strptime(main_timestamp +"Z", "%Y%m%dT%H%M%S.%fZ" )
if offset:
# create timedelta based on offset
offset_delta = datetime.timedelta(hours=int(sign+offset[:-2]), minutes=int(sign+offset[-2:]))
# offset datetime with timedelta
output_datetime = output_datetime + offset_delta
El paquete python-dateutil puede analizar no solo las cadenas de fecha y hora RFC 3339 como la de la pregunta, sino también otras cadenas de fecha y hora ISO 8601 que no cumplen con RFC 3339 (como las que no tienen una compensación UTC o las que representan sólo una fecha).
>>> import dateutil.parser
>>> dateutil.parser.parse(''2008-09-03T20:56:35.450686Z'') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.parse(''2008-09-03T20:56:35.450686'') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.parse(''20080903T205635.450686'') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.parse(''20080903'') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)
Tenga en cuenta que el dateutil.parser
es intencionalmente hacky: intenta adivinar el formato y hace suposiciones inevitables (solo personalizables a mano) en casos ambiguos. Solo Úselo si necesita analizar entradas de formato desconocido y está bien tolerar errores de lectura ocasionales. (gracias ivan_pozdeev )
El nombre de Pypi es python-dateutil
, no dateutil
(gracias code3monk3y ):
pip install python-dateutil
Si está utilizando Python 3.7, consulte esta respuesta sobre datetime.datetime.fromisoformat
.
En estos días, Arrow también se puede utilizar como una solución de terceros:
>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
Es mucho más simple de lo que todos ustedes lo están haciendo.
Si desea obtener los segundos desde la época, puede usar python-dateutil para convertirlo en un objeto de fecha y hora y luego convertirlo en segundos usando el método strftime. Al igual que:
>>> import dateutil.parser as dp
>>> t = ''1984-06-02T19:05:00.000Z''
>>> parsed_t = dp.parse(t)
>>> t_in_seconds = parsed_t.strftime(''%s'')
>>> t_in_seconds
''455047500''
Source
Nota: Esto convertirá la datetime
y datetime
dada en tiempo de época. Pero puede usar la función strftime()
para convertir esa datetime
y datetime
en cualquier formato. El objeto parsed_t
aquí es de tipo datetime
en este punto.
Esto funciona para stdlib en Python 3.2 en adelante (asumiendo que todas las marcas de tiempo son UTC):
from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
tzinfo=timezone(timedelta(0)))
Por ejemplo,
>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
Gracias a la excelente respuesta de Mark Amery, diseñé una función para dar cuenta de todos los formatos ISO posibles de datetime:
class FixedOffset(tzinfo):
"""Fixed offset in minutes: `time = utc_time + utc_offset`."""
def __init__(self, offset):
self.__offset = timedelta(minutes=offset)
hours, minutes = divmod(offset, 60)
#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>%+d'' % (hours, minutes, -hours)
def utcoffset(self, dt=None):
return self.__offset
def tzname(self, dt=None):
return self.__name
def dst(self, dt=None):
return timedelta(0)
def __repr__(self):
return ''FixedOffset(%d)'' % (self.utcoffset().total_seconds() / 60)
def __getinitargs__(self):
return (self.__offset.total_seconds()/60,)
def parse_isoformat_datetime(isodatetime):
try:
return datetime.strptime(isodatetime, ''%Y-%m-%dT%H:%M:%S.%f'')
except ValueError:
pass
try:
return datetime.strptime(isodatetime, ''%Y-%m-%dT%H:%M:%S'')
except ValueError:
pass
pat = r''(.*?[+-]/d{2}):(/d{2})''
temp = re.sub(pat, r''/1/2'', isodatetime)
naive_date_str = temp[:-5]
offset_str = temp[-5:]
naive_dt = datetime.strptime(naive_date_str, ''%Y-%m-%dT%H:%M:%S.%f'')
offset = int(offset_str[-4:-2])*60 + int(offset_str[-2:])
if offset_str[0] == "-":
offset = -offset
return naive_dt.replace(tzinfo=FixedOffset(offset))
He codificado un analizador para el estándar ISO 8601 y lo puse en GitHub: https://github.com/boxed/iso8601 . Esta implementación admite todo en la especificación, excepto las duraciones, los intervalos, los intervalos periódicos y las fechas fuera del rango de fechas admitidas del módulo datetime de Python.
Las pruebas están incluidas! :PAG
He encontrado que ciso8601 es la forma más rápida de analizar las marcas de tiempo ISO 8601. Como su nombre lo indica, se implementa en C.
import ciso8601
ciso8601.parse_datetime(''2014-01-09T21:48:00.921000+05:30'')
GitHub Repo README muestra su velocidad de aceleración> 10x en comparación con todas las otras bibliotecas enumeradas en las otras respuestas.
Mi proyecto personal involucró mucho el análisis de ISO 8601. Fue agradable poder simplemente cambiar la llamada e ir 10 veces más rápido. :)
Edit: desde entonces me he convertido en un mantenedor de ciso8601. ¡Ahora es más rápido que nunca!
Hoy en día, está Maya: Datetimes for Humans ™ , del autor del popular paquete Solicitudes: HTTP para Humans ™:
>>> import maya
>>> str = ''2008-09-03T20:56:35.450686Z''
>>> maya.MayaDT.from_rfc3339(str).datetime()
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=<UTC>)
La función parse_datetime () de Django admite fechas con compensaciones UTC:
parse_datetime(''2016-08-09T15:12:03.65478Z'') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)
Por lo tanto, se podría utilizar para analizar fechas ISO 8601 en campos dentro del proyecto completo:
from django.utils import formats
from django.forms.fields import DateTimeField
from django.utils.dateparse import parse_datetime
class DateTimeFieldFixed(DateTimeField):
def strptime(self, value, format):
if format == ''iso-8601'':
return parse_datetime(value)
return super().strptime(value, format)
DateTimeField.strptime = DateTimeFieldFixed.strptime
formats.ISO_INPUT_FORMATS[''DATETIME_INPUT_FORMATS''].insert(0, ''iso-8601'')
Para algo que funciona con la biblioteca estándar 2.X intente:
calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))
calendar.timegm es la versión de gm que falta en time.mktime.
Prueba el módulo iso8601 ; hace exactamente esto.
Hay varias otras opciones mencionadas en la página de WorkingWithTime en la wiki de python.org.
Python-dateutil emitirá una excepción si analiza cadenas de fecha no válidas, por lo que es posible que desee capturar la excepción.
from dateutil import parser
ds = ''2012-60-31''
try:
dt = parser.parse(ds)
except ValueError, e:
print ''"%s" is an invalid date'' % ds
Si está trabajando con Django, proporciona el módulo dateparse que acepta un montón de formatos similares al formato ISO, incluida la zona horaria.
Si no está usando Django y no quiere usar una de las otras bibliotecas mencionadas aquí, probablemente podría adaptar el código fuente de Django para dateparse a su proyecto.
Si no quieres usar dateutil, puedes probar esta función:
def from_utc(utcTime,fmt="%Y-%m-%dT%H:%M:%S.%fZ"):
"""
Convert UTC time string to time.struct_time
"""
# change datetime.datetime to time, return time.struct_time type
return datetime.datetime.strptime(utcTime, fmt)
Prueba:
from_utc("2007-03-04T21:08:12.123Z")
Resultado:
datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
Tenga en cuenta que en Python 2.6+ y Py3K, el carácter% f captura microsegundos.
>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
Ver problema here
Una forma sencilla de convertir una cadena de fecha similar a ISO 8601 en un objeto de marca de tiempo UNIX o datetime.datetime
en todas las versiones de Python compatibles sin instalar módulos de terceros es usar el analizador de fechas de SQLite .
#!/usr/bin/env python
from __future__ import with_statement, division, print_function
import sqlite3
import datetime
testtimes = [
"2016-08-25T16:01:26.123456Z",
"2016-08-25T16:01:29",
]
db = sqlite3.connect(":memory:")
c = db.cursor()
for timestring in testtimes:
c.execute("SELECT strftime(''%s'', ?)", (timestring,))
converted = c.fetchone()[0]
print("%s is %s after epoch" % (timestring, converted))
dt = datetime.datetime.fromtimestamp(int(converted))
print("datetime is %s" % dt)
Salida:
2016-08-25T16:01:26.123456Z is 1472140886 after epoch
datetime is 2016-08-25 12:01:26
2016-08-25T16:01:29 is 1472140889 after epoch
datetime is 2016-08-25 12:01:29
Several answers here suggest uso de strptime para analizar los tiempos de las fechas RFC 3339 o ISO 8601 con zonas horarias, como la que se muestra en la pregunta:
2008-09-03T20:56:35.450686Z
Esta es una mala idea.
Suponiendo que desea admitir el formato completo RFC 3339, incluido el soporte para compensaciones UTC distintas de cero, entonces el código que sugieren estas respuestas no funciona. De hecho, no puede funcionar, porque es imposible analizar la sintaxis de RFC 3339 usando strptime
. Las cadenas de formato utilizadas por el módulo datetime de Python son incapaces de describir la sintaxis RFC 3339.
El problema son las compensaciones UTC. El formato de fecha / hora de Internet del RFC 3339 requiere que cada fecha y hora incluya un desplazamiento UTC, y que esas compensaciones puedan ser Z
(abreviatura de "Zulu time") o en formato +HH:MM
o -HH:MM
, como +05:00
o -10:30
.
En consecuencia, estos son todos los tiempos de referencia RFC 3339 válidos:
-
2008-09-03T20:56:35.450686Z
-
2008-09-03T20:56:35.450686+05:00
-
2008-09-03T20:56:35.450686-10:30
Por desgracia, las cadenas de formato utilizadas por strptime
y strftime
no tienen ninguna directiva que corresponda a las compensaciones UTC en formato RFC 3339. Puede encontrar una lista completa de las directivas que admiten en https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , y la única directiva de compensación UTC incluida en la lista es %z
% z
Desplazamiento UTC en la forma + HHMM o -HHMM (cadena vacía si el objeto es ingenuo).
Ejemplo: (vacío), +0000, -0400, +1030
Esto no coincide con el formato de un desplazamiento RFC 3339, y de hecho, si intentamos usar %z
en la cadena de formato y analizamos una fecha RFC 3339, fallaremos:
>>> from datetime import datetime
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data ''2008-09-03T20:56:35.450686Z'' does not match format ''%Y-%m-%dT%H:%M:%S.%f%z''
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data ''2008-09-03T20:56:35.450686+05:00'' does not match format ''%Y-%m-%dT%H:%M:%S.%f%z''
(En realidad, lo anterior es justo lo que verá en Python 3. En Python 2 fallaremos por una razón aún más simple, que es que strptime
no implementa la directiva %z
en Python 2 ).
Las respuestas múltiples aquí que recomiendan que strptime
todo strptime
esto al incluir una Z
literal en su cadena de formato, que coincide con la Z
de la cadena datetime de ejemplo de la pregunta (y la descarta, produciendo un objeto datetime
sin una zona horaria):
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
Dado que esto descarta la información de la zona horaria que se incluyó en la cadena de fecha y hora original, es cuestionable si debemos considerar incluso este resultado como correcto. Pero lo que es más importante, debido a que este enfoque involucra la codificación rígida de un determinado UTC en la cadena de formato , se ahogará en el momento en que intenta analizar cualquier fecha y hora RFC 3339 con un desplazamiento UTC diferente:
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%fZ")
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
tt, fraction = _strptime(data_string, format)
File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
(data_string, format))
ValueError: time data ''2008-09-03T20:56:35.450686+05:00'' does not match format ''%Y-%m-%dT%H:%M:%S.%fZ''
A menos que esté seguro de que solo necesita admitir tiempos de datos de RFC 3339 en tiempo Zulú, y no con otros desplazamientos de zona horaria, no use tiempo de strptime
. Utilice uno de los muchos otros enfoques descritos en las respuestas aquí en su lugar.
import re,datetime s="2008-09-03T20:56:35.450686Z" d=datetime.datetime(*map(int, re.split(''[^/d]'', s)[:-1]))
def parseISO8601DateTime(datetimeStr):
import time
from datetime import datetime, timedelta
def log_date_string(when):
gmt = time.gmtime(when)
if time.daylight and gmt[8]:
tz = time.altzone
else:
tz = time.timezone
if tz > 0:
neg = 1
else:
neg = 0
tz = -tz
h, rem = divmod(tz, 3600)
m, rem = divmod(rem, 60)
if neg:
offset = ''-%02d%02d'' % (h, m)
else:
offset = ''+%02d%02d'' % (h, m)
return time.strftime(''%d/%b/%Y:%H:%M:%S '', gmt) + offset
dt = datetime.strptime(datetimeStr, ''%Y-%m-%dT%H:%M:%S.%fZ'')
timestamp = dt.timestamp()
return dt + timedelta(hours=dt.hour-time.gmtime(timestamp).tm_hour)
Tenga en cuenta que deberíamos ver si la cadena no termina con Z
, podríamos analizar usando %z
.