¿Cómo analizo una fecha con formato ISO 8601?


643

Necesito analizar cadenas RFC 3339 como "2008-09-03T20:56:35.450686Z"en el datetimetipo de Python .

He encontrado strptimeen la biblioteca estándar de Python, pero no es muy conveniente.

¿Cuál es la mejor manera de hacer esto?




3
Para ser claros: ISO 8601 es el estándar principal. RFC 3339 es un autoproclamado "perfil" de ISO 8601 que hace algunas anulaciones imprudentes de las normas ISO 8601.
Basil Bourque

3
No se pierda la solución python3.7 + a continuación para invertir isoformat ()
Brad M

2
Esta pregunta no debe cerrarse como engañar a la publicación vinculada. Dado que este solicita analizar una cadena de tiempo ISO 8601 (que no era compatible de forma nativa con python pre a 3.7) y la otra es formatear un objeto de fecha y hora en una cadena de época utilizando un método obsoleto.
abccd

Respuestas:


462

El paquete python-dateutil puede analizar no solo 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 desplazamiento UTC o las que representan solo una cita).

>>> import dateutil.parser
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)

Tenga en cuenta que dateutil.parser.isoparsees presumiblemente más estricto que el más hacky dateutil.parser.parse, pero ambos son bastante indulgentes e intentarán interpretar la cadena que ingresa. Si desea eliminar la posibilidad de cualquier lectura errónea, debe usar algo más estricto que cualquiera de estos funciones

El nombre de Pypi es python-dateutil, no dateutil(gracias code3monk3y ):

pip install python-dateutil

Si está utilizando Python 3.7, echar un vistazo a esta respuesta acerca datetime.datetime.fromisoformat.


75
Para los perezosos, que se instala a través python-dateutilno dateutil, por lo que: pip install python-dateutil.
cod3monk3y

29
Tenga en cuenta que dateutil.parseres intencionalmente hacky: intenta adivinar el formato y hace suposiciones inevitables (personalizables solo a mano) en casos ambiguos. Así que ÚNICAMENTE úselo si necesita analizar entradas de formato desconocido y está bien tolerar errores de lectura ocasionales.
ivan_pozdeev

2
Convenido. Un ejemplo es pasar una "fecha" de 9999. Esto devolverá lo mismo que datetime (9999, mes actual, día actual). No es una fecha válida en mi opinión.
timbo

1
@ivan_pozdeev ¿qué paquete recomendaría para el análisis sin adivinanzas?
bgusach

2
@ivan_pozdeev hay una actualización del módulo que lee las fechas iso8601
theEpsilon

198

Nuevo en Python 3.7+


La datetimebiblioteca estándar introdujo una función para invertir datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Devuelve un datetimecorrespondiente a a date_stringen uno de los formatos emitidos por date.isoformat()y datetime.isoformat().

Específicamente, esta función admite cadenas en los formatos:

YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]

donde *puede coincidir con cualquier personaje individual.

Precaución : Esto no admite el análisis de cadenas arbitrarias ISO 8601; solo está pensado como la operación inversa de datetime.isoformat().

Ejemplo de uso:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')

66
Eso es raro. ¿Porque a datetimepuede contener a tzinfo, y por lo tanto generar una zona horaria, pero datetime.fromisoformat()no analiza el tzinfo? parece un error ...
Hendy Irawan

20
No se pierda esa nota en la documentación, esto no acepta todas las cadenas ISO 8601 válidas, solo las generadas por isoformat. No acepta el ejemplo en la pregunta "2008-09-03T20:56:35.450686Z"debido al seguimiento Z, pero sí acepta "2008-09-03T20:56:35.450686".
Flimm

26
Para soportar adecuadamente el Zscript de entrada se puede modificar con date_string.replace("Z", "+00:00").
jox

77
Tenga en cuenta que durante segundos solo maneja exactamente 0, 3 o 6 decimales. Si los datos de entrada tienen 1, 2, 4, 5, 7 o más decimales, el análisis fallará.
Felk

1
@JDOaktown Este ejemplo utiliza la biblioteca de fecha y hora nativa de Python, no el analizador de dateutil. En realidad, fallará si los lugares decimales no son 0, 3 o 6 con este enfoque.
abccd

174

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


44
Nota: si usa fechas de fecha ingenuas, creo que no tiene TZ en absoluto, Z puede no coincidir con nada.
Danny Staple

24
Esta respuesta (en su forma actual editada) se basa en la codificación rígida de un desplazamiento UTC particular (es decir, "Z", que significa +00: 00) en la cadena de formato. Esta es una mala idea porque no podrá analizar cualquier fecha y hora con un desplazamiento UTC diferente y generará una excepción. Vea mi respuesta que describe cómo analizar RFC 3339 con strptimees de hecho imposible.
Mark Amery

1
en mi caso,% f captó microsegundos en lugar de Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') así que esto funcionó
ashim888,

¿Py3K significa Python 3000?!?
Robino

2
@Robino IIRC, "Python 3000" es un nombre antiguo para lo que ahora se conoce como Python 3.
Deseche la cuenta el

161

Varias respuestas aquí sugieren usar datetime.datetime.strptimepara analizar las fechas y horas 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 RFC 3339 completo, incluida la compatibilidad con compensaciones UTC que no sean cero, el código que sugieren estas respuestas no funciona. De hecho, no puede funcionar, porque strptimees imposible analizar la sintaxis RFC 3339 . 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 RFC 3339 Internet Fecha / Hora Formato requiere que cada fecha-hora incluye un desplazamiento de UTC, y que esas compensaciones puede ser Z(abreviatura de "tiempo Zulu") o en +HH:MMo -HH:MMformato, como +05:00o -10:30.

En consecuencia, todas estas son fechas válidas RFC 3339:

  • 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 strptimey strftimeno tienen una 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 desplazamiento 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 tratamos de usar %zen la cadena de formato y analizar 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 strptimeno implementa la %zdirectiva en Python 2 ).

Las múltiples respuestas aquí que recomiendan strptimeevitarlo incluyen un literal Zen su cadena de formato, que coincide con la Zcadena de fecha y hora del ejemplo del autor de la pregunta (y la descarta, produciendo un datetimeobjeto sin 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 zona horaria que se incluyó en la cadena de fecha y hora original, es cuestionable si deberíamos considerar incluso este resultado como correcto. Pero lo que es más importante, debido a que este enfoque implica codificar de forma rígida un desplazamiento UTC particular en la cadena de formato , se ahogará en el momento en que intente 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 las fechas RFC 3339 en hora zulú, y no las que tienen otras compensaciones de zona horaria, no las use strptime. Utilice uno de los muchos otros enfoques descritos en las respuestas aquí en su lugar.


79
Es alucinante por qué strptime no tiene una directiva para la información de zona horaria en formato ISO, y por qué no se puede analizar. Increíble.
Csaba Toth

2
@CsabaToth Totalmente de acuerdo: si tengo algo de tiempo para matar, tal vez intente agregarlo al idioma. O podría hacerlo, si estuviera tan inclinado: veo que tiene algo de experiencia en C, a diferencia de mí.
Mark Amery

1
@CsabaToth - ¿Por qué increíble? Funciona lo suficientemente bien para la mayoría de las personas, o encontraron una solución lo suficientemente fácil. Si necesita la función, es de código abierto y puede agregarla. O paga a alguien para que lo haga por ti. ¿Por qué alguien debería ofrecer su propio tiempo libre para resolver sus problemas específicos? Deja que la fuente te acompañe.
Peter M. - representa a Mónica el

2
@PeterMasiar Increíble porque generalmente uno descubre que las cosas en Python se han implementado de manera cuidadosa y completa. Esta atención al detalle nos ha echado a perder y, por lo tanto, cuando nos topamos con algo en el lenguaje que no es "propónico", tiramos nuestros juguetes del cochecito, ya que estoy a punto de hacerlo ahora. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino

2
strptime()en Python 3.7 ahora admite todo lo que se describe como imposible en esta respuesta ('Z' literal y ':' en el desplazamiento de la zona horaria). Desafortunadamente, hay otro caso de esquina que hace que RFC 3339 sea fundamentalmente incompatible con ISO 8601, a saber, el primero permite un desplazamiento de zona horaria nulo negativo -00: 00 y el segundo no.
SergiyKolesnikov

75

Pruebe el módulo iso8601 ; hace exactamente esto

Hay varias otras opciones mencionadas en la página WorkingWithTime en el wiki de python.org.


Simple comoiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman

3
La pregunta no era "cómo analizo las fechas ISO 8601", sino "cómo analizo este formato de fecha exacto".
Nicholas Riley

3
@tiktak El OP preguntó "Necesito analizar cadenas como X" y mi respuesta a eso, después de haber probado ambas bibliotecas, es usar otra, porque iso8601 tiene problemas importantes aún abiertos. Mi participación o falta de ella en dicho proyecto no tiene relación alguna con la respuesta.
Tobia

2
Tenga en cuenta que la versión pip de iso8601 no se ha actualizado desde 2007 y tiene algunos errores graves que son sobresalientes. Recomiendo aplicar algunos críticos de los parches usted mismo o encontrar uno de los muchos tenedores github que ya lo han hecho github.com/keithhackbarth/pyiso8601-strict
keithhackbarth

66
iso8601 , también conocido como pyiso8601 , se actualizó en febrero de 2014. La última versión admite un conjunto mucho más amplio de cadenas ISO 8601. He estado usando con buenos resultados en algunos de mis proyectos.
Dave Hein

34
importar re, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))

73
No estoy de acuerdo, esto es prácticamente ilegible y, por lo que puedo decir, no tiene en cuenta el Zulu (Z) que hace que esta fecha y hora sea ingenua a pesar de que se proporcionaron datos de zona horaria.
umbrae

14
Me parece bastante legible. De hecho, es probablemente la forma más fácil y eficiente de realizar la conversión sin instalar paquetes adicionales.
Tobia

2
Esto es equivalente a d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])) supongo.
Xuan

44
una variación:datetime.datetime(*map(int, re.findall('\d+', s))
jfs

3
Esto da como resultado un objeto de fecha y hora ingenuo sin zona horaria, ¿verdad? ¿Entonces el bit UTC se pierde en la traducción?
w00t

32

¿Cuál es el error exacto que obtienes? ¿Es como lo 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

En caso afirmativo, puede dividir su 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)

10
No puede simplemente quitar .Z porque significa zona horaria y puede ser diferente. Necesito convertir la fecha a la zona horaria UTC.
Alexander Artemenko

Un objeto simple de fecha y hora no tiene concepto de zona horaria. Si todas sus horas terminan en "Z", todas las horas que obtiene son UTC (hora zulú).
tzot

si la zona horaria no es ""o "Z", entonces debe ser un desplazamiento en horas / minutos, que se puede agregar / restar directamente del objeto de fecha y hora. se podría crear una subclase tzinfo para manejarlo, pero que probablemente no es recomendable.
SingleNegationElimination

8
Además, "% f" es el especificador de microsegundos, por lo que una cadena de strptime (timezone-naive) se ve así: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor

1
Esto generará una excepción si la cadena de fecha y hora dada tiene un desplazamiento UTC distinto de "Z". No es compatible con todo el formato RFC 3339 y es una respuesta inferior a otras que manejan las compensaciones UTC correctamente.
Mark Amery

25

A partir de Python 3.7, strptime admite delimitadores de colon en desplazamientos UTC ( fuente ). Entonces puedes usar:

import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')

EDITAR:

Como señaló Martijn, si creó el objeto datetime usando isoformat (), simplemente puede usar datetime.fromisoformat ()


44
Pero en 3.7, que también tiene datetime.fromisoformat()que maneja cadenas como su entrada automáticamente: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters

2
Buen punto. Estoy de acuerdo, recomiendo usar datetime.fromisoformat()ydatetime.isoformat()
Andreas Profous

19

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

66
Sin embargo, Arrow no es compatible con ISO8601 correctamente: github.com/crsmithdev/arrow/issues/291
caja el

1
Simplemente use python-dateutil: la flecha requiere python-dateutil.
danizen

Arrow ahora es compatible con ISO8601. Los problemas a los que se hace referencia ahora están cerrados.
Altus

18

Solo usa el python-dateutilmódulo:

>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> print(parsed_t)
datetime.datetime(1984, 6, 2, 19, 5, tzinfo=tzutc())

Documentación


1
¿No es esto exactamente @Flimms respuesta anterior?
leo

1
¿Dónde lo ves analizando en segundos? Encontré este artículo tratando de obtener tiempo de época, así que pensé que alguien más también lo estaría.
Blairg23

1
Esto no es UTC en mi sistema. Más bien, la salida en segundos es el tiempo de época de Unix como si la fecha estuviera en mi zona horaria local.
Elliot

1
Esta respuesta tiene errores y no debe aceptarse. Probablemente toda la pregunta debería marcarse como un duplicado de stackoverflow.com/questions/11743019/…
tripleee

@tripleee En realidad, acabo de comprobar el código y parece que devuelve la respuesta correcta: 455051100(comprobado en epochconverter.com ), ¿a menos que me falte algo?
Blairg23

13

Si no desea utilizar dateutil, puede 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)

55
Esta respuesta se basa en la codificación rígida de un desplazamiento UTC particular (es decir, "Z", que significa +00: 00) en la cadena de formato que se pasa strptime. Esta es una mala idea porque no podrá analizar cualquier fecha y hora con un desplazamiento UTC diferente y generará una excepción. Vea mi respuesta que describe cómo analizar RFC 3339 con strptime es de hecho imposible.
Mark Amery

1
Está codificado pero es suficiente para el caso cuando solo necesita analizar zulu.
Sasha

1
@alexander yes, que puede ser el caso si, por ejemplo, sabe que su cadena de fecha se generó con el toISOStringmétodo de JavaScript . Pero en esta respuesta no se menciona la limitación de las fechas de tiempo en zulú, ni la pregunta indica que eso es todo lo que se necesita, y solo usarlo dateutilsuele ser igualmente conveniente y menos limitado en lo que puede analizar.
Mark Amery


11

He encontrado que ciso8601 es la forma más rápida de analizar las marcas de tiempo ISO 8601. Como su nombre indica, se implementa en C.

import ciso8601
ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')

El archivo README de GitHub Repo muestra su aceleración> 10x en comparación con todas las otras bibliotecas enumeradas en las otras respuestas.

Mi proyecto personal implicaba mucho análisis ISO 8601. Fue agradable poder cambiar la llamada e ir 10 veces más rápido. :)

Editar: desde entonces me he convertido en un mantenedor de ciso8601. ¡Ahora es más rápido que nunca!


¡Esto parece una gran biblioteca! Para aquellos que desean optimizar el análisis ISO8601 en Google App Engine, lamentablemente, no podemos usarlo ya que es una biblioteca C, pero sus puntos de referencia fueron perspicaces para mostrar que Native datetime.strptime()es la próxima solución más rápida. ¡Gracias por reunir toda esa información!
hamx0r

3
@ hamx0r, tenga en cuenta que datetime.strptime()no es una biblioteca de análisis ISO 8601 completa. Si estás en Python 3.7, puedes usar el datetime.fromisoformat()método, que es un poco más flexible. Es posible que le interese esta lista más completa de analizadores que deberían fusionarse pronto en el archivo README de ciso8601.
movermeyer

ciso8601 funciona bastante bien, pero primero hay que hacer "pip install pytz", porque no se puede analizar una marca de tiempo con información de zona horaria sin la dependencia de pytz. El ejemplo sería: dob = ciso8601.parse_datetime (result ['dob'] ['date'])
Dirk

2
@Dirk, solo en Python 2 . Pero incluso eso debería eliminarse en la próxima versión.
movermeyer

8

Esto funciona para stdlib en Python 3.2 en adelante (suponiendo que todas las marcas de tiempo sean 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)

2
Esta respuesta se basa en la codificación rígida de un desplazamiento UTC particular (es decir, "Z", que significa +00: 00) en la cadena de formato que se pasa strptime. Esta es una mala idea porque no podrá analizar cualquier fecha y hora con un desplazamiento UTC diferente y generará una excepción. Vea mi respuesta que describe cómo analizar RFC 3339 con strptime es de hecho imposible.
Mark Amery

1
En teoría, sí, esto falla. En la práctica, nunca me he encontrado con una fecha con formato ISO 8601 que no estuviera en tiempo de zulú. Para mi necesidad muy ocasional, esto funciona muy bien y no depende de alguna biblioteca externa.
Benjamin Riggs

44
podrías usar en timezone.utclugar de timezone(timedelta(0)). Además, el código funciona en Python 2.6+ (al menos) si proporciona el utcobjeto
tzinfo

No importa si lo has encontrado, no coincide con la especificación.
locutor

Puede usar la %Zzona horaria for en las versiones más recientes de Python.
sventechie

7

Soy el autor de las utilidades iso8601. Se puede encontrar en GitHub o en PyPI . Así es como puedes analizar tu ejemplo:

>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

6

Una forma sencilla de convertir una cadena de fecha similar a ISO 8601 en una marca de tiempo u datetime.datetimeobjeto UNIX en todas las versiones compatibles de Python sin instalar módulos de terceros es usar el analizador de fecha 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

11
Gracias. Esto es desagradable. Me encanta.
wchargin

1
¡Qué increíble, increíble, hermoso truco! ¡Gracias!
Havok

6

Codifiqué 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 duraciones, intervalos, intervalos periódicos y fechas fuera del rango de fechas admitido del módulo de fecha y hora de Python.

Las pruebas están incluidas! :PAGS



6

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, podría usarse para analizar las fechas ISO 8601 en campos de todo el proyecto:

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

4

Debido a que ISO 8601 permite muchas variaciones de puntos y guiones opcionales que están presentes, 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.


Si solo desea un caso básico que funcione para UTC con el sufijo Z como 2016-06-29T19:36:29.3453Z:

datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")


Si desea manejar compensaciones de zona horaria como 2016-06-29T19:36:29.3453-0400o 2008-09-03T20:56:35.450686+05:00usar lo siguiente. Esto convertirá todas las variaciones en algo sin delimitadores variables, como 20080903T205635.450686+0500hacerlo 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 es compatible con la %zdirectiva strptime (ve algo como ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z') entonces necesita compensar manualmente el tiempo desde Z(UTC). Es %zposible que Note 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

2

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 gm faltante de time.mktime.


1
Esto simplemente ignora la zona horaria '2013-01-28T14: 01: 01.335612-08: 00' -> analizada como UTC, no PDT
gatoatigrado

2

Python-dateutil arrojará una excepción si analiza cadenas de fechas no válidas, por lo que es posible que desee detectar 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

2

Hoy en día está Maya: Datetimes for Humans ™ , del autor del popular paquete Solicitudes: HTTP for 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>)

2

Una otra forma es utilizar analizador especializado para ISO-8601 es utilizar isoparse función de dateutil analizador:

from dateutil import parser

date = parser.isoparse("2008-09-03T20:56:35.450686+01:00")
print(date)

Salida:

2008-09-03 20:56:35.450686+01:00

Esta función también se menciona en la documentación de la función estándar de Python datetime.fromisoformat :

Un analizador ISO 8601 más completo, dateutil.parser.isoparse está disponible en el paquete de terceros dateutil.


1

Gracias a la gran respuesta de Mark Amery, ideé una función para tener en cuenta todos los posibles formatos ISO de fecha y hora:

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

0
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 debemos mirar si la cadena no termina con Z, podríamos analizar usando %z.


0

Inicialmente probé con:

from operator import neg, pos
from time import strptime, mktime
from datetime import datetime, tzinfo, timedelta

class MyUTCOffsetTimezone(tzinfo):
    @staticmethod
    def with_offset(offset_no_signal, signal):  # type: (str, str) -> MyUTCOffsetTimezone
        return MyUTCOffsetTimezone((pos if signal == '+' else neg)(
            (datetime.strptime(offset_no_signal, '%H:%M') - datetime(1900, 1, 1))
          .total_seconds()))

    def __init__(self, offset, name=None):
        self.offset = timedelta(seconds=offset)
        self.name = name or self.__class__.__name__

    def utcoffset(self, dt):
        return self.offset

    def tzname(self, dt):
        return self.name

    def dst(self, dt):
        return timedelta(0)


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        dt, sign, offset = strptime(dt[:-6], fmt), dt[-6], dt[-5:]
        return datetime.fromtimestamp(mktime(dt),
                                      tz=MyUTCOffsetTimezone.with_offset(offset, sign))
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

Pero eso no funcionó en zonas horarias negativas. Sin embargo, esto funcionó bien, en Python 3.7.3:

from datetime import datetime


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        return datetime.strptime(dt, fmt + '%z')
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

Algunas pruebas, tenga en cuenta que la salida solo difiere por la precisión de microsegundos. Tengo 6 dígitos de precisión en mi máquina, pero YMMV:

for dt_in, dt_out in (
        ('2019-03-11T08:00:00.000Z', '2019-03-11T08:00:00'),
        ('2019-03-11T08:00:00.000+11:00', '2019-03-11T08:00:00+11:00'),
        ('2019-03-11T08:00:00.000-11:00', '2019-03-11T08:00:00-11:00')
    ):
    isoformat = to_datetime_tz(dt_in).isoformat()
    assert isoformat == dt_out, '{} != {}'.format(isoformat, dt_out)

¿Puedo preguntar por qué lo hiciste frozenset(('+', '-'))? ¿No debería una tupla normal como ('+', '-')ser capaz de lograr lo mismo?
Prahlad Yeri

Claro, pero ¿no es eso un escaneo lineal en lugar de una búsqueda perfectamente hash?
AL
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.