L'analisi di una stringa con data e ora in un determinato momento (Java lo chiama " Instant
") è piuttosto complicata. Java ha affrontato questo problema in diverse iterazioni. L'ultima, java.time
e java.time.chrono
, copre quasi tutte le esigenze (tranne Time Dilation :)).
Tuttavia, questa complessità crea molta confusione.
La chiave per comprendere l'analisi della data è:
Perché Java ha tanti modi per analizzare una data
- Esistono diversi sistemi per misurare un tempo. Ad esempio, i calendari storici giapponesi sono stati derivati dagli intervalli di tempo del regno del rispettivo imperatore o dinastia. Quindi c'è ad esempio un timestamp UNIX. Fortunatamente, l'intero mondo (aziendale) è riuscito a utilizzare lo stesso.
- Storicamente, i sistemi venivano cambiati da / a, per vari motivi . Ad esempio dal calendario giuliano al calendario gregoriano nel 1582. Quindi le date "occidentali" prima devono essere trattate diversamente.
- E ovviamente il cambiamento non è avvenuto in una sola volta. Poiché il calendario proveniva dai quartier generali di alcune religioni e altre parti d'Europa credevano in altre diete, ad esempio la Germania non cambiò fino al 1700.
... e perché è LocalDateTime
, ZonedDateTime
et al. così complicato
Ci sono fusi orari . Un fuso orario è fondamentalmente una "striscia" * [1] della superficie terrestre le cui autorità seguono le stesse regole di quando ha quale offset temporale. Questo include le regole dell'ora legale.
I fusi orari cambiano nel tempo per varie aree, principalmente in base a chi conquista chi. E anche le regole di un fuso orario cambiano nel tempo .
Ci sono offset di tempo. Non è lo stesso dei fusi orari, perché un fuso orario può essere ad es. "Praga", ma ha la differenza di ora legale e la differenza di orario invernale.
Se si ottiene un timestamp con un fuso orario, l'offset può variare, a seconda della parte dell'anno in cui si trova. Durante l'ora bisestile, il timestamp può significare 2 tempi diversi, quindi senza ulteriori informazioni, non può essere affidabile convertito.
Nota: Con timestamp voglio dire "una stringa che contiene una data e / o tempo, a scelta con un fuso orario e / o il tempo di offset".
Diversi fusi orari possono condividere la stessa differenza oraria per determinati periodi. Ad esempio, il fuso orario GMT / UTC è uguale al fuso orario "Londra" quando la differenza di ora legale non è attiva.
Per renderlo un po 'più complicato (ma questo non è troppo importante per il tuo caso d'uso):
- Gli scienziati osservano la dinamica della Terra, che cambia nel tempo; in base a ciò, aggiungono secondi alla fine dei singoli anni. (Quindi
2040-12-31 24:00:00
potrebbe essere una data-ora valida.) Ciò richiede aggiornamenti regolari dei metadati utilizzati dai sistemi per avere le conversioni di date corrette. Ad esempio su Linux, si ottengono aggiornamenti regolari ai pacchetti Java inclusi questi nuovi dati.
Gli aggiornamenti non mantengono sempre il comportamento precedente per entrambi i timestamp storici e futuri. Quindi può accadere che l'analisi dei due timestamp attorno alla modifica di alcuni fusi orari confrontandoli possa dare risultati diversi quando si esegue su diverse versioni del software. Ciò vale anche per il confronto tra il fuso orario interessato e l'altro fuso orario.
Se ciò dovesse causare un bug nel tuo software, considera l'utilizzo di un timestamp che non ha regole così complicate, come il timestamp UNIX .
A causa di 7, per le date future, non possiamo convertire le date esattamente con certezza. Quindi, ad esempio, l'analisi corrente 8524-02-17 12:00:00
potrebbe essere disattivata di un paio di secondi dall'analisi futura.
Le API di JDK per questo si sono evolute con le esigenze contemporanee
- Le prime versioni di Java avevano esattamente
java.util.Date
un approccio un po 'ingenuo, supponendo che ci fossero solo l'anno, il mese, il giorno e l'ora. Questo rapidamente non è bastato.
- Inoltre, le esigenze dei database erano diverse, quindi abbastanza presto, è
java.sql.Date
stato introdotto, con i suoi limiti.
- Poiché nessuno dei due copriva bene calendari e fusi orari diversi, è
Calendar
stata introdotta l' API.
- Ciò non copriva ancora la complessità dei fusi orari. Eppure, il mix delle API di cui sopra è stato davvero un dolore con cui lavorare. Così quando gli sviluppatori Java hanno iniziato a lavorare su applicazioni Web globali, le librerie destinate alla maggior parte dei casi d'uso, come JodaTime, sono diventate rapidamente popolari. JodaTime è stato lo standard di fatto per circa un decennio.
- Ma JDK non si è integrato con JodaTime, quindi lavorare con esso è stato un po 'complicato. Quindi, dopo una lunghissima discussione su come affrontare la questione, JSR-310 è stato creato principalmente sulla base di JodaTime .
Come gestirlo in Java java.time
Determina a quale tipo analizzare un timestamp
Quando si utilizza una stringa timestamp, è necessario sapere quali informazioni contiene. Questo è il punto cruciale. Se non riesci a farlo bene, finisci con eccezioni criptiche come "Impossibile creare Instant" o "Offset zona mancante" o "ID zona sconosciuto" ecc.
Contiene la data e l'ora?
Ha un time offset?
Un offset di tempo è la +hh:mm
parte. A volte, +00:00
può essere sostituito Z
come "tempo Zulu", UTC
come tempo coordinato universale o GMT
come tempo medio di Greenwich. Questi impostano anche il fuso orario.
Per questi timestamp, si utilizza OffsetDateTime
.
Ha un fuso orario?
Per questi timestamp, si utilizza ZonedDateTime
.
La zona è specificata da
- nome ("Praga", "Pacific Standard Time", "PST") o
- "ID zona" ("America / Los_Angeles", "Europa / Londra"), rappresentato da java.time.ZoneId .
L'elenco dei fusi orari è compilato da un "database TZ" , supportato da ICAAN.
Secondo ZoneId
javadoc, l'id della zona può anche essere in qualche modo specificato come Z
offset. Non sono sicuro di come si associ a zone reali. Se il timestamp, che ha solo una TZ, cade in un salto nell'ora del cambio di offset, allora è ambiguo e l'interpretazione è soggetta ResolverStyle
, vedi sotto.
Se non ha nessuno dei due , il contesto mancante viene assunto o trascurato. E il consumatore deve decidere. Quindi deve essere analizzato LocalDateTime
e convertito OffsetDateTime
aggiungendo le informazioni mancanti:
- Si può presumere che sia l'ora UTC. Aggiungi l'offset UTC di 0 ore.
- Puoi presumere che sia il momento del luogo in cui sta avvenendo la conversione. Converti aggiungendo il fuso orario del sistema.
- Puoi trascurare e usarlo così com'è. Ciò è utile, ad esempio, per confrontare o sottrarre due volte (vedi
Duration
), o quando non lo sai e non ha importanza (ad es. Orario degli autobus locali).
Informazioni sul tempo parziale
- Sulla base di quanto il timestamp contiene, si può prendere
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, o YearMonth
fuori di esso.
Se hai le informazioni complete, puoi ottenere un java.time.Instant
. Questo è anche usato internamente per convertire tra OffsetDateTime
e ZonedDateTime
.
Scopri come analizzarlo
Esiste una vasta documentazione sulla DateTimeFormatter
quale è possibile analizzare una stringa di data e ora e formattare su stringa.
I pre-creato DateTimeFormatter
s dovrebbero coprire tutti i formati di piùmeno timestamp standard. Ad esempio, ISO_INSTANT
può analizzare 2011-12-03T10:15:30.123457Z
.
Se hai un formato speciale, puoi creare il tuo DateTimeFormatter (che è anche un parser).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Consiglio di guardare il codice sorgente di DateTimeFormatter
e trarre ispirazione su come costruirne uno usando DateTimeFormatterBuilder
. Mentre sei lì, dai anche un'occhiata a ResolverStyle
che controlla se il parser è LENIENT, SMART o STRICT per i formati e le informazioni ambigue.
TemporalAccessor
Ora, l'errore frequente è quello di andare nella complessità di TemporalAccessor
. Questo deriva dal modo in cui gli sviluppatori erano abituati a lavorare SimpleDateFormatter.parse(String)
. Bene, DateTimeFormatter.parse("...")
ti dà TemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Tuttavia, dotato delle conoscenze della sezione precedente, è possibile analizzare comodamente il tipo necessario:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
In realtà non è necessario il DateTimeFormatter
entrambi. I tipi che si desidera analizzare hanno i parse(String)
metodi.
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Per quanto riguarda TemporalAccessor
, puoi usarlo se hai una vaga idea di quali informazioni ci siano nella stringa e vuoi decidere in fase di esecuzione.
Spero di aver fatto luce sulla comprensione della tua anima :)
Nota: esiste un backport su java.time
Java 6 e 7: ThreeTen-Backport . Per Android ha ThreeTenABP .
[1] Non solo non sono strisce, ma anche alcuni estremi strani. Ad esempio, alcune isole del Pacifico vicine hanno fusi orari +14: 00 e -11: 00. Ciò significa che mentre ci si trova su un'isola, c'è il 1 maggio alle 15.00, su un'altra isola non molto lontano, è ancora il 30 aprile alle 12:00 (se ho contato correttamente :))
ZonedDateTime
piuttosto che unLocalDateTime
. Il nome è controintuitivo; laLocal
significa qualsiasi frazione in generale piuttosto che un tempo specifico orario. Come tale, unLocalDateTime
oggetto non è legato alla linea del tempo. Per avere un significato, per ottenere un momento specifico sulla linea temporale, è necessario applicare un fuso orario.