Como analiso uma data no formato ISO 8601?

643

Eu preciso analisar as seqüências de caracteres RFC 3339 como "2008-09-03T20:56:35.450686Z"no datetimetipo de Python .

Eu encontrei strptimena biblioteca padrão do Python, mas não é muito conveniente.

Qual é a melhor maneira de fazer isso?

Alexander Artemenko
fonte
3
Para ser claro: a ISO 8601 é o padrão principal. O RFC 3339 é um "perfil" auto-proclamado da ISO 8601 que faz algumas substituições imprudentes das regras da ISO 8601.
Basil Bourque
3
Não perca a python3.7 + solução abaixo para inverter isoformat ()
Brad M
2
Esta questão não deve ser encerrada como uma brincadeira com a postagem vinculada. Como este está pedindo para analisar uma sequência de tempo ISO 8601 (que não era suportada nativamente pelo python antes da versão 3.7), a outra é formatar um objeto datetime em uma sequência de época usando um método obsoleto.
abccd

Respostas:

462

O pacote python-dateutil pode analisar não apenas as seqüências de data e hora RFC 3339 como a da pergunta, mas também outras seqüências de data e hora ISO 8601 que não estão em conformidade com a RFC 3339 (como aquelas sem deslocamento UTC ou que representam apenas uma data).

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

Observe que dateutil.parser.isoparseé presumivelmente mais rigoroso do que o mais hacky dateutil.parser.parse, mas ambos são bastante tolerantes e tentarão interpretar a string que você passa. Se você deseja eliminar a possibilidade de erros de leitura, precisará usar algo mais rigoroso do que qualquer um deles. funções.

O nome Pypi é python-dateutil, não dateutil(obrigado code3monk3y ):

pip install python-dateutil

Se você estiver usando Python 3.7, ter um olhar para esta resposta sobre datetime.datetime.fromisoformat.

Flimm
fonte
75
Para o preguiçoso, ele é instalado via python-dateutilnão dateutil, então: pip install python-dateutil.
Cod3monk3y
29
Esteja avisado de que o dateutil.parserintencionalmente é hacky: ele tenta adivinhar o formato e cria suposições inevitáveis ​​(personalizáveis ​​apenas à mão) em casos ambíguos. Portanto, APENAS use-o se você precisar analisar entradas de formato desconhecido e estiver bem para tolerar erros de leitura ocasionais.
precisa saber é o seguinte
2
Acordado. Um exemplo está passando uma "data" de 9999. Isso retornará o mesmo que datetime (9999, mês atual, dia atual). Não é uma data válida na minha opinião.
timbó
1
@ivan_pozdeev que pacote você recomendaria para a análise sem adivinhação?
bgusach
2
@ivan_pozdeev há uma atualização para o módulo que lê datas iso8601: dateutil.readthedocs.io/en/stable/…
theEpsilon
198

Novo no Python 3.7+


A datetimebiblioteca padrão introduziu uma função para inversão datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Retorne a datetimecorrespondente a a date_stringem um dos formatos emitidos por date.isoformat()e datetime.isoformat().

Especificamente, esta função suporta seqüências de caracteres no (s) formato (s):

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

onde *pode corresponder a qualquer caractere único.

Cuidado : Isso não suporta a análise de cadeias arbitrárias da ISO 8601 - destina-se apenas à operação inversa de datetime.isoformat().

Exemplo de uso:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
abccd
fonte
6
Isso é estranho. Porque a datetimepode conter ae tzinfo, assim, datetime.fromisoformat()gerar um fuso horário, mas não analisa o tzinfo? parece ser um bug ..
Hendy Irawan
20
Não perca essa nota na documentação, isso não aceita todas as seqüências ISO 8601 válidas, apenas as geradas por isoformat. Ele não aceita o exemplo da pergunta "2008-09-03T20:56:35.450686Z"por causa do rastreamento Z, mas aceita "2008-09-03T20:56:35.450686".
Flimm 23/08/19
26
Para suportar adequadamente o Zscript de entrada, pode ser modificado com date_string.replace("Z", "+00:00").
jox
7
Observe que, por segundos, ele lida apenas com exatamente 0, 3 ou 6 casas decimais. Se os dados de entrada tiverem 1, 2, 4, 5, 7 ou mais casas decimais, a análise falhará!
Felk 28/05/19
1
@JDOaktown Este exemplo usa a biblioteca de data e hora nativa do Python, não o analisador do dateutil. Na verdade, ele falhará se as casas decimais não forem 0, 3 ou 6 com essa abordagem.
precisa saber é
174

Observe no Python 2.6+ e Py3K, o caractere% f captura microssegundos.

>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")

Veja a edição aqui

sethbc
fonte
4
Nota - se estiver usando datações ingênuas - acho que você não recebe TZ - Z pode não corresponder a nada.
Danny Staple
24
Esta resposta (em sua forma atual editada) depende da codificação codificada de um deslocamento UTC específico (ou seja, "Z", que significa +00: 00) na cadeia de caracteres do formato. Essa é uma péssima idéia, pois falhará na análise de qualquer data e hora com um deslocamento UTC diferente e gerará uma exceção. Veja minha resposta que descreve como a análise do RFC 3339 strptimeé de fato impossível.
Mark Amery
1
no meu caso% f microssegundos capturados em vez de Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') de modo que este fez o truque
ashim888
Py3K significa Python 3000?!?
Robino 13/11/19
2
@Robino IIRC, "Python 3000" é um nome antigo para o que agora é conhecido como Python 3.
Jogue fora a conta de
161

Várias respostas aqui sugerem o uso datetime.datetime.strptimepara analisar as datas RFC 3339 ou ISO 8601 com fusos horários, como o exibido na pergunta:

2008-09-03T20:56:35.450686Z

Esta é uma má ideia.

Supondo que você queira oferecer suporte ao formato RFC 3339 completo, incluindo suporte a compensações UTC diferentes de zero, o código sugerido por essas respostas não funcionará. Na verdade, ele não pode funcionar, porque strptimeé impossível analisar a sintaxe da RFC 3339 . As seqüências de formato usadas pelo módulo de data e hora do Python são incapazes de descrever a sintaxe do RFC 3339.

O problema são compensações de UTC. O RFC 3339 Internet Data / Time Format requer que cada data-hora inclui um UTC offset, e que estas compensações tanto pode ser Z(abreviação de "tempo de Zulu") ou +HH:MMou -HH:MMformato, como +05:00ou -10:30.

Conseqüentemente, essas são todas as datas válidas da RFC 3339:

  • 2008-09-03T20:56:35.450686Z
  • 2008-09-03T20:56:35.450686+05:00
  • 2008-09-03T20:56:35.450686-10:30

Infelizmente, as seqüências de caracteres de formato usadas strptimee strftimenão possuem nenhuma diretiva que corresponda às compensações do UTC no formato RFC 3339. Uma lista completa das diretivas suportadas pode ser encontrada em https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , e a única diretiva de deslocamento UTC incluída na lista é %z:

% z

Deslocamento UTC no formato + HHMM ou -HHMM (sequência vazia se o objeto for ingênuo).

Exemplo: (vazio), +0000, -0400, +1030

Isso não corresponde ao formato de um deslocamento da RFC 3339 e, de fato, se tentarmos usar %za string de formato e analisar uma data da RFC 3339, ocorreremos falhas:

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

(Na verdade, o que foi mostrado acima é exatamente o que você verá no Python 3. No Python 2, falharemos por uma razão ainda mais simples, que é que strptimenão implementa a %zdiretiva no Python 2. )

As várias respostas aqui que recomendam strptimetoda a solução para isso, incluindo um literal Zem sua sequência de formatação, que corresponde à Zsequência de data / hora do exemplo do solicitante da pergunta (e a descarta, produzindo um datetimeobjeto sem um fuso horário):

>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

Como isso descarta as informações do fuso horário incluídas na cadeia de data e hora original, é questionável se devemos considerar mesmo esse resultado correto. Porém, o mais importante é que, como essa abordagem envolve a codificação embutida de um deslocamento UTC específico na cadeia de caracteres de formato , ela sufocará o momento em que tentar analisar qualquer data e hora da RFC 3339 com um deslocamento 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 você tenha certeza de que precisa apenas suportar dados RFC 3339 no horário Zulu, e não aqueles com outros desvios de fuso horário, não use strptime. Use uma das muitas outras abordagens descritas nas respostas aqui.

Mark Amery
fonte
79
É incompreensível que o strptime não tenha uma diretiva para informações de fuso horário no formato ISO e por que não pode ser analisado. Incrível.
Csaba Toth
2
@CsabaToth Concordo totalmente - se eu tiver algum tempo para matar, talvez eu tente adicioná-lo ao idioma. Ou você poderia fazê-lo, se estivesse tão inclinado - vejo que você tem alguma experiência em C, diferente de mim.
Mark Amery
1
@CsabaToth - Por que incrível? Funciona bem o suficiente para a maioria das pessoas, ou eles acharam uma solução fácil. Se você precisar do recurso, ele é de código aberto e você pode adicioná-lo. Ou pague alguém para fazer isso por você. Por que alguém deve oferecer seu próprio tempo livre para resolver seus problemas específicos? Deixe a fonte estar com você.
Peter M. - significa Monica
2
@PeterMasiar Incrível porque geralmente se descobre que as coisas em python foram implementadas de maneira cuidadosa e completa. Ficamos estragados com essa atenção aos detalhes e, por isso, quando nos deparamos com algo na linguagem "não-tônica", jogamos nossos brinquedos fora do carrinho, como estou prestes a fazer agora. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino
2
strptime()no Python 3.7 agora suporta tudo descrito como impossível nesta resposta (literal 'Z' e ':' no deslocamento do fuso horário). Infelizmente, há outro caso de esquina que torna o RFC 3339 fundamentalmente incompatível com a ISO 8601, a saber, o primeiro permite um deslocamento de fuso horário nulo negativo -00: 00 e o posterior não.
SergiyKolesnikov
75

Experimente o módulo iso8601 ; faz exatamente isso.

Existem várias outras opções mencionadas na página WorkingWithTime no wiki do python.org.

Nicholas Riley
fonte
Simples comoiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman
3
A pergunta não era "como analiso datas ISO 8601", mas "como analiso esse formato exato de data".
22912 Nicholas Riley
3
@tiktak O OP perguntou "Preciso analisar seqüências de caracteres como X" e minha resposta a isso, depois de tentar as duas bibliotecas, é usar outra, porque o iso8601 ainda tem questões importantes em aberto. Meu envolvimento ou a falta dele em um projeto desse tipo não tem nenhuma relação com a resposta.
Tobia
2
Esteja ciente de que a versão do pip do iso8601 não é atualizada desde 2007 e tem alguns erros sérios que são pendentes. Eu recomendo a aplicação de algum crítico dos patches-se ou encontrar um dos muitos garfos github que já o fizeram github.com/keithhackbarth/pyiso8601-strict
keithhackbarth
6
O iso8601 , também conhecido como pyiso8601 , foi atualizado recentemente em fevereiro de 2014. A versão mais recente suporta um conjunto muito mais amplo de seqüências de caracteres ISO 8601. Eu tenho usado com bons resultados em alguns dos meus projetos.
Dave Hein
34
import re, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1])))
Ted
fonte
73
Discordo, isso é praticamente ilegível e, até onde sei, não leva em consideração o Zulu (Z), que torna essa data e hora ingênuas, mesmo que os dados do fuso horário tenham sido fornecidos.
Umbrae
14
Acho bastante legível. De fato, é provavelmente a maneira mais fácil e com melhor desempenho de fazer a conversão sem instalar pacotes adicionais.
Tobia
2
Isso é equivalente a d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1]))) suponho.
Xuan
4
uma variação:datetime.datetime(*map(int, re.findall('\d+', s))
jfs 16/05
3
Isso resulta em um objeto datetime ingênuo sem fuso horário, certo? Então o bit UTC se perde na tradução?
W00t
32

Qual é o erro exato que você recebe? É como o seguinte?

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

Se sim, você pode dividir sua sequência de entrada em "." E adicionar os microssegundos à data e hora que você obteve.

Tente o seguinte:

>>> 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)
tzot
fonte
10
Você não pode simplesmente retirar o .Z porque significa fuso horário e pode ser diferente. Preciso converter a data no fuso horário UTC.
Alexander Artemenko
Um objeto de data e hora simples não tem conceito de fuso horário. Se todos os seus horários estão terminando em "Z", todas as vezes em que você obtém são UTC (hora do Zulu).
tzot 24/09/08
se o fuso horário for diferente de ""ou "Z", deverá ser um deslocamento em horas / minutos, que pode ser diretamente adicionado / subtraído do objeto datetime. você pode criar uma subclasse tzinfo para lidar com isso, mas isso provavelmente não é recomendado.
SingleNegationElimination
8
Além disso, "% f" é o especificador de microssegundos, portanto, uma string de strptime (ingênua ao fuso horário) se parece com: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor
1
Isso gerará uma exceção se a sequência de data / hora especificada tiver um deslocamento UTC diferente de "Z". Ele não suporta todo o formato RFC 3339 e é uma resposta inferior a outras pessoas que lidam com compensações UTC corretamente.
Mark Amery
25

A partir do Python 3.7, o strptime suporta delimitadores de dois pontos nos desvios do UTC ( origem ). Então você pode usar:

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

EDITAR:

Conforme apontado por Martijn, se você criou o objeto datetime usando isoformat (), pode simplesmente usar datetime.fromisoformat ()

Andreas Profous
fonte
4
Mas, em 3,7, você também tem datetime.fromisoformat()que manipula strings como sua entrada automaticamente datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters
2
Bom ponto. Eu concordo, eu recomendo usar datetime.fromisoformat()edatetime.isoformat()
Andreas Profous
19

Atualmente, o Arrow também pode ser usado como uma solução de terceiros:

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
Ilker Kesen
fonte
6
No entanto, o Arrow não suporta a ISO8601 corretamente: github.com/crsmithdev/arrow/issues/291
box
1
Basta usar python-dateutil - a seta requer python-dateutil.
Danizen
A Arrow agora suporta ISO8601. Os problemas mencionados estão encerrados.
Altus
18

Basta usar o 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())

Documentação

Blairg23
fonte
1
Esta não é exatamente a resposta @Flimms acima?
Leo
1
Onde você o vê analisando em segundos? Eu encontrei este artigo tentando obter um tempo de época, então imaginei que outra pessoa também estaria.
precisa saber é o seguinte
1
Isso não é UTC no meu sistema. Em vez disso, a saída em segundos é a hora da época unix, como se a data estivesse no meu fuso horário local.
Elliot
1
Esta resposta é incorreta e não deve ser aceita. Provavelmente toda a questão deve ser marcada como uma duplicata de stackoverflow.com/questions/11743019/…
tripleee
@tripleee Na verdade, acabei de verificar o código e ele parece retornar a resposta correta: 455051100(verificado no epochconverter.com ) ,,, a menos que esteja faltando alguma coisa?
Blairg23
13

Se você não quiser usar o dateutil, tente esta função:

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)

Teste:

from_utc("2007-03-04T21:08:12.123Z")

Resultado:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
encantador
fonte
5
Esta resposta depende da codificação embutida de um deslocamento UTC específico (ou seja, "Z", que significa +00: 00) na cadeia de formato passada para strptime. Essa é uma péssima idéia, pois falhará na análise de qualquer data e hora com um deslocamento UTC diferente e gerará uma exceção. Veja minha resposta que descreve como analisar o RFC 3339 com strptime é de fato impossível.
Mark Amery
1
É codificado, mas é suficiente para o caso em que você precisa analisar apenas o zulu.
Sasha
1
@alexander yes - que pode ser o caso se, por exemplo, você souber que sua string de data foi gerada com o toISOStringmétodo JavaScript . Mas não há menção à limitação das datas do horário do Zulu nesta resposta, nem a pergunta indica que é tudo o que é necessário, e o uso dateutilé geralmente igualmente conveniente e menos restrito no que ele pode analisar.
Mark Amery
11

Se você estiver trabalhando com o Django, ele fornece o módulo dateparse que aceita vários formatos semelhantes ao formato ISO, incluindo o fuso horário.

Se você não estiver usando o Django e não quiser usar uma das outras bibliotecas mencionadas aqui, provavelmente poderá adaptar o código-fonte do Django para dateparse no seu projeto.

Don Kirkby
fonte
O Django DateTimeFieldusa isso quando você define um valor de string.
djvg
11

Eu descobri que o ciso8601 é a maneira mais rápida de analisar os carimbos de data / hora ISO 8601. Como o nome sugere, ele é implementado em C.

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

O README do GitHub Repo mostra sua aceleração> 10x em relação a todas as outras bibliotecas listadas nas outras respostas.

Meu projeto pessoal envolveu muita análise da ISO 8601. Foi bom poder mudar a chamada e ir 10 vezes mais rápido. :)

Edit: Desde então, me tornei um mantenedor do ciso8601. Agora está mais rápido do que nunca!

movermeyer
fonte
Parece uma ótima biblioteca! Para aqueles que desejam otimizar a análise ISO8601 no Google App Engine, infelizmente, não podemos usá-lo, pois é uma biblioteca C, mas seus benchmarks foram úteis para mostrar que o nativo datetime.strptime()é a próxima solução mais rápida. Obrigado por reunir todas essas informações!
21419 hamx0r #
3
@ hamx0r, esteja ciente de que datetime.strptime()não é uma biblioteca de análise ISO 8601 completa. Se você estiver no Python 3.7, poderá usar o datetime.fromisoformat()método, que é um pouco mais flexível. Você pode estar interessado nesta lista mais completa de analisadores que deve ser mesclada no README do ciso8601 em breve.
Movmeyer
O ciso8601 funciona bem, mas é preciso primeiro "instalar pip pytz", porque não é possível analisar um carimbo de data / hora com informações de fuso horário sem a dependência do pytz. O exemplo seria: dob = ciso8601.parse_datetime (resultado ['dob'] ['date'])
Dirk
2
@Dirk, apenas em Python 2 . Mas mesmo isso deve ser removido na próxima versão.
movmeyer
8

Isso funciona para o stdlib no Python 3.2 em diante (assumindo que todos os timestamps são UTC):

from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
    tzinfo=timezone(timedelta(0)))

Por exemplo,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
Benjamin Riggs
fonte
2
Esta resposta depende da codificação embutida de um deslocamento UTC específico (ou seja, "Z", que significa +00: 00) na cadeia de formato passada para strptime. Essa é uma péssima idéia, pois falhará na análise de qualquer data e hora com um deslocamento UTC diferente e gerará uma exceção. Veja minha resposta que descreve como analisar o RFC 3339 com strptime é de fato impossível.
Mark Amery
1
Em teoria, sim, isso falha. Na prática, nunca encontrei uma data no formato ISO 8601 que não estava no tempo do Zulu. Para minha necessidade ocasional, isso funciona muito bem e não depende de alguma biblioteca externa.
Benjamin Riggs
4
você poderia usar em timezone.utcvez de timezone(timedelta(0)). Além disso, o código funciona em Python 2.6+ (pelo menos) se você fornecer utctzinfo objeto
JFS
Não importa se você o encontrou, ele não corresponde à especificação.
Theannouncer 25/02/19
Você pode usar o %Zfuso horário for nas versões mais recentes do Python.
26419 sventechie
7

Eu sou o autor de iso8601 utils. Pode ser encontrado no GitHub ou no PyPI . Veja como você pode analisar seu exemplo:

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

Uma maneira simples de converter uma sequência de datas do tipo ISO 8601 em um carimbo de data / hora UNIX ou datetime.datetimeobjeto em todas as versões suportadas do Python sem instalar módulos de terceiros é usar o analisador de datas do 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)

Resultado:

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
Damian Yerrick
fonte
11
Obrigado. Isso é nojento. Eu amo isso.
precisa saber é
1
Que hack incrível, incrível e lindo! Obrigado!
Havok
6

Codifiquei um analisador para o padrão ISO 8601 e o coloquei no GitHub: https://github.com/boxed/iso8601 . Essa implementação suporta tudo na especificação, exceto por durações, intervalos, intervalos periódicos e datas fora do período suportado do módulo de data e hora do Python.

Os testes estão incluídos! : P

encaixotado
fonte
2
Geralmente, os links para uma ferramenta ou biblioteca devem ser acompanhados de notas de uso, uma explicação específica de como o recurso vinculado é aplicável ao problema, ou algum código de amostra ou, se possível, todos os itens acima.
Samuel Liew
6

A função parse_datetime () do Django suporta datas com deslocamentos UTC:

parse_datetime('2016-08-09T15:12:03.65478Z') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)

Portanto, poderia ser usado para analisar datas ISO 8601 em campos de todo o projeto:

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')
Artem Vasilev
fonte
4

Como a ISO 8601 permite que muitas variações de dois pontos e hífens opcionais estejam presentes, basicamente CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Se você deseja usar o strptime, é necessário eliminar essas variações primeiro.

O objetivo é gerar um objeto utc datetime.


Se você quer apenas um caso básico que funcione para o UTC com o sufixo Z, como 2016-06-29T19:36:29.3453Z:

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


Se você deseja lidar com deslocamentos de fuso horário como 2016-06-29T19:36:29.3453-0400ou 2008-09-03T20:56:35.450686+05:00use o seguinte. Isso converterá todas as variações em algo sem delimitadores de variáveis, como 20080903T205635.450686+0500torná-lo mais consistente / mais fácil de analisar.

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


Se o seu sistema não suportar a %zdiretiva strptime (você vê algo assim ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'), será necessário compensar manualmente a hora Z(UTC). A nota %zpode não funcionar em seu sistema nas versões python <3, pois depende do suporte da biblioteca c, que varia de acordo com o tipo de compilação do sistema / python (por exemplo, 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
theannouncer
fonte
2

Para algo que funcione com a biblioteca padrão 2.X, tente:

calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))

calendar.timegm é a versão gm ausente do time.mktime.

Gordon Wrigley
fonte
1
Isso simplesmente ignora o fuso horário '2013-01-28T14: 01: 01.335612-08: 00' -> analisado como UTC, não PDT
gatoatigrado
2

O python-dateutil lançará uma exceção ao analisar cadeias de datas inválidas, portanto, você pode capturar a exceção.

from dateutil import parser
ds = '2012-60-31'
try:
  dt = parser.parse(ds)
except ValueError, e:
  print '"%s" is an invalid date' % ds
user2646026
fonte
2

Atualmente, existe o Maya: Datetimes for Humans ™ , do autor do popular pacote Requests: 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>)
jrc
fonte
2

Uma outra maneira é usar o analisador especializado para ISO-8601 é usar a função isoparse do analisador dateutil:

from dateutil import parser

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

Resultado:

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

Essa função também é mencionada na documentação da função Python padrão datetime.fromisoformat :

Um analisador ISO 8601 com mais recursos, dateutil.parser.isoparse está disponível no pacote de terceiros dateutil.

zawuza
fonte
1

Graças à excelente resposta de Mark Amery, criei a função para explicar todos os possíveis formatos ISO de data e 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))
omikron
fonte
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)

Note que devemos procurar se a string não terminar Z, poderíamos analisar usando %z.

Denny Weinberg
fonte
0

Inicialmente, tentei com:

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)

Mas isso não funcionou em fusos horários negativos. No entanto, isso funcionou bem, no 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)

Alguns testes, observe que a saída difere apenas pela precisão de microssegundos. Tenho 6 dígitos de precisão na minha máquina, mas 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)
AT
fonte
Posso perguntar por que você fez frozenset(('+', '-'))? Uma tupla normal não deveria ('+', '-')ser capaz de realizar a mesma coisa?
Prahlad Yeri
Claro, mas não é uma varredura linear em vez de uma pesquisa com hash perfeitamente?
AT