Como faço para analisar uma data no formato ISO 8601?

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

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

Qual é a melhor maneira de fazer isso?

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

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

Esteja avisado que o dateutil.parser é intencionalmente hacky: ele tenta adivinhar o formato e faz suposições inevitáveis ​​(personalizáveis ​​apenas à mão) em casos ambíguos. Então, use-o APENAS se você precisar analisar inputs de formato desconhecido e não houver problema em tolerar erros de leitura ocasionais. (obrigado ivan_pozdeev )

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

 pip install python-dateutil 

Se você estiver usando o Python 3.7, dê uma olhada nesta resposta sobre datetime.datetime.fromisoformat .

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 questão aqui

Várias respostas aqui sugerem usar datetime.datetime.strptime para analisar os tempos de dados 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 suportar o formato RFC 3339 completo, incluindo suporte para deslocamentos UTC diferentes de zero, o código sugerido pelas respostas não funciona. Na verdade, não pode funcionar, porque é impossível analisar a syntax RFC 3339 usando strptime . As strings de formato usadas pelo módulo datetime do Python são incapazes de descrever a syntax RFC 3339.

O problema é compensações UTC. O formato de data / hora da Internet RFC 3339 exige que cada data e hora inclua um deslocamento UTC e que esses deslocamentos possam ser Z (abreviação de “Zulu time”) ou +HH:MM ou -HH:MM , como +05:00 ou -10:30 .

Conseqüentemente, esses são todos os dados válidos 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 strings de formato usadas por strptime e strptime não possuem uma diretiva que corresponda a deslocamentos UTC no formato RFC 3339. Uma lista completa das diretivas que eles suportam pode ser encontrada em https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , e a única diretiva de compensação UTC incluída na lista é %z :

% z

Deslocamento UTC no formato + HHMM ou -HHMM (cadeia vazia se o object for ingênuo).

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

Isso não corresponde ao formato de um offset RFC 3339 e, de fato, se tentarmos usar %z na string de formato e analisar uma data RFC 3339, falharemos:

 >>> 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 está acima é exatamente o que você verá no Python 3. No Python 2, falhamos por um motivo ainda mais simples, que é o fato de o strptime não implementar a diretiva %z no Python 2. )

As várias respostas aqui que recomendam strptime funcionam ao redor disso, incluindo um literal Z em sua string de formato, que corresponde ao Z da string de data e hora de exemplo do questionador (e descarta, produzindo um object datetime 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 informações de fuso horário que foram incluídas na cadeia de data e hora original, é questionável se devemos considerar esse resultado como correto. Mas o mais importante, porque essa abordagem envolve a codificação de um determinado deslocamento UTC na cadeia de caracteres de formato , ela irá sufocar no momento em que tentar analisar qualquer datetime 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 tenha certeza de que você só precisa dar suporte a datagramas RFC 3339 no horário do Zulu, e não àqueles com outros deslocamentos de fuso horário, não use o strptime . Use uma das muitas outras abordagens descritas nas respostas aqui.

Novo no Python 3.7+


A biblioteca padrão datetime introduziu uma function para inverter datetime.isoformat() .

classmethod datetime.fromisoformat(date_string) :

Retorna um datetime correspondente a um date_string em um dos formatos emitidos por date.isoformat() e datetime.isoformat() .

Especificamente, esta function suporta strings no 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 arbitrária de seqüências de caracteres ISO 8601 – destina-se apenas como a operação inversa de datetime.isoformat() .

Exemplo de uso:

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

Experimente o módulo iso8601 ; faz exatamente isso.

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

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

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

 >>> datetime.datetime.strptime("2008-08-12T12:20:30.656234Z", "%Y-%m-%dT%H:%M:%SZ") ValueError: time data did not match format: data=2008-08-12T12:20:30.656234Z fmt=%Y-%m-%dT%H:%M:%SZ 

Se sim, você pode dividir sua string de input em “.” E depois adicionar os microssegundos ao datetime que você obteve.

Tente isto:

 >>> 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 do Python 3.7, o strptime suporta delimitadores de dois pontos em deslocamentos 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') 

Nos dias de hoje, 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()) 

Se você não quiser usar dateutil, você pode tentar esta function:

 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) 

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

Se você não está usando o Django e não quer usar uma das outras bibliotecas mencionadas aqui, você provavelmente poderia adaptar o código-fonte do Django para dateparse em seu projeto.

Apenas use o módulo python-dateutil :

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

Eu encontrei ciso8601 para ser o caminho mais rápido para analisar os timestamps 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 versus todas as outras bibliotecas listadas nas outras respostas.

Meu projeto pessoal envolveu muita análise ISO 8601. Foi bom poder apenas mudar a chamada e ir 10x mais rápido. 🙂

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

Eu sou o autor de utilitários iso8601. 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) 

Uma maneira simples de converter uma string de data do tipo ISO 8601 em um timestamp do UNIX ou em um object datetime.datetime em todas as versões suportadas do Python sem instalar módulos de terceiros é usar o analisador de data 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) 

Saída:

 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 

Eu codifiquei um analisador para o padrão ISO 8601 e coloquei no GitHub: https://github.com/boxed/iso8601 . Essa implementação suporta tudo na especificação, exceto para durações, intervalos, intervalos periódicos e datas fora do intervalo de data com suporte do módulo datetime do Python.

Testes estão incluídos! : P

A function 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=) 

Assim, ele poderia ser usado para analisar datas ISO 8601 em campos dentro 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') 

Como o ISO 8601 permite muitas variações de cólons e traços opcionais presentes, basicamente CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm] . Se você quiser usar strptime, você precisa remover essas variações primeiro.

O objective é gerar um object 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ê quiser lidar com deslocamentos de fuso horário, como 2016-06-29T19:36:29.3453-0400 ou 2008-09-03T20:56:35.450686+05:00 use o seguinte. Eles converterão todas as variações em algo sem delimitadores variables, como 20080903T205635.450686+0500 tornando-o mais consistente / 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 suporta a diretiva %z strptime (você vê algo como ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z' ) então você precisa para compensar manualmente o tempo de Z (UTC). Nota O %z pode não funcionar no seu sistema em versões do python <3, pois dependia do suporte à 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 

Isso funciona para stdlib no Python 3.2 em diante (supondo 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) 

Para algo que funciona 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.

O python-dateutil lançará uma exceção se analisar sequências de datas inválidas, portanto, talvez você queira 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 

Hoje em dia há o Maya: Datetimes for Humans ™ , do autor do popular pacote Solicitações: 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=) 

Graças à grande resposta de Mark Amery, eu criei uma function para explicar todos os possíveis formatos ISO 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 eg, 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)) 
 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 olhar se a string não terminar com Z , poderíamos analisar usando %z .