Come convertire ZonedDateTime in Date?


102

Sto cercando di impostare una data / ora indipendente dal server nel mio database e credo che la migliore pratica per farlo sia impostare una data e ora UTC. Il mio server db è Cassandra e il driver db per Java comprende solo il tipo di data.

Quindi, supponendo che nel mio codice sto usando il nuovo Java 8 ZonedDateTime per ottenere l'UTC now ( ZonedDateTime.now(ZoneOffset.UTC)), come posso convertire questa istanza di ZonedDateTime nella classe Date "legacy"?


Esistono due classi "Date" incorporate in Java java.util.Datee java.sql.Date.
Basil Bourque

Risposte:


162

Puoi convertire ZonedDateTime in un istante, che puoi utilizzare direttamente con Date.

Date.from(java.time.ZonedDateTime.now().toInstant());

27
No, sarà la data corrente sul sistema di zone predefinito.
Slim Soltani Dridi

6
@MilenKovachev La tua domanda non ha senso - una data non ha un fuso orario - rappresenta solo un istante nel tempo.
assylias

1
@assylias In realtà, la tua dichiarazione non ha senso. Il formato in cui è memorizzata l'ora implica un fuso orario. La data è basata su UTC, sfortunatamente Java fa alcune cose stupide e non le tratta come tali e, inoltre, considera l'ora come TZ locale anziché UTC. Il modo in cui Data, LocalDateTime, ZonedDateTime memorizzano i dati implica un fuso orario. Il modo in cui si abituano è come se i TZ non esistessero, il che è semplicemente sbagliato. java.util.Date ha una TZ dell'impostazione predefinita JVM, implicitamente. Il fatto che le persone lo trattino come qualcosa di diverso (inclusi i documenti java per esso!) È solo persone cattive.
David

5
@David uh no - a Dateè un numero di millisecondi dall'epoca - quindi è correlato a UTC. Se lo stampi , verrà utilizzato il fuso orario predefinito, ma la classe Date non conosce il fuso orario dell'utente ... Vedi ad esempio la sezione "mapping" in docs.oracle.com/javase/tutorial/datetime/iso/legacy .html E LocalDateTime è esplicitamente senza riferimento a un fuso orario - che può essere visto come
fonte di

1
La tua risposta coinvolge inutilmente ZonedDateTime. La java.time.Instantclasse è un sostituto diretto di java.util.Date, entrambi rappresentano un momento in UTC sebbene Instantutilizzino una risoluzione più fine di nanosecondi invece di millisecondi. Date.from( Instant.now() )avrebbe dovuto essere la tua soluzione. O per quella materia, new Date()che ha lo stesso effetto, catturare il momento corrente in UTC.
Basil Bourque

64

tl; dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

Sebbene non ci fosse alcun punto in quel codice sopra. Entrambi java.util.Datee Instantrappresentano un momento in UTC, sempre in UTC. Il codice sopra ha lo stesso effetto di:

new java.util.Date()  // Capture current moment in UTC.

Nessun vantaggio qui per l'utilizzo ZonedDateTime. Se hai già un ZonedDateTime, regola su UTC estraendo un file Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

Altra risposta corretta

La risposta di ssoltanid affronta correttamente la tua domanda specifica, come convertire un oggetto java.time di nuova scuola in un oggetto ZonedDateTimedi vecchia scuola java.util.Date. Estrarre il Instantda ZonedDateTime e passare a java.util.Date.from().

Perdita di dati

Si noti che si soffre di perdita di dati , come Instanttracce nanosecondi dal epoca , mentre java.util.Datele tracce millisecondi dal epoca.

diagramma che confronta risoluzioni di millisecondi, microsecondi e nanosecondi

La tua domanda e i tuoi commenti sollevano altri problemi.

Mantieni i server in UTC

I tuoi server dovrebbero avere il loro sistema operativo host impostato su UTC come best practice in generale. La JVM riprende questa impostazione del sistema operativo host come fuso orario predefinito, nelle implementazioni Java di cui sono a conoscenza.

Specificare il fuso orario

Ma non dovresti mai fare affidamento sul fuso orario predefinito corrente della JVM. Invece di prendere l'impostazione dell'host, un flag passato all'avvio di una JVM può impostare un altro fuso orario. Ancora peggio: qualsiasi codice in qualsiasi thread di qualsiasi app in qualsiasi momento può effettuare una chiamata a java.util.TimeZone::setDefaultper modificare l'impostazione predefinita in fase di esecuzione!

TimestampTipo Cassandra

Qualsiasi database e driver decente dovrebbe gestire automaticamente la regolazione di una data-ora passata su UTC per l'archiviazione. Non uso Cassandra, ma sembra avere un supporto rudimentale per la data e l'ora. La documentazione dice che il suo Timestamptipo è un conteggio di millisecondi della stessa epoca (primo momento del 1970 in UTC).

ISO 8601

Inoltre, Cassandra accetta input di stringhe nei formati standard ISO 8601 . Fortunatamente, java.time utilizza i formati ISO 8601 come impostazioni predefinite per l'analisi / la generazione di stringhe. L' implementazione della Instantclasse toStringandrà bene.

Precisione: millisecondi vs nanosecord

Ma prima dobbiamo ridurre la precisione in nanosecondi di ZonedDateTime a millisecondi. Un modo è creare un nuovo istantaneo usando i millisecondi. Fortunatamente, java.time ha alcuni metodi utili per la conversione in e da millisecondi.

Codice di esempio

Di seguito è riportato un codice di esempio in Java 8 Update 60.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );

Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

O secondo questo documento del driver Java Cassandra , puoi passare java.util.Dateun'istanza (da non confondere con java.sqlDate). Quindi potresti creare un juDate da quello instantTruncatedToMillisecondsnel codice sopra.

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

Se lo fai spesso, potresti creare una battuta.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

Ma sarebbe più ordinato creare un piccolo metodo di utilità.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

Notare la differenza in tutto questo codice rispetto alla domanda. Il codice della domanda stava tentando di regolare il fuso orario dell'istanza di ZonedDateTime su UTC. Ma non è necessario. Concettualmente:

ZonedDateTime = Istantaneo + ZoneId

Estraiamo solo la parte Instant, che è già in UTC (fondamentalmente in UTC, leggi il documento di classe per dettagli precisi).


Tabella dei tipi di data e ora in Java, sia moderni che legacy


Informazioni su java.time

Il framework java.time è integrato in Java 8 e versioni successive. Queste classi soppiantare la vecchia fastidiosi legacy classi data-time come java.util.Date, Calendar, e SimpleDateFormat.

Il progetto Joda-Time , ora in modalità di manutenzione , consiglia la migrazione alle classi java.time .

Per saperne di più, vedere il tutorial Oracle . E cerca Stack Overflow per molti esempi e spiegazioni. La specifica è JSR 310 .

Puoi scambiare oggetti java.time direttamente con il tuo database. Utilizza un driver JDBC conforme a JDBC 4.2 o successivo. Non c'è bisogno di stringhe, non c'è bisogno di java.sql.*classi.

Dove ottenere le classi java.time?

Tabella di quale libreria java.time utilizzare con quale versione di Java o Android

Il progetto ThreeTen-Extra estende java.time con classi aggiuntive. Questo progetto è un banco di prova per possibili future aggiunte a java.time. Si possono trovare alcune classi utili, come per esempio Interval, YearWeek, YearQuartere altro .


Splendida spiegazione, ben dettagliata. Grazie mille!
Gianmarco F.

4

Ecco un esempio di conversione dell'ora di sistema corrente in UTC. Implica la formattazione di ZonedDateTime come String e quindi l'oggetto String verrà analizzato in un oggetto data utilizzando java.text DateFormat.

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }

4

Se stai usando il backport ThreeTen per Android e non puoi usare il più recente Date.from(Instant instant)(che richiede almeno l'API 26) puoi usare:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

o:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Si prega di leggere anche il consiglio nella risposta di Basil Bourque


1
Il ThreeTen Backport (e ThreeTenABP) includono una DateTimeUtilsclasse con i metodi di conversione, quindi userei Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. Non è così di basso livello.
Ole VV

1

Puoi farlo utilizzando le classi java.time integrate in Java 8 e versioni successive.

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);

Non ho capito, da dove provengono il tempo e tutte queste costanti?
Milen Kovachev

@MilenKovachev A ZonedDateTime è un'istanza di Temporal
Peter Lawrey

Grazie, ma avresti dovuto menzionare che hai modificato la tua risposta in modo che il mio commento non sembri sciocco.
Milen Kovachev

2
@MilenKovachev puoi cancellare un commento, ma non è una domanda stupida.
Peter Lawrey

1
Non riesco a capire perché questa risposta sia stata sottovalutata. Instants traccia secondi e nanosecondi. Dates traccia millisecondi. Questa conversione dall'una all'altra è corretta.
scottb

1

Io uso questo.

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

Perché Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); non funziona !!! Se esegui la tua applicazione sul tuo computer, non è un problema. Ma se esegui in qualsiasi regione di AWS, Docker o GCP, genererà problemi. Perché il computer non è il tuo fuso orario su Cloud. È necessario impostare correttamente il fuso orario in Code. Ad esempio, Asia / Taipei. Quindi verrà corretto in AWS o Docker o GCP.

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}

1
Questo sembra essere uno dei modi più complessi in una qualsiasi delle 7 risposte. Ha dei vantaggi? Non penserei. Non c'è davvero bisogno di passare attraverso la formattazione e l'analisi.
Ole VV

Grazie chiedi. Perché Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Non funziona !!!!!
Beehuang

Ho eseguito lo snippet di secondi appena prima delle 17:08 nel mio fuso orario e ho ottenuto ans=Mon Jun 18 01:07:56 CEST 2018, che non è corretto, e quindi wrongAns=Sun Jun 17 17:07:56 CEST 2018, che è corretto.
Ole VV

Non ho alcun problema quando eseguo l'app sul mio computer. Ma quando si usa finestra mobile, ha un problema. Perché il fuso orario nella finestra mobile non è il tuo fuso orario nel computer. È necessario impostare correttamente il fuso orario in Code. Ad esempio, Asia / Taipei. Quindi verrà corretto in AWS, Docker, GCP o qualsiasi computer.
Beehuang

@ OleV.V. Riesci a capire ?? o qualche domanda?
Beehuang

1

La risposta accettata non ha funzionato per me. La data restituita è sempre la data locale e non la data del fuso orario originale. Vivo in UTC + 2.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

Ho escogitato due modi alternativi per ottenere la data corretta da ZonedDateTime.

Supponiamo di avere questo ZonedDateTime per le Hawaii

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

o per UTC come richiesto originariamente

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Alternativa 1

Possiamo usare java.sql.Timestamp. È semplice ma probabilmente intaccerà anche la tua integrità di programmazione

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

Alternativa 2

Creiamo la data da millisecondi (risposta qui prima). Nota che ZoneOffset locale è un must.

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);

0

Per un'applicazione docker come ha commentato Beehuang, è necessario impostare il fuso orario.

In alternativa puoi usare con ZoneSameLocal . Per esempio:

01-07-2014T00: 00 + 02: 00 [GMT + 02: 00] viene convertito da

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

a Tue Jul 01 00:00:00 CEST 2014 e da

Date.from(zonedDateTime.toInstant())

a lunedì 30 giugno 22:00:00 UTC 2014


-1

Se sei interessato solo ora, usa semplicemente:

Date d = new Date();

Mi interessa adesso, ma ora UTC.
Milen Kovachev

2
Sì, ora sarà l'UTC, la data non sa di meglio.
Jacob Eckel

3
No, non lo farà. Sarà Now nel fuso orario del sistema locale. La data non memorizza alcuna informazione sul fuso orario, ma utilizza l'ora di sistema corrente e ignora il fuso orario di sistema corrente, quindi non la converte mai in UTC
David

2
@David Date mantiene l'ora relativa all'epoca UTC, quindi se prendi una nuova Date () da un sistema negli Stati Uniti e un altro oggetto da un computer in Giappone saranno identici (guarda il tempo che entrambi mantengono internamente).
Jacob Eckel

1
@JacobEckel - Sì, ma se inserisci 9am in una data mentre la tua tz è una tz americana e poi cambi in JP tz e crei una nuova data con 9am, il valore interno in Data sarà diverso. Non appena si inserisce l'ora legale nel mix, non è possibile utilizzare in modo affidabile Date a meno che l'app non venga SEMPRE eseguita in UTC in modo specifico, il che potrebbe cambiare per un numero qualsiasi di motivi, la maggior parte dei quali ruota attorno a codice non valido.
David
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.