Come posso analizzare una data in formato ISO 8601?


643

Ho bisogno di analizzare le stringhe RFC 3339 come "2008-09-03T20:56:35.450686Z"nel datetimetipo di Python .

Ho trovato strptimenella libreria standard di Python, ma non è molto conveniente.

Qual è il modo migliore per farlo?




3
Per essere chiari: ISO 8601 è lo standard principale. RFC 3339 è un "profilo" autoproclamato ISO 8601 che esegue alcune sostituzioni poco sagge delle regole ISO 8601.
Basil Bourque,

3
Non perdere la soluzione python3.7 + di seguito per invertire isoformat ()
Brad M

2
Questa domanda non deve essere chiusa come duplicata al post collegato. Poiché questo sta chiedendo di analizzare una stringa temporale ISO 8601 (che non era supportata nativamente da Python prima della 3.7) e l'altra è di formattare un oggetto datetime in una stringa di epoca usando un metodo obsoleto.
abccd,

Risposte:


462

Il pacchetto python-dateutil può analizzare non solo stringhe datetime RFC 3339 come quella nella domanda, ma anche altre stringhe di data e ora ISO 8601 che non sono conformi a RFC 3339 (come quelle senza offset UTC o che rappresentano solo una 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)

Si noti che dateutil.parser.isoparseè presumibilmente più rigoroso di quello più confuso dateutil.parser.parse, ma entrambi sono abbastanza indulgenti e tenteranno di interpretare la stringa in cui si passa. Se si desidera eliminare la possibilità di eventuali fraintendimenti, è necessario utilizzare qualcosa di più rigoroso di uno di questi funzioni.

Il nome Pypi è python-dateutil, non dateutil(grazie code3monk3y ):

pip install python-dateutil

Se stai usando Python 3.7, dare un'occhiata a questa risposta su datetime.datetime.fromisoformat.


75
Per i più pigri, è installato via python-dateutilnon dateutil, in modo da: pip install python-dateutil.
cod3monk3y

29
Attenzione che dateutil.parserè intenzionalmente confuso: cerca di indovinare il formato e fa ipotesi inevitabili (personalizzabili solo a mano) in casi ambigui. Quindi utilizzalo SOLO se devi analizzare input di formato sconosciuto e va bene tollerare errori di lettura occasionali.
ivan_pozdeev,

2
Concordato. Un esempio sta passando una "data" di 9999. Ciò restituirà lo stesso di datetime (9999, mese corrente, giorno corrente). A mio avviso, non è una data valida.
timbo,

1
@ivan_pozdeev quale pacchetto consiglieresti di analizzare senza indovinare?
bgusach,

2
@ivan_pozdeev c'è un aggiornamento al modulo che legge le date iso8601
theEpsilon

198

Novità in Python 3.7+


La datetimelibreria standard ha introdotto una funzione per l'inversione datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Restituisce un datetimecorrispondente a date_stringin uno dei formati emessi da date.isoformat()e datetime.isoformat().

In particolare, questa funzione supporta le stringhe nei formati:

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

dove *può abbinare qualsiasi singolo personaggio.

Attenzione : questo non supporta l'analisi di stringhe ISO 8601 arbitrarie - è inteso solo come operazione inversa di datetime.isoformat().

Esempio di utilizzo:

from datetime import datetime

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

6
Quello è strano. Perché a datetimepuò contenere a tzinfoe quindi generare un fuso orario, ma datetime.fromisoformat()non analizza tzinfo? sembra un insetto ...
Hendy Irawan,

20
Non perdere questa nota nella documentazione, questo non accetta tutte le stringhe ISO 8601 valide, solo quelle generate da isoformat. Non accetta l'esempio nella domanda a "2008-09-03T20:56:35.450686Z"causa del trascinamento Z, ma accetta "2008-09-03T20:56:35.450686".
Flimm,

26
Per supportare correttamente lo Zscript di input può essere modificato con date_string.replace("Z", "+00:00").
jox,

7
Si noti che per secondi gestisce solo esattamente 0, 3 o 6 decimali. Se i dati di input hanno 1, 2, 4, 5, 7 o più cifre decimali, l'analisi avrà esito negativo!
Felk,

1
@JDOaktown Questo esempio usa la libreria datetime di Python nativa, non il parser di dateutil. In realtà fallirà se le posizioni decimali non sono 0, 3 o 6 con questo approccio.
abccd,

174

Nota in Python 2.6+ e Py3K, il carattere% f cattura i microsecondi.

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

Vedi il problema qui


4
Nota: se si usano tempi di dati Naive, penso che non si ottenga alcun TZ, Z potrebbe non corrispondere a nulla.
Danny Staple,

24
Questa risposta (nella sua forma attuale e modificata) si basa sulla codifica definitiva di un particolare offset UTC (ovvero "Z", che significa +00: 00) nella stringa di formato. Questa è una cattiva idea perché non analizzerà alcun datetime con un diverso offset UTC e genererà un'eccezione. Vedi la mia risposta che descrive come analizzare RFC 3339 strptimesia di fatto impossibile.
Mark Amery,

1
nel mio caso% f ha catturato microsecondi anziché Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') quindi questo ha funzionato
ashim888,

Py3K significa Python 3000?!?
Robino,

2
@Robino IIRC, "Python 3000" è un vecchio nome per quello che ora è noto come Python 3.
Getta via l'account

161

Diverse risposte qui suggeriscono di utilizzare datetime.datetime.strptimeper analizzare i dati RFC 3339 o ISO 8601 con fusi orari, come quello mostrato nella domanda:

2008-09-03T20:56:35.450686Z

Questa è una cattiva idea.

Supponendo che si desideri supportare l'intero formato RFC 3339, incluso il supporto per offset UTC diversi da zero, il codice suggerito da queste risposte non funziona. In effetti, non può funzionare perché analizza la sintassi RFC 3339 usandostrptime è possibile. Le stringhe di formato utilizzate dal modulo datetime di Python non sono in grado di descrivere la sintassi RFC 3339.

Il problema sono gli offset UTC. Il formato data / ora Internet RFC 3339 richiede che ogni data e ora includa un offset UTC e che tali offset possano essere Z(abbreviazione di "ora Zulu") o in +HH:MMo -HH:MMformato, come +05:00o -10:30.

Di conseguenza, si tratta di tutti i periodi di dati RFC 3339 validi:

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

Purtroppo, le stringhe di formato utilizzate da strptimee strftimenon hanno una direttiva corrispondente agli offset UTC nel formato RFC 3339. Un elenco completo delle direttive che supportano è disponibile all'indirizzo https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior e l'unica direttiva offset UTC inclusa nell'elenco è %z:

% z

Offset UTC nel formato + HHMM o -HHMM (stringa vuota se l'oggetto è ingenuo).

Esempio: (vuoto), +0000, -0400, +1030

Questo non corrisponde al formato di un offset RFC 3339, e infatti se proviamo a utilizzare %znella stringa di formato e analizziamo una data RFC 3339, falliremo:

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

(In realtà, quanto sopra è proprio quello che vedrai in Python 3. In Python 2 falliremo per un motivo ancora più semplice, ovvero che strptimenon implementa affatto la %zdirettiva in Python 2 )

Le risposte multiple qui che raccomandano di strptimeaggirare tutto ciò includendo un letterale Znella loro stringa di formato, che corrisponde alla Zstringa datetime di esempio del richiedente (e la scarta, producendo un datetimeoggetto senza un fuso orario):

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

Poiché ciò elimina le informazioni sul fuso orario incluse nella stringa datetime originale, è lecito chiedersi se anche questo risultato debba essere considerato corretto. Ma, cosa ancora più importante, poiché questo approccio prevede la codifica hardware di un determinato offset UTC nella stringa di formato , si bloccherà nel momento in cui tenta di analizzare qualsiasi datetime RFC 3339 con un offset UTC diverso:

>>> 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 meno che tu non sia sicuro di dover solo supportare i tempi di trasmissione dei dati RFC 3339 in ora Zulu e non quelli con altri offset del fuso orario, non utilizzarli strptime. Usa invece uno dei molti altri approcci descritti nelle risposte qui.


79
È pazzesco perché strptime non abbia una direttiva per le informazioni sul fuso orario in formato ISO e perché non possa essere analizzato. Incredibile.
Csaba Toth,

2
@CsabaToth Completamente d'accordo - se avrò del tempo per uccidere, forse proverò ad aggiungerlo nella lingua. Oppure potresti farlo, se eri così propenso - Vedo che hai qualche esperienza in C, a differenza di me.
Mark Amery,

1
@CsabaToth - Perché incredibile? Funziona abbastanza bene per la maggior parte delle persone, oppure hanno trovato una soluzione abbastanza semplice. Se hai bisogno della funzione, è opensource e puoi aggiungerla. O paga qualcuno per farlo per te. Perché qualcuno dovrebbe offrire volontariamente il proprio tempo libero per risolvere i tuoi problemi specifici? Lascia che la fonte sia con te.
Peter M. - sta per Monica

2
@PeterMasiar Incredibile perché di solito si scopre che le cose in Python sono state implementate in modo meditato e completo. Siamo stati viziati da questa attenzione ai dettagli e quindi quando ci imbattiamo in qualcosa nel linguaggio "non sinconico" buttiamo i nostri giocattoli fuori dalla carrozzina, come sto per farlo proprio ora. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino

2
strptime()in Python 3.7 ora supporta tutto ciò che è descritto come impossibile in questa risposta ('Z' letterale e ':' nell'offset del fuso orario). Sfortunatamente, c'è un altro caso angolare che rende RFC 3339 fondamentalmente incompatibile con ISO 8601, vale a dire, il primo consente un offset del fuso orario nullo negativo -00: 00 e il successivo no.
SergiyKolesnikov,

75

Prova il modulo iso8601 ; fa esattamente questo.

Ci sono molte altre opzioni menzionate nella pagina WorkingWithTime sul wiki di python.org.


Semplice comeiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman,

3
La domanda non era "come analizzo le date ISO 8601", era "come analizzo questo esatto formato data".
Nicholas Riley,

3
@tiktak L'OP ha chiesto "Ho bisogno di analizzare stringhe come X" e la mia risposta a questa, avendo provato entrambe le librerie, è di usarne un'altra, perché iso8601 ha ancora problemi importanti ancora aperti. Il mio coinvolgimento o mancanza di ciò in un tale progetto non è completamente correlato alla risposta.
Tobia,

2
Tenere presente che la versione pip di iso8601 non è stata aggiornata dal 2007 e presenta alcuni bug gravi che sono eccezionali. Ti consiglio di applicare tu stesso alcune critiche alle patch o di trovare una delle tante forcelle github che hanno già fatto github.com/keithhackbarth/pyiso8601-strict
keithhackbarth

6
iso8601 , noto anche come pyiso8601 , è stato aggiornato di recente a febbraio 2014. L'ultima versione supporta un set molto più ampio di stringhe ISO 8601. Ho usato con buoni risultati in alcuni dei miei progetti.
Dave Hein,

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

73
Non sono d'accordo, questo è praticamente illeggibile e per quanto ne so non tiene conto dello Zulu (Z) che rende questo datetime ingenuo anche se sono stati forniti i dati del fuso orario.
Umbrae,

14
Lo trovo abbastanza leggibile. In effetti, è probabilmente il modo più semplice ed efficace per eseguire la conversione senza installare pacchetti aggiuntivi.
Tobia,

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

4
una variazione:datetime.datetime(*map(int, re.findall('\d+', s))
jfs

3
Ciò si traduce in un oggetto datetime ingenuo senza fuso orario, giusto? Quindi il bit UTC si perde nella traduzione?
wt

32

Qual è l'errore esatto che ricevi? È come il seguente?

>>> 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 sì, puoi dividere la stringa di input su ".", Quindi aggiungere i microsecondi al datetime ottenuto.

Prova questo:

>>> 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
Non puoi semplicemente eliminare .Z perché significa fuso orario e può essere diverso. Devo convertire la data nel fuso orario UTC.
Alexander Artemenko,

Un semplice oggetto datetime non ha il concetto di fuso orario. Se tutti i tuoi tempi terminano in "Z", tutti i tempi che ottieni sono UTC (ora Zulu).
tzot

se il fuso orario è diverso da ""o "Z", allora deve essere un offset in ore / minuti, che può essere aggiunto / sottratto direttamente dall'oggetto datetime. è possibile creare una sottoclasse tzinfo per gestirla, ma probabilmente non è consigliabile.
SingleNegationElimination

8
Inoltre, "% f" è l'identificatore dei microsecondi, quindi una stringa di tempo rapido (ingenuo rispetto al fuso orario) appare come: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor,

1
Ciò genererà un'eccezione se la stringa datetime specificata ha un offset UTC diverso da "Z". Non supporta l'intero formato RFC 3339 ed è una risposta inferiore ad altri che gestiscono correttamente gli offset UTC.
Mark Amery,

25

A partire da Python 3.7, strptime supporta i delimitatori dei due punti negli offset UTC ( sorgente ). Quindi puoi quindi usare:

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

MODIFICARE:

Come sottolineato da Martijn, se hai creato l'oggetto datetime usando isoformat (), puoi semplicemente usare datetime.fromisoformat ()


4
Ma in 3.7, è anche necessario datetime.fromisoformat()che gestisce le stringhe come il vostro input automaticamente: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters

2
Buon punto. Sono d'accordo, consiglio di usare datetime.fromisoformat()edatetime.isoformat()
Andreas Profous,

19

In questi giorni, Arrow può anche essere usato come soluzione di terze parti:

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())

6
Arrow non supporta correttamente ISO8601: github.com/crsmithdev/arrow/issues/291
scatola il

1
Basta usare python-dateutil - la freccia richiede python-dateutil.
danizen,

Arrow ora supporta ISO8601. I problemi indicati sono ora chiusi.
Altus

18

Basta usare il python-dateutilmodulo:

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

Documentazione


1
Non è esattamente la risposta @Flimms sopra?
Leo

1
Dove lo vedi analizzare in pochi secondi? Ho trovato questo articolo cercando di guadagnare tempo, quindi ho pensato che lo sarebbe stato anche qualcun altro.
Blairg23

1
Questo non è UTC sul mio sistema. Piuttosto, l'output in secondi è l'ora dell'epoca unix come se la data fosse nel mio fuso orario locale.
Elliot

1
Questa risposta è errata e non dovrebbe essere accettata. Probabilmente tutta la questione deve essere contrassegnato come duplicato di stackoverflow.com/questions/11743019/...
tripleee

@tripleee In realtà ho appena controllato il codice e sembra restituire la risposta corretta: 455051100(controllato su epochconverter.com ) ,,, a meno che non mi manchi qualcosa?
Blairg23,

13

Se non vuoi usare dateutil, puoi provare questa funzione:

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)

Test:

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

Risultato:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)

5
Questa risposta si basa sulla codifica definitiva di un particolare offset UTC (ovvero "Z", che significa +00: 00) nella stringa di formato passata a strptime. Questa è una cattiva idea perché non analizzerà alcun datetime con un diverso offset UTC e genererà un'eccezione. Vedi la mia risposta che descrive come analizzare RFC 3339 con strptime sia di fatto impossibile.
Mark Amery,

1
È hard-coded ma è sufficiente per il caso in cui è necessario analizzare solo zulu.
Sasha,

1
@alexander yes - che può essere il caso se, ad esempio, sai che la tua stringa di date è stata generata con il toISOStringmetodo JavaScript . Ma in questa risposta non si fa menzione della limitazione alle date di tempo di Zulu, né la domanda indica che è tutto ciò che serve, e il solo utilizzo dateutilè di solito altrettanto conveniente e meno stretto in ciò che può analizzare.
Mark Amery,


11

Ho trovato ciso8601 il modo più veloce per analizzare i timestamp ISO 8601. Come suggerisce il nome, è implementato in C.

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

Il README di GitHub Repo mostra il loro speedup> 10x rispetto a tutte le altre librerie elencate nelle altre risposte.

Il mio progetto personale prevedeva un sacco di analisi ISO 8601. È stato bello poter semplicemente cambiare chiamata e andare 10 volte più veloce. :)

Modifica: da allora sono diventato un manutentore di ciso8601. Ora è più veloce che mai!


Sembra un'ottima biblioteca! Per coloro che desiderano ottimizzare l'analisi ISO8601 su Google App Engine, purtroppo, non possiamo usarlo poiché è una libreria C, ma i tuoi benchmark sono stati penetranti per dimostrare che nativa datetime.strptime()è la soluzione più veloce successiva. Grazie per aver raccolto tutte queste informazioni!
hamx0r,

3
@ hamx0r, tenere presente che datetime.strptime()non è una libreria di analisi ISO 8601 completa. Se usi Python 3.7, puoi usare il datetime.fromisoformat()metodo, che è un po 'più flessibile. Potresti essere interessato a questo elenco più completo di parser che dovrebbe essere unito al README ciso8601 al più presto.
Movermeyer,

ciso8601 funziona abbastanza bene, ma si deve prima fare "pip install pytz", perché non è possibile analizzare un timestamp con le informazioni sul fuso orario senza la dipendenza pytz. L'esempio sarebbe simile a: dob = ciso8601.parse_datetime (risultato ['dob'] ['data'])
Dirk,

2
@Dirk, solo in Python 2 . Ma anche questo dovrebbe essere rimosso nella prossima versione.
Movermeyer,

8

Funziona con stdlib da Python 3.2 in poi (supponendo che tutti i timestamp siano UTC):

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

Per esempio,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)

2
Questa risposta si basa sulla codifica definitiva di un particolare offset UTC (ovvero "Z", che significa +00: 00) nella stringa di formato passata a strptime. Questa è una cattiva idea perché non analizzerà alcun datetime con un diverso offset UTC e genererà un'eccezione. Vedi la mia risposta che descrive come analizzare RFC 3339 con strptime sia di fatto impossibile.
Mark Amery,

1
In teoria, sì, questo non riesce. In pratica, non ho mai incontrato una data in formato ISO 8601 che non era in Zulu. Per le mie necessità molto occasionali, funziona alla grande e non dipende da alcune librerie esterne.
Benjamin Riggs,

4
potresti usare al timezone.utcposto di timezone(timedelta(0)). Inoltre, il codice funziona in Python 2.6+ (almeno) se si fornisce l' utcoggetto tzinfo
jfs

Non importa se l'hai incontrato, non corrisponde alle specifiche.
Anniversario del

È possibile utilizzare %Zper il fuso orario nelle versioni più recenti di Python.
sventechie,

7

Sono l'autore dei programmi di utilità iso8601. Può essere trovato su GitHub o su PyPI . Ecco come è possibile analizzare il tuo esempio:

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

6

Un modo semplice per convertire una stringa di data simile a ISO 8601 in un timestamp o datetime.datetimeoggetto UNIX in tutte le versioni di Python supportate senza installare moduli di terze parti è utilizzare il parser di date di 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)

Produzione:

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
Grazie. Questo è disgustoso. Lo adoro.
mercoledì

1
Che trucco incredibile, fantastico, bello! Grazie!
Ho

6

Ho codificato un parser per lo standard ISO 8601 e l'ho messo su GitHub: https://github.com/boxed/iso8601 . Questa implementazione supporta tutto nelle specifiche tranne durate, intervalli, intervalli periodici e date al di fuori dell'intervallo di date supportato del modulo datetime di Python.

I test sono inclusi! : P



6

La funzione parse_datetime () di Django supporta le date con offset UTC:

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

Quindi potrebbe essere utilizzato per l'analisi delle date ISO 8601 in campi all'interno dell'intero progetto:

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

Perché ISO 8601 consente in sostanza molte varianti di due punti e trattini opzionali CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Se vuoi usare strptime, devi prima eliminare quelle variazioni.

L'obiettivo è generare un oggetto datetime utc.


Se vuoi solo un caso di base che funzioni per UTC con il suffisso Z come 2016-06-29T19:36:29.3453Z:

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


Se si desidera gestire offset del fuso orario come 2016-06-29T19:36:29.3453-0400o 2008-09-03T20:56:35.450686+05:00utilizzare quanto segue. Questi convertiranno tutte le variazioni in qualcosa senza delimitatori variabili come 20080903T205635.450686+0500renderlo più coerente / più facile da analizzare.

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 il tuo sistema non supporta la %zdirettiva strptime (vedi qualcosa di simile ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'), devi compensare manualmente l'ora da Z(UTC). Nota %zpotrebbe non funzionare sul tuo sistema nelle versioni di Python <3 poiché dipendeva dal supporto della libreria c che varia a seconda del tipo di build di sistema / python (es. Jython, Cython, ecc.).

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

Per qualcosa che funziona con la libreria standard 2.X prova:

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

calendar.timegm è la versione gm mancante di time.mktime.


1
Questo ignora semplicemente il fuso orario '28/01/2013 T14: 01: 01.335612-08: 00' -> analizzato come UTC, non PDT
gatoatigrado

2

Python-dateutil genererà un'eccezione se analizza stringhe di date non valide, quindi potresti voler catturare l'eccezione.

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

2

Oggi c'è Maya: Datetimes for Humans ™ , dall'autore del popolare pacchetto 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>)

2

Un altro modo è quello di usare parser specializzata per ISO-8601 è quello di utilizzare isoparse funzione dateutil parser:

from dateutil import parser

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

Produzione:

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

Questa funzione è anche menzionata nella documentazione per la funzione standard Python datetime.fromisoformat :

Un parser ISO 8601 più completo, dateutil.parser.isoparse è disponibile nel pacchetto di terze parti dateutil.


1

Grazie alla grande risposta di Mark Amery, ho ideato la funzione per tenere conto di tutti i possibili formati ISO di 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))

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)

Nota che dovremmo cercare se la stringa non termina con Z, potremmo analizzare usando %z.


0

Inizialmente ho provato 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)

Ma questo non ha funzionato su fusi orari negativi. Questo comunque ho funzionato bene, in 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)

Alcuni test, notano che l'uscita differisce solo per precisione dei microsecondi. Sono arrivato a 6 cifre di precisione sulla mia macchina, ma 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)

Posso chiederti perché l'hai fatto frozenset(('+', '-'))? Una tupla normale non dovrebbe ('+', '-')essere in grado di realizzare la stessa cosa?
Prahlad Yeri,

Certo, ma non è una scansione lineare piuttosto che una ricerca con hash perfetto?
All'8
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.