Come analizzare / formattare le date con LocalDateTime? (Java 8)


341

Java 8 ha aggiunto una nuova API java.time per lavorare con date e orari ( JSR 310 ).

Ho data e ora come stringa (ad es "2014-04-08 12:30".). Come posso ottenere LocalDateTimeun'istanza dalla stringa specificata?

Dopo aver finito di lavorare con l' LocalDateTimeoggetto: come posso riconvertire l' LocalDateTimeistanza in una stringa con lo stesso formato mostrato sopra?


11
Cordiali saluti, la maggior parte delle persone vorrebbe un ZonedDateTimepiuttosto che un LocalDateTime. Il nome è controintuitivo; la Localsignifica qualsiasi frazione in generale piuttosto che un tempo specifico orario. Come tale, un LocalDateTimeoggetto non è legato alla linea del tempo. Per avere un significato, per ottenere un momento specifico sulla linea temporale, è necessario applicare un fuso orario.
Basil Bourque,

Vedi la mia risposta per una spiegazione di LocalDateTimevs. ZonedDateTimevs. OffsetDateTimevs. Instantvs. LocalDatevs. LocalTime, su come mantenere la calma sul perché è così complicato e su come farlo al primo colpo.
Ondra Žižka,

1
Se non fosse stato impraticabilmente lungo, LocalDateTimeprobabilmente sarebbe stato nominato ZonelessOffsetlessDateTime.
Ondra Žižka,

Risposte:


534

Data e ora di analisi

Per creare un LocalDateTimeoggetto da una stringa è possibile utilizzare il LocalDateTime.parse()metodo statico . Prende una stringa e un DateTimeFormatterparametro as. Il DateTimeFormatterviene utilizzato per specificare il modello di data / ora.

String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

Data e ora di formattazione

Per creare una stringa formattata fuori da un LocalDateTimeoggetto è possibile utilizzare il format()metodo

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

Si noti che ci sono alcuni formati di data / ora comunemente usati predefiniti come costanti in DateTimeFormatter. Ad esempio: l'utilizzo DateTimeFormatter.ISO_DATE_TIMEper formattare l' LocalDateTimeistanza dall'alto comporterebbe la stringa "1986-04-08T12:30:00".

I metodi parse()e format()sono disponibili per tutti gli oggetti relativi a data / ora (ad es. LocalDateO ZonedDateTime)


77
Solo per notare che DateTimeFormatter è immutabile e sicuro per i thread, e quindi l'approccio raccomandato è di memorizzarlo in una costante statica ove possibile.
JodaStephen,

@micha cosa succede se ho "2016-12-31T07: 59: 00.000Z" in questa data?
Dawood Ahmed,

14
@DawoodAbbasi tryDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha,

1
@Loenix forse è perché stai provando a chiamare format()la classe LocalDateTime anziché sull'istanza? Almeno, è quello che ho fatto: ho confuso DateTimecon dateTimel'esempio sopra.
glaed

2
Non dimenticare il maiuscolo su MM
Wesos de Queso,

159

Puoi anche usare LocalDate.parse()o LocalDateTime.parse()su a Stringsenza fornire un modello, se Stringè in formato ISO-8601 .

per esempio,

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date: " + aLD);

String strDatewithTime = "2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time: " + aLDT);

Uscita ,

Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

e utilizzalo DateTimeFormattersolo se hai a che fare con altri schemi di date.

Ad esempio, nel seguente esempio, dd MMM uuuu rappresenta il giorno del mese (due cifre), tre lettere del nome del mese (gennaio, febbraio, marzo, ...) e un anno di quattro cifre:

DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate = "04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate + " parses to " + lds);

Produzione

04 Aug 2015 parses to 2015-08-04

ricorda anche che l' DateTimeFormatteroggetto è bidirezionale; può analizzare sia l'input che il formato dell'output.

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD + " formats as " + dTF.format(aLD));

Produzione

2015-08-04 formats as 04 Aug 2015

(vedi l' elenco completo dei Pattern per la formattazione e l'analisi del DateFormatter )

  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use

11
Questa risposta ha toccato un argomento importante: usa i formattatori predefiniti ove possibile, ad es. NON creare una base di formattatori su "yyyy-MM-dd", usa invece DateTimeFormatter.ISO_LOCAL_DATE. Farà apparire il tuo codice molto più pulito. Inoltre, cerca di massimizzare l'uso del formato ISO8061, pagherà i dividendi a lungo termine.
Christopher Yang,

Voglio analizzare una data per la convalida come 2018-08-09 12:00:08ma quando analizzo vedo che Tviene aggiunta una che non mi serve. C'è un modo per farlo?
Raghuveer,

@Raghuveer La T è solo il delimitatore ISO-8061 tra data e ora. Se invece hai uno spazio nel tuo formato, puoi semplicemente usare il modello yyyy-MM-dd hh:mm:ssper l'analisi e la formattazione. La T mostrerà sempre nel formato predefinito (ISO-8061), ma puoi usare i tuoi schemi.
Egor Hans,

39

Entrambe le risposte sopra spiegano molto bene la domanda relativa agli schemi di stringhe. Tuttavia, nel caso in cui si lavori con ISO 8601, non è necessario applicare DateTimeFormatterpoiché LocalDateTime è già pronto per questo:

Convertire LocalDateTime in fuso orario ISO8601 String

LocalDateTime ldt = LocalDateTime.now(); 
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();

Converti da ISO8601 String di nuovo in LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();

20

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

  1. 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.
  2. 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.
  3. 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, ZonedDateTimeet al. così complicato

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

  2. 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".

  3. 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):

  1. 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:00potrebbe 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.
  2. 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 .

  3. 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:00potrebbe 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.Dateun 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.Datestato introdotto, con i suoi limiti.
  • Poiché nessuno dei due copriva bene calendari e fusi orari diversi, è Calendarstata 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?

  1. Ha un time offset?
    Un offset di tempo è la +hh:mmparte. A volte, +00:00può essere sostituito Zcome "tempo Zulu", UTCcome tempo coordinato universale o GMTcome tempo medio di Greenwich. Questi impostano anche il fuso orario.
    Per questi timestamp, si utilizza OffsetDateTime.

  2. 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 ZoneIdjavadoc, l'id della zona può anche essere in qualche modo specificato come Zoffset. 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.

  3. Se non ha nessuno dei due , il contesto mancante viene assunto o trascurato. E il consumatore deve decidere. Quindi deve essere analizzato LocalDateTimee convertito OffsetDateTimeaggiungendo 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 YearMonthfuori di esso.

Se hai le informazioni complete, puoi ottenere un java.time.Instant. Questo è anche usato internamente per convertire tra OffsetDateTimee ZonedDateTime.

Scopri come analizzarlo

Esiste una vasta documentazione sulla DateTimeFormatterquale è possibile analizzare una stringa di data e ora e formattare su stringa.

I pre-creato DateTimeFormatters dovrebbero coprire tutti i formati di piùmeno timestamp standard. Ad esempio, ISO_INSTANTpuò 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 DateTimeFormattere trarre ispirazione su come costruirne uno usando DateTimeFormatterBuilder. Mentre sei lì, dai anche un'occhiata a ResolverStyleche 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.timeJava 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 :))


3

OTTIENI L'ORA UTC ATTUALE NEL FORMATO RICHIESTO

// Current UTC time
        OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);

        // GET LocalDateTime 
        LocalDateTime localDateTime = utc.toLocalDateTime();
        System.out.println("*************" + localDateTime);

        // formated UTC time
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF.format(localDateTime));

        //GET UTC time for current date
        Date now= new Date();
        LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
        DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));

0

Ho trovato meraviglioso coprire più varianti del formato data e ora in questo modo:

final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);

1
`` `DateTimeFormatter statico finale pubblico TIMESTAMP_XX = new DateTimeFormatterBuilder (). appendPattern (" [[uuuu] [- MM] [- dd]] [[HH] [: mm] [: ss] [. SSS]] "). parseDefaulting (ChronoField.YEAR, 2020) .parseDefaulting (ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting (ChronoField.DAY_OF_MONTH, 1) .parseDefaulting (ChronoFiODDF) 0) , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); ``
Alan Stewart il
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.