Ich habe eine Java 8 Spring-Webanwendung, die mehrere Regionen unterstützt. Ich muss Kalenderereignisse für einen Kundenstandort erstellen. Nehmen wir also an, mein Web- und Postgres-Server wird in der MST-Zeitzone gehostet (aber ich denke, er könnte überall sein, wenn wir in die Cloud gehen). Aber der Kunde ist in EST. Nachdem ich einige bewährte Methoden befolgt hatte, die ich gelesen hatte, dachte ich, ich würde alle Datums- und Uhrzeitangaben im UTC-Format speichern. Alle Datums-/Uhrzeitfelder in der Datenbank werden als TIMESTAMP deklariert.
So nehme ich eine LocalDateTime und konvertiere sie in UTC:
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(), ZoneOffset.UTC, null);
//appointment startDateTime is a LocalDateTime
appointment.setStartDateTime( startZonedDT.toLocalDateTime() );
Wenn nun beispielsweise eine Suche nach einem Datum eingeht, muss ich vom angeforderten Datum die Uhrzeit in UTC umrechnen, die Ergebnisse abrufen und in die Benutzerzeitzone (in der Datenbank gespeichert) konvertieren:
Ich bin mir nicht sicher, ob dies der richtige Ansatz ist. Ich frage mich auch, ob ich möglicherweise Zeitzoneninformationen verliere, weil ich von LocalDateTime zu ZonedDateTime und umgekehrt wechsle.
Hier ist, was ich sehe, und es scheint mir nicht korrekt zu sein. Wenn ich die LocalDateTime von der Benutzeroberfläche erhalte, erhalte ich Folgendes:
2016-04-04T08:00
Die ZonedDateTime =
dateTime=2016-04-04T08:00
offset="Z"
zone="Z"
Und wenn ich dann diesen konvertierten Wert meinem Termin LocalDateTime zuordne, speichere ich Folgendes:
2016-04-04T08:00
Ich habe das Gefühl, dass ich durch die Speicherung in LocalDateTime die Zeitzone verliere, die ich in ZonedDateTime konvertiert habe.
Sollte ich dafür sorgen, dass meine Entität (Termin) ZonedDateTime anstelle von LocalDateTime verwendet, damit Postgres diese Informationen nicht verliert?
—————- Bearbeiten —————-
Nach Basils ausgezeichneter Antwort wurde mir klar, dass ich den Luxus habe, mich nicht um die Zeitzone des Benutzers zu kümmern – alle Termine beziehen sich auf einen bestimmten Standort, sodass ich alle Datums- und Uhrzeitangaben als UTC speichern und sie beim Abrufen in die Zeitzone des Standorts umrechnen kann. Ich habe die folgende Folgefrage gestellt
Ihre App benötigt Zeitzoneninformationen?
– Sanj
5. April 2016 um 5:03
Was Ihren Abschnitt „Bearbeiten“ betrifft, wissen Sie, dass Politiker Liebe um Zeitzonen, Offsets, Sommerzeit (DST) usw. neu zu definieren. Gehen Sie also niemals zu weit in die Zukunft mit UTC-Werten. Wenn Sie weit im Voraus vereinbarte Zahnhygienetermine meinen, z. „15.00 Uhr am 5. Dezember 2017 später in diesem Jahr“, dann möchten Sie vielleicht tatsächlich a verwenden LocalDateTime um diese Tatsache zu speichern. Geplant ist dort 15:00 Uhr, allerdings haben die Politiker diesen Zeitpunkt bis dahin möglicherweise neu definiert. Für einen Zeitplan können Sie die Zeitzone vorübergehend auf das Potenzial anwenden LocalDateTime einen tatsächlichen Moment als a erzeugen ZonedDateTime/Instant.
– Basil Bourque
2. März 2017 um 0:15
Basil Bourque
Postgres hat keinen solchen Datentyp wie TIMESTAMP. Postgres hat zwei Arten für Datum plus Uhrzeit: TIMESTAMP WITH TIME ZONE Und TIMESTAMP WITHOUT TIME ZONE. Diese Typen verhalten sich hinsichtlich der Zeitzoneninformationen sehr unterschiedlich.
Der WITH Typ Verwendet alle Offset- oder Zeitzoneninformationen, um das Datum und die Uhrzeit anzupassen koordinierte Weltzeit, entsorgt dann diesen Offset oder diese Zeitzone; Postgres speichert niemals die Offset-/Zoneninformationen.
Dieser Typ repräsentiert einen Moment, einen bestimmten Punkt auf der Zeitachse.
Der WITHOUT Typ ignoriert eventuell vorhandene Offset- oder Zoneninformationen.
Dieser Typ tut es nicht einen Moment darstellen. Es stellt eine vage Vorstellung davon dar Potenzial Momente entlang eines Bereichs von etwa 26–27 Stunden (dem Bereich der Zeitzonen rund um den Globus).
Das will man praktisch immer WITH Typ, als hier erklärt vom Experten David E. Wheeler. Der WITHOUT macht nur dann Sinn, wenn Sie eine vage Vorstellung von einem Datum und einer Uhrzeit haben und nicht von einem festen Punkt auf der Zeitachse. Beispielsweise würde „Weihnachten beginnt dieses Jahr am 25.12.2016T00:00:00“ im gespeichert werden WITHOUT wie es darauf ankommt beliebig Zeitzone, die noch nicht auf eine einzelne Zeitzone angewendet wurde, um einen tatsächlichen Moment auf der Zeitachse zu erhalten. Wenn die Elfen des Weihnachtsmanns die Startzeit für Eugene Oregon US verfolgen würden, würden sie das verwenden WITH Typ und eine Eingabe, die einen Offset oder eine Zeitzone enthielt, z 2016-12-25T00:00:00-08:00 was in Postgres als gespeichert wird 2016-12-25T08:00.00Z (bei dem die Z bedeutet Zulu oder UTC).
Das Äquivalent von Postgres TIMESTAMP WITHOUT TIME ZONE in java.time ist java.time.LocalDateTime. Da es Ihre Absicht war, in UTC zu arbeiten (eine gute Sache), sollten Sie das auch tun nicht verwenden LocalDateTime (Eine schlechte Sache). Das könnte für Sie der Hauptgrund für Verwirrung und Ärger sein. Sie denken ständig über die Verwendung nach LocalDateTime oder ZonedDateTime aber Sie sollten keines von beiden verwenden; stattdessen sollten Sie verwenden Instant (nachfolgend diskutiert).
Ich frage mich auch, ob ich möglicherweise Zeitzoneninformationen verliere, weil ich von LocalDateTime zu ZonedDateTime und umgekehrt wechsle.
Das bist du wirklich. Der ganze Sinn LocalDateTime ist zu verlieren Zeitzoneninformationen. Daher verwenden wir diese Klasse in den meisten Apps selten. Wieder das Weihnachtsbeispiel. Oder ein anderes Beispiel: „Unternehmensrichtlinie: Alle unsere Fabriken auf der ganzen Welt essen um 12:30 Uhr zu Mittag.“ Das wäre LocalTimeund für ein bestimmtes Datum, LocalDateTime. Aber das hat keine wirkliche Bedeutung, keinen tatsächlichen Punkt auf der Zeitachse, bis Sie eine Zeitzone anwenden, um einen zu erhalten ZonedDateTime. Diese Mittagspause wird in der Fabrik in Delhi zu einem anderen Zeitpunkt auf der Zeitleiste liegen als in der Fabrik in Düsseldorf und wiederum anders in der Fabrik in Detroit.
Das Wort „lokal“ in LocalDateTime mag so kontraintuitiv sein, wie es bedeutet keine besondere Lokalität. Wenn Sie „Lokal“ in einem Klassennamen lesen, denken Sie: „Kein Moment … nicht auf der Zeitachse … nur eine vage Vorstellung von einer Art Datum und Uhrzeit“.
Ihre Server sollten in der Zeitzone ihres Betriebssystems fast immer auf UTC eingestellt sein. Aber Ihre Programmierung sollte niemals von dieser Externalität abhängen, da es für einen Systemadministrator oder für jede andere Java-App allzu einfach ist, die aktuelle Standardzeitzone innerhalb der JVM zu ändern. Geben Sie daher immer Ihre gewünschte/erwartete Zeitzone an. (Gleiches gilt für LocaleÜbrigens.)
Ergebnis:
Du arbeitest viel zu hart.
Programmierer/Systemadministratoren müssen lernen, „global zu denken, lokal zu präsentieren“.
Denken Sie während des Arbeitstages in UTC, während Sie Ihren Geek-Schutzhelm tragen. Erst am Ende des Tages, wenn Sie auf die Zeit Ihres Laien umsteigen, sollten Sie wieder an die Ortszeit Ihrer Stadt denken.
Ihre Geschäftslogik sollte sich auf UTC konzentrieren. Ihre Datenbankspeicherung, Geschäftslogik, Datenaustausch, Serialisierung, Protokollierung und Ihr eigenes Denken sollten alle in der UTC-Zeitzone (und übrigens im 24-Stunden-Format) erfolgen. Wenden Sie beim Präsentieren von Daten für Benutzer nur dann eine bestimmte Zeitzone an. Betrachten Sie in Zonen unterteilte Datums- und Uhrzeitangaben als eine externe Sache und nicht als einen funktionierenden Teil der Interna Ihrer App.
Verwenden Sie auf der Java-Seite java.time.Instant (ein Moment auf der Zeitachse in UTC) in einem Großteil Ihrer Geschäftslogik.
Instant now = Instant.now();
Hoffentlich JDBC Die Treiber werden irgendwann aktualisiert, um mit java.time-Typen wie z. B. umzugehen Instant direkt. Bis dahin müssen wir java.sql-Typen verwenden. Die alte java.sql-Klasse verfügt über neue Methoden für die Konvertierung in/von java.time.
Geben Sie das jetzt weiter java.sql.TimeStamp Objekt über setTimestamp auf einen PreparedStatement in einer Spalte gespeichert werden soll, die als definiert ist TIMESTAMP WITH TIME ZONE in Postgres.
Um in die andere Richtung zu gehen:
Instant instant = ts.toInstant();
Das ist also einfach Instant Zu java.sql.Timestamp Zu TIMESTAMP WITH TIME ZONEalles in UTC. Keine Zeitzonen beteiligt. Die aktuelle Standardzeitzone Ihres Server-Betriebssystems, Ihrer JVM und Ihrer Clients ist irrelevant.
Um es dem Benutzer zu präsentieren, wenden Sie eine Zeitzone an. Verwenden richtige Zeitzonennamenniemals die 3-4-Buchstaben-Codes wie z EST oder IST.
Wenn Sie Daten ohne Offset von UTC oder Zeitzone erhalten, z. B 2016-04-04T08:00, sind diese Daten für Sie völlig nutzlos (vorausgesetzt, wir sprechen nicht über die oben besprochenen Weihnachts- oder Firmenessen-Szenarien). Ein Datum/Uhrzeit ohne Offset-/Zoneninformationen ist wie ein Geldbetrag ohne Angabe der Währung: 142.70 oder auch $142.70 — nutzlos. Aber USD 142.70oder CAD 142.70oder MXN 142.70… die sind nützlich.
Wenn du das verstehst 2016-04-04T08:00 Wert, und du bist es absolut sicher des beabsichtigten Offset-/Zonenkontexts, dann:
Analysieren Sie diese Zeichenfolge als LocalDateTime.
Wenden Sie einen Offset von UTC an, um eine zu erhalten OffsetDateTimeoder (besser) wenden Sie eine Zeitzone an, um eine zu erhalten ZonedDateTime.
Wie dieser Code.
LocalDateTime ldt = LocalDateTime.parse( "2016-04-04T08:00" );
ZoneId zoneId = ZoneId.of( "Asia/Kolkata" ); // Or "America/Montreal" etc.
ZonedDateTime zdt = ldt.atZone( zoneId ); // Or atOffset( myZoneOffset ) if only an offset is known rather than a full time zone.
Ihre Frage ist wirklich ein Duplikat vieler anderer. Diese Themen wurden bereits mehrfach in anderen Fragen und Antworten erörtert. Ich fordere Sie dringend auf, Stack Overflow zu durchsuchen und zu studieren, um mehr zu diesem Thema zu erfahren.
JDBC 4.2
Ab JDBC 4.2 können wir direkt austauschen java.time Objekte mit der Datenbank. Keine Notwendigkeit, es jemals zu verwenden java.sql.Timestamp noch einmal, noch die zugehörigen Klassen.
Speichern, verwenden OffsetDateTime wie in der JDBC-Spezifikation definiert.
myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ; // The JDBC spec requires support for `OffsetDateTime`.
…oder eventuell nutzen Instant direkt, sofern dies von Ihrem JDBC-Treiber unterstützt wird.
myPreparedStatement.setObject( … , instant ) ; // Your JDBC driver may or may not support `Instant` directly, as it is not required by the JDBC spec.
Weitere Informationen finden Sie unter Oracle-Tutorial. Und durchsuchen Sie Stack Overflow nach vielen Beispielen und Erklärungen. Spezifikation ist JSR 310.
Sie können umtauschen java.time Objekte direkt mit Ihrer Datenbank. Benutze einen JDBC-Treiber gemäß JDBC 4.2 oder später. Keine Notwendigkeit für Saiten, keine Notwendigkeit java.sql.* Klassen. Unterstützung für Hibernate 5 und JPA 2.2 java.time.
Spätere Versionen von Android (26+) bündeln Implementierungen des java.time Klassen.
Für frühere Android-Versionen (<26) ist ein Prozess bekannt als API-Entzuckerung bringt ein Teilmenge der java.time Funktionalität, die ursprünglich nicht in Android integriert war.
Wenn die Entzuckerung nicht das bietet, was Sie brauchen, ist die ThreeTenABP Projekt passt sich an ThreeTen-Backport (oben erwähnt) auf Android. Sehen So verwenden Sie ThreeTenABP….
Vielen Dank für die hervorragende Antwort. Ich habe viele Beiträge und Artikel über den besten Ansatz für den Umgang mit Zeitzonen in einer Web-App gelesen. Ich habe den Luxus zu wissen, dass alle verwendeten Zeiten für einen bestimmten Standort gelten, unabhängig davon, wo die Systemuhr des Benutzers sie anzeigt. Ich habe noch eine Anschlussfrage zu meiner Frage, falls es Ihnen nichts ausmacht. Nochmals vielen Dank für die ausführliche und hilfreiche Antwort.
– Sonoerin
7. April 2016 um 12:41
Ich muss das noch einmal lesen, wenn ich Zeit habe. Sehr schöne Erklärung. So gibt eine LocalTime beispielsweise den reinen „TIME“-Wert an, wie ein Schild an einer Ladentür mit der Aufschrift „Wir haben von 8:00 bis 19:00 Uhr geöffnet“ ← Dabei spielt die Sommerzeit keine Rolle, zum Beispiel ist 8 8 Zoll jeden Tag im Jahr für Ladenbesitzer und Kunden. Wenn wir hingegen einen Verweis auf ein Ereignis speichern müssen, müssen wir dessen Uhrzeit mit Zeitzone an PostgresSQL senden – es wird in UTC gespeichert. Wenn wir oder ein anderer Benutzer schließlich diese Zeit sehen müssen, wird sie in UTC ermittelt und an die endgültige Zeitzone des Benutzers angepasst. Stimmt das?
– Geldgeber7
23. Okt. 2020 um 22:36 Uhr
@funder7 (A) Ja, LocalTime stellt eine Tageszeit dar, nicht mehr und nicht weniger. (B) Die Buchung einer Veranstaltung oder eines Termins in der Zukunft ist etwas kompliziert, um möglichen Änderungen der Zonenregeln Rechnung zu tragen. Wenn Sie „Zahnarzttermin am nächsten 21. Januar 2021 um 15:00 Uhr“ meinen, speichern Sie Datum und Uhrzeit im Postgres-Typ TIMESTAMP WITHOUT TIME ZONE. Separat hinterlegen Sie in einer zweiten Textspalte die ID der gewünschten Zeitzone, z.B America/Chicago. Wenn Sie einen Kalender zur Präsentation für den Benutzer erstellen, extrahieren Sie zur Laufzeit den ersten als LocalDateTime & 2. als ZoneIdzu einer Form zusammenfügen ZonedDateTime.
– Basil Bourque
24. Okt. 2020 um 4:16
Lassen Sie uns diese Diskussion im Chat fortsetzen.
– Basil Bourque
24. Okt. 2020 um 16:25 Uhr
@funder7 Angenommen, Sie buchen Ihren nächsten Zahnarzttermin für den 15. Oktober des folgenden Jahres. Als Sie den Termin gebucht haben, gilt in Ihrem Land die Sommerzeit (DST), die am letzten Tag im September beginnt. Und dann beschließt Ihr Land, die Regel zu ändern, sodass die Sommerzeit am letzten Tag im Oktober beginnt. Zu welcher Tageszeit sollten Sie in der Zahnarztpraxis erscheinen? Ein in UTC aufgezeichneter Termin wäre falsch, da er um die Stunde des DST-Versatzes verschoben wäre.
– Basil Bourque
24. Okt. 2020 um 16:33 Uhr
TLDR:
Um von LocalDateTime in ZonedDateTime zu konvertieren, verwenden Sie den folgenden Code. Sie können verwenden .atZone( zoneId )eine umfassende Liste der Zonen-IDs finden Sie in der Spalte TZ database nameauf Wikipedia.
Ihre App benötigt Zeitzoneninformationen?
– Sanj
5. April 2016 um 5:03
Was Ihren Abschnitt „Bearbeiten“ betrifft, wissen Sie, dass Politiker Liebe um Zeitzonen, Offsets, Sommerzeit (DST) usw. neu zu definieren. Gehen Sie also niemals zu weit in die Zukunft mit UTC-Werten. Wenn Sie weit im Voraus vereinbarte Zahnhygienetermine meinen, z. „15.00 Uhr am 5. Dezember 2017 später in diesem Jahr“, dann möchten Sie vielleicht tatsächlich a verwenden
LocalDateTime
um diese Tatsache zu speichern. Geplant ist dort 15:00 Uhr, allerdings haben die Politiker diesen Zeitpunkt bis dahin möglicherweise neu definiert. Für einen Zeitplan können Sie die Zeitzone vorübergehend auf das Potenzial anwendenLocalDateTime
einen tatsächlichen Moment als a erzeugenZonedDateTime
/Instant
.– Basil Bourque
2. März 2017 um 0:15