Conversione tra java.time.LocalDateTime e java.util.Date


485

Java 8 ha un'API completamente nuova per data e ora. Una delle classi più utili in questa API è LocalDateTime, per contenere un valore data-con-ora indipendente dal fuso orario.

Probabilmente ci sono milioni di righe di codice che usano la classe legacy java.util.Dateper questo scopo. Pertanto, quando si interfacciano il vecchio e il nuovo codice, sarà necessario convertirli tra i due. Poiché non sembrano esserci metodi diretti per raggiungere questo obiettivo, come si può fare?




Risposte:


706

Risposta breve:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

Spiegazione: (basato su questa domanda su LocalDate)

Nonostante il suo nome, java.util.Daterappresenta un istante sulla linea temporale, non una "data". I dati effettivi memorizzati nell'oggetto sono longconteggi di millisecondi dal 1970-01-01T00: 00Z (mezzanotte all'inizio del 1970 GMT / UTC).

La classe equivalente a java.util.Datein JSR-310 è Instant, quindi ci sono metodi convenienti per fornire la conversione avanti e indietro:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

Un java.util.Dateesempio ha il concetto di fuso orario. Questo potrebbe sembrare strano se si chiama toString()a java.util.Date, perché toStringè relativo a un fuso orario. Tuttavia, questo metodo utilizza effettivamente il fuso orario predefinito di Java al volo per fornire la stringa. Il fuso orario non fa parte dello stato attuale di java.util.Date.

Una Instant, inoltre, non contiene alcuna informazione circa il fuso orario. Pertanto, per convertire da una Instantdata a ora locale è necessario specificare un fuso orario. Questo potrebbe essere il fuso orario predefinito ZoneId.systemDefault()- o potrebbe essere un fuso orario controllato dall'applicazione, ad esempio un fuso orario dalle preferenze dell'utente. LocalDateTimeha un metodo di fabbrica conveniente che richiede sia l'istante che il fuso orario:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

Al contrario, il LocalDateTimefuso orario viene specificato chiamando il atZone(ZoneId)metodo. La ZonedDateTimepossono poi essere convertiti direttamente ad un Instant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

Si noti che la conversione da LocalDateTimea ZonedDateTimeha il potenziale per introdurre comportamenti imprevisti. Questo perché non tutte le date-ora locali esistono a causa dell'ora legale. In autunno / autunno, c'è una sovrapposizione nella linea temporale locale in cui la stessa data-ora locale si verifica due volte. In primavera, c'è un vuoto, in cui scompare un'ora. Vedi il Javadoc atZone(ZoneId)per ulteriori informazioni sulla definizione di cosa farà la conversione.

Sintesi, se di andata e ritorno una java.util.Datead una LocalDateTimee di nuovo ad un java.util.Datesi può finire con un istante diverso a causa di ora legale.

Informazioni aggiuntive: c'è un'altra differenza che influenzerà le date molto vecchie. java.util.Dateusa un calendario che cambia al 15 ottobre 1582, con date precedenti a quella usando il calendario giuliano invece di quello gregoriano. Al contrario, java.time.*utilizza il sistema di calendario ISO (equivalente al gregoriano) per tutti i tempi. Nella maggior parte dei casi d'uso, il sistema di calendario ISO è quello che desideri, ma potresti vedere strani effetti quando si confrontano le date prima dell'anno 1582.


5
Grazie mille per una chiara spiegazione. Soprattutto per il motivo per cui java.util.Datenon contiene fuso orario, ma stamparlo durante toString(). La documentazione ufficiale dell'evento non lo dice chiaramente quando pubblichi.
Cherry,

Attenzione: LocalDateTime.ofInstant(date.toInstant()... non si comporta come ci si aspetterebbe ingenuamente. Ad esempio new Date(1111-1900,11-1,11,0,0,0);diventerà 1111-11-17 23:53:28usando questo approccio. Dai un'occhiata all'implementazione di java.sql.Timestamp#toLocalDateTime()se il risultato fosse necessario 1111-11-11 00:00:00nell'esempio precedente.
cane

2
Ho aggiunto una sezione su date molto vecchie (pre-1582). FWIW, la correzione suggerita potrebbe essere sbagliata, poiché 1111-11-11 in java.util.Date è lo stesso giorno effettivo nella storia di 1111-11-18 in java.time a causa dei diversi sistemi di calendario (la differenza di 6,5 minuti ricorre in molti fusi orari prima del 1900)
JodaStephen,

2
Vale anche la pena notare che java.sql.Date#toInstantgenera un UnsupportedOperationException. Quindi non utilizzare toInstantin RowMapper su java.sql.ResultSet#getDate.
LazerBass,

132

Ecco cosa mi è venuto in mente (e come tutti gli enigmi del Time Date probabilmente sarà smentito in base a qualche strano aggiustamento del fuso orario-leapyear-daylight: D)

Andata e ritorno: Date<<->>LocalDateTime

Dato: Date date = [some date]

(1) LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

Esempio:

Dato:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

Crea Instantda Date:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

Crea Dateda Instant(non necessario, ma a scopo illustrativo):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Crea LocalDateTimedaInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

Crea Instantda LocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Crea Dateda Instant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

L'output è:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

2
@scottb lo stai rivolgendo a me o alla persona che ha posto la domanda? Per quanto riguarda i miei pensieri, beh, sarebbe bello che la conversione fosse dichiarata esplicitamente nell'API jdk 8 - Avrebbero potuto almeno farlo. In ogni caso, ci sono molte librerie che verranno ricodificate per includere le nuove funzionalità Java-8 e sapere come farlo è importante.
Il coordinatore

4
@scottb Perché il caso di terze parti sarebbe insolito? È dannatamente comune. Un esempio: JDBC 4 e meno (si spera non 5).
Raman,

5
Come regola generale JSR-310, non è necessario eseguire la conversione tra i tipi usando epoca-millis. Esistono alternative migliori usando gli oggetti, vedi la mia risposta completa di seguito. La risposta sopra è valida anche solo se si utilizza un offset di zona come UTC - alcune parti della risposta non funzioneranno per un fuso orario completo come America / New_York.
JodaStephen,

2
Invece di Instant.ofEpochMilli(date.getTime())faredate.toInstant()
capra

1
@goat toInstant()sembra carino, tranne che non riesce java.sql.Date, arggggh! Quindi è finalmente più facile da usare Instant.ofEpochMilli(date.getTime()).
Vadipp,

22

Modo molto più conveniente se sei sicuro di aver bisogno di un fuso orario predefinito:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );

25
Certo, è più facile, ma non mi piace mescolare roba correlata a jdbc con una semplice gestione della data, almeno IMHO.
Enrico Giurin,

9
Mi dispiace, questa è una soluzione terribile a un semplice problema. È come definire 5 per essere uguale al numero di anelli nel logo olimpico.
Madbreaks,

7

quanto segue sembra funzionare durante la conversione dalla nuova API LocalDateTime in java.util.date:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

la conversione inversa può essere (si spera) raggiunta in modo simile ...

spero che sia d'aiuto...


5

Tutto è qui: http://blog.progs.be/542/date-to-java-time

La risposta con "round-trip" non è esatta: quando lo fai

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

se il fuso orario del tuo sistema non è UTC / GMT, puoi cambiare l'ora!


Solo se lo fai per 1 ora all'anno, in autunno, quando due volte da LocalDateTime si sovrappongono. Lo spostamento in avanti della molla non causa problemi. La maggior parte delle volte converte correttamente entrambe le direzioni.
David,

4

Il modo più veloce per LocalDateTime-> Dateè:

Date.from(ldt.toInstant(ZoneOffset.UTC))


3

Non sono sicuro se questo è il modo più semplice o migliore, o se ci sono insidie, ma funziona:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}

3
C'è sicuramente una trappola che va da LocalDateTimea Date. Alle transizioni di ora legale, a LocalDateTimepuò essere inesistente o verificarsi due volte. Devi capire cosa vuoi che accada in ogni caso.
Jon Skeet,

1
A proposito, GregorianCalendarappartiene alla vecchia API scomoda che la nuova java.timeAPI mira a sostituire
Vadzim,

3

Se sei su Android e stai usando threetenbp puoi usarloDateTimeUtils invece .

ex:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

non puoi usarlo Date.frompoiché è supportato solo su api 26+

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.