Wie konvertiere ich ZonedDateTime in Date?

101

Ich versuche, eine serverunabhängige Datumszeit in meiner Datenbank festzulegen, und ich glaube, dass die beste Vorgehensweise darin besteht, eine UTC-Datumszeit festzulegen. Mein DB-Server ist Cassandra und der DB-Treiber für Java versteht nur den Datumstyp.

Angenommen, ich verwende in meinem Code das neue Java 8 ZonedDateTime, um die UTC jetzt ( ZonedDateTime.now(ZoneOffset.UTC)) abzurufen. Wie kann ich diese ZonedDateTime-Instanz in die Datumsklasse "Legacy" konvertieren?

Milen Kovachev
quelle
Es gibt zwei „Date“ Klassen in Java gebaut, java.util.Dateund java.sql.Date.
Basil Bourque

Antworten:

161

Sie können ZonedDateTime in einen Moment konvertieren, den Sie direkt mit Date verwenden können.

Date.from(java.time.ZonedDateTime.now().toInstant());
Schlanke Soltani Dridi
quelle
27
Nein, es ist das aktuelle Datum in Ihrem Zonensystem.
Slim Soltani Dridi
6
@MilenKovachev Ihre Frage ist nicht sinnvoll - ein Datum hat keine Zeitzone - es repräsentiert nur einen Zeitpunkt.
Assylias
1
@assylias Eigentlich macht deine Aussage keinen Sinn. Das Format, in dem die Zeit gespeichert wird, impliziert eine Zeitzone. Das Datum basiert auf UTC. Leider macht Java einige dumme Dinge und behandelt es nicht als solches. Darüber hinaus wird die Zeit als lokale TZ anstelle von UTC betrachtet. Die Art und Weise, wie Daten, LocalDateTime und ZonedDateTime Daten speichern, impliziert eine Zeitzone. Die Art und Weise, wie sie sich daran gewöhnen, ist, als ob es keine TZs gibt, was einfach falsch ist. java.util.Date hat implizit eine TZ der JVM-Standardeinstellung. Die Tatsache, dass die Leute es als etwas anderes behandeln (einschließlich der Java-Dokumente dafür!), Sind nur schlechte Leute.
David
5
@ David uh nein - a Dateist eine Anzahl von Millisekunden seit der Epoche - also hängt es mit UTC zusammen. Wenn Sie es drucken , wird die Standardzeitzone verwendet, aber die Date-Klasse kennt die Zeitzone des Benutzers nicht ... Siehe beispielsweise den Abschnitt "Zuordnung" in docs.oracle.com/javase/tutorial/datetime/iso/legacy .html Und LocalDateTime ist explizit ohne Bezug auf eine Zeitzone - was als verwirrend angesehen werden kann ...
Assylias
1
Ihre Antwort beinhaltet unnötig ZonedDateTime. Die java.time.InstantKlasse ist ein direkter Ersatz für java.util.Datebeide, die einen Moment in UTC darstellen, obwohl Instanteine feinere Auflösung von Nanosekunden anstelle von Millisekunden verwendet wird. Date.from( Instant.now() )hätte deine Lösung sein sollen. Oder genau new Date()das , was den gleichen Effekt hat und den aktuellen Moment in UTC erfasst.
Basil Bourque
63

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

Obwohl dieser Code oben keinen Sinn hatte. Beide java.util.Dateund Instantrepräsentieren einen Moment in UTC, immer in UTC. Der obige Code hat den gleichen Effekt wie:

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

Kein Vorteil hier zu verwenden ZonedDateTime. Wenn Sie bereits eine haben ZonedDateTime, passen Sie UTC an, indem Sie a extrahieren 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.
)

Andere Antwort richtig

Die Antwort von ssoltanid behandelt Ihre spezifische Frage, wie ein java.time-Objekt ( ZonedDateTime) der neuen Schule in ein Objekt der alten Schule konvertiert werden kann, korrekt java.util.Date. Extrahieren Sie die Instantaus der ZonedDateTime und übergeben Sie an java.util.Date.from().

Datenverlust

Beachten Sie, dass Sie einen Datenverlust erleiden , als InstantSpuren ns seit Epoche , während java.util.DateSpuren Millisekunden seit Epoche.

Diagramm zum Vergleich der Auflösungen von Millisekunden, Mikrosekunden und Nanosekunden

Ihre Frage und Ihre Kommentare werfen andere Fragen auf.

Server in UTC halten

Auf Ihren Servern sollte das Host-Betriebssystem generell auf UTC eingestellt sein. Die JVM übernimmt diese Host-Betriebssystemeinstellung als Standardzeitzone in den mir bekannten Java-Implementierungen.

Geben Sie die Zeitzone an

Sie sollten sich jedoch niemals auf die aktuelle Standardzeitzone der JVM verlassen. Anstatt die Hosteinstellung zu übernehmen, kann ein Flag, das beim Starten einer JVM übergeben wird, eine andere Zeitzone festlegen. Noch schlimmer: Jeder Code in einem Thread einer App kann jederzeit einen Aufruf tätigen java.util.TimeZone::setDefault, um diesen Standard zur Laufzeit zu ändern!

Cassandra- TimestampTyp

Jede anständige Datenbank und jeder anständige Treiber sollte automatisch die Anpassung einer übergebenen Datums- und Uhrzeit an UTC zur Speicherung übernehmen. Ich benutze Cassandra nicht, aber es scheint eine rudimentäre Unterstützung für Datum und Uhrzeit zu haben. Die Dokumentation besagt, dass sein TimestampTyp eine Anzahl von Millisekunden aus derselben Epoche ist (erster Moment von 1970 in UTC).

ISO 8601

Darüber hinaus akzeptiert Cassandra Zeichenfolgeneingaben in den Standardformaten ISO 8601 . Glücklicherweise verwendet java.time die ISO 8601-Formate als Standard für das Parsen / Generieren von Zeichenfolgen. Die Implementierung der InstantKlasse toStringwird gut funktionieren.

Präzision: Millisekunde gegen Nanosekord

Aber zuerst müssen wir die Nanosekundengenauigkeit von ZonedDateTime auf Millisekunden reduzieren. Eine Möglichkeit besteht darin, einen neuen Instant in Millisekunden zu erstellen. Glücklicherweise verfügt java.time über einige praktische Methoden zum Konvertieren in und aus Millisekunden.

Beispielcode

Hier ist ein Beispielcode 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

Oder Sie können gemäß diesem Cassandra Java-Treiberdokument eine java.util.DateInstanz übergeben (nicht zu verwechseln mit java.sqlDate). Daraus können Sie instantTruncatedToMillisecondsim obigen Code ein JuDate erstellen.

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

Wenn Sie dies oft tun, können Sie einen Einzeiler erstellen.

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

Aber es wäre ordentlicher, eine kleine Dienstprogrammmethode zu erstellen.

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;
}

Beachten Sie den Unterschied in all diesem Code als in der Frage. Der Code der Frage versuchte, die Zeitzone der ZonedDateTime-Instanz an UTC anzupassen. Das ist aber nicht nötig. Konzeptionell:

ZonedDateTime = Instant + ZoneId

Wir extrahieren nur den Instant-Teil, der sich bereits in UTC befindet (im Grunde genommen in UTC, lesen Sie das Klassendokument für genaue Details).


Tabelle der Datums- und Uhrzeittypen in Java, sowohl modern als auch alt


Über java.time

Das java.time- Framework ist in Java 8 und höher integriert. Diese Klassen verdrängen die lästigen alten Legacy - Datum-Zeit - Klassen wie java.util.Date, Calendar, & SimpleDateFormat.

Das Joda-Time- Projekt, das sich jetzt im Wartungsmodus befindet , empfiehlt die Migration zu den Klassen java.time .

Weitere Informationen finden Sie im Oracle-Lernprogramm . Suchen Sie im Stapelüberlauf nach vielen Beispielen und Erklärungen. Die Spezifikation ist JSR 310 .

Sie können java.time- Objekte direkt mit Ihrer Datenbank austauschen . Verwenden Sie einen JDBC-Treiber, der mit JDBC 4.2 oder höher kompatibel ist . Keine Notwendigkeit für Strings, keine Notwendigkeit für java.sql.*Klassen.

Woher bekomme ich die java.time-Klassen?

Tabelle, welche java.time-Bibliothek mit welcher Java- oder Android-Version verwendet werden soll

Das ThreeTen-Extra- Projekt erweitert java.time um zusätzliche Klassen. Dieses Projekt ist ein Testfeld für mögliche zukünftige Ergänzungen von java.time. Sie können einige nützliche Klassen hier wie finden Interval, YearWeek, YearQuarter, und mehr .

Basil Bourque
quelle
Wunderbare Erklärung, sehr detailliert. Ich danke dir sehr!
Gianmarco F.
4

Hier ist ein Beispiel für die Konvertierung der aktuellen Systemzeit in UTC. Dabei wird ZonedDateTime als String formatiert. Anschließend wird das String-Objekt mit java.text DateFormat in ein Datumsobjekt analysiert.

    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();
    }
Asanka Siriwardena
quelle
4

Wenn Sie den ThreeTen-Backport für Android verwenden und den neueren nicht verwenden können Date.from(Instant instant)(für den mindestens API 26 erforderlich ist), können Sie Folgendes verwenden:

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

oder:

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

Bitte lesen Sie auch den Rat in der Antwort von Basil Bourque

David Rawson
quelle
1
Der ThreeTen Backport (und ThreeTenABP) enthalten eine DateTimeUtilsKlasse mit den Konvertierungsmethoden, daher würde ich Date date = DateTimeUtils.toDate (zdt.toInstant ()); `verwenden. Es ist nicht so niedrig.
Ole VV
1

Sie können dies mit den in Java 8 und höher integrierten java.time- Klassen tun .

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);
Peter Lawrey
quelle
Entschuldigung, woher kommen die zeitlichen und all diese Konstanten?
Milen Kovachev
@MilenKovachev A ZonedDateTime ist eine Instanz von Temporal
Peter Lawrey
Danke, aber Sie hätten erwähnen sollen, dass Sie Ihre Antwort so bearbeitet haben, dass mein Kommentar nicht albern erscheint.
Milen Kovachev
2
@MilenKovachev Sie können einen Kommentar löschen, aber es ist keine dumme Frage.
Peter Lawrey
1
Ich kann nicht verstehen, warum diese Antwort abgelehnt wurde. Instants Sekunden und Nanosekunden verfolgen. Dates Spur Millisekunden. Diese Konvertierung von einem zum anderen ist korrekt.
Scottb
1

Ich benutze das.

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;
    }
}

Weil Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); es nicht funktioniert !!! Wenn Sie Ihre Anwendung auf Ihrem Computer ausführen, ist dies kein Problem. Wenn Sie jedoch in einer Region von AWS, Docker oder GCP ausgeführt werden, tritt ein Problem auf. Weil Computer nicht Ihre Zeitzone in der Cloud ist. Sie sollten Ihre korrekte Zeitzone im Code einstellen. Zum Beispiel Asien / Taipeh. Dann wird es in AWS oder Docker oder GCP korrigiert.

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);
    }
}
Beehuang
quelle
1
Dies scheint einer der komplexesten Wege in einer der 7 Antworten zu sein. Hat es irgendwelche Vorteile? Ich würde nicht denken. Es ist wirklich nicht nötig, Formatierungen und Analysen durchzuführen.
Ole VV
Danke, dass du fragst. Weil Date falseAns = Date.from (java.time.ZonedDateTime.ofInstant (jetzt zoneId) .toInstant ()) ;; Es funktioniert nicht !!!!!
Beehuang
Ich habe dir kurz vor 17:08 Sekundenschnipsel in meiner Zeitzone gezeigt und bekommen ans=Mon Jun 18 01:07:56 CEST 2018, was falsch ist und was dann wrongAns=Sun Jun 17 17:07:56 CEST 2018richtig ist.
Ole VV
Ich habe kein Problem beim Ausführen der App auf meinem Computer. Aber wenn Sie Docker verwenden, hat es ein Problem. Weil die Zeitzone im Docker nicht Ihre Zeitzone im Computer ist. Sie sollten Ihre korrekte Zeitzone im Code einstellen. Zum Beispiel Asien / Taipeh. Dann wird es in AWS, Docker, GCP oder einem anderen Computer korrigiert.
Beehuang
@ OleV.V. Kannst du verstehen? oder irgendwelche Fragen?
Beehuang
1

Die akzeptierte Antwort hat bei mir nicht funktioniert. Das zurückgegebene Datum ist immer das lokale Datum und nicht das Datum für die ursprüngliche Zeitzone. Ich lebe in UTC + 2.

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

Ich habe mir zwei alternative Möglichkeiten ausgedacht, um das richtige Datum aus einer ZonedDateTime zu erhalten.

Angenommen, Sie haben diese ZonedDateTime für Hawaii

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

oder für UTC wie ursprünglich gefragt

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

Alternative 1

Wir können java.sql.Timestamp verwenden. Es ist einfach, aber es wird wahrscheinlich auch Ihre Programmierintegrität beeinträchtigen

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

Alternative 2

Wir erstellen das Datum aus Millis ( hier früher beantwortet ). Beachten Sie, dass lokales ZoneOffset ein Muss ist.

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

Für eine Docker-Anwendung wie beehuang kommentiert sollten Sie Ihre Zeitzone einstellen.

Alternativ können Sie withZoneSameLocal verwenden . Beispielsweise:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00] wird konvertiert von

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

bis Di Jul 01 00:00:00 MESZ 2014 und bis

Date.from(zonedDateTime.toInstant())

bis Montag, 30. Juni, 22:00:00 UTC 2014

dwe
quelle
-1

Wenn Sie nur jetzt interessiert sind, dann verwenden Sie einfach:

Date d = new Date();
Jacob Eckel
quelle
Ich interessiere mich jetzt aber jetzt für UTC.
Milen Kovachev
2
Ja, das wird jetzt die UTC sein, das Datum weiß es nicht besser.
Jacob Eckel
3
Nein, das wird es nicht. Es befindet sich jetzt in der Zeitzone des lokalen Systems. Datum speichert keine Zeitzoneninformationen, verwendet jedoch die aktuelle Systemzeit und ignoriert die aktuelle Systemzeitzone, sodass sie für nie wieder in UTC konvertiert wird
David
2
@David Date behält die Zeit relativ zur UTC-Epoche bei. Wenn Sie also ein neues Date () von einem System in den USA und einem anderen Objekt von einem Computer in Japan nehmen, sind diese identisch (prüfen Sie, wie lange beide intern gespeichert sind).
Jacob Eckel
1
@JacobEckel - Ja, aber wenn Sie 9 Uhr morgens in ein Datum eingeben, während Ihr tz ein US-tz ist, und dann zu einem JP tz wechseln und ein neues Datum mit 9 Uhr morgens erstellen, ist der interne Wert in Datum unterschiedlich. Sobald Sie DST in den Mix werfen, können Sie Date nur dann zuverlässig verwenden, wenn Ihre App IMMER speziell in UTC ausgeführt wird. Dies kann sich aus einer Reihe von Gründen ändern, von denen sich die meisten auf schlechten Code beziehen.
David