PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Speichern von Terminen in einer SQL-Datenbank wie Postgres zur Verwendung mit java.time Framework

Das hängt von der Art des Termins ab.

Es gibt zwei Arten von Terminen:

  • Ein Moment, ein bestimmter Punkt auf der Zeitachse, wobei alle Änderungen an den Zeitzonenregeln ignoriert werden.
    Beispiel:Raketenstart.
  • Ein Datum und eine Uhrzeit, die an Änderungen der Zeitzonenregeln angepasst werden sollten.
    Beispiel:Arzt-/Zahnarztbesuch.

Augenblick

Wenn wir zum Beispiel einen Raketenstart buchen, ist uns das Datum und die Uhrzeit egal. Wir kümmern uns nur um den Moment, wenn (a) der Himmel sich ausrichtet und (b) wir günstiges Wetter erwarten.

Sollten die Politiker in der Zwischenzeit die Regeln der Zeitzone an unserem Startplatz oder in unseren Büros ändern, hat dies keine Auswirkungen auf unseren Starttermin. Wenn Politiker, die unsere Startsite verwalten, die Daylight Saving Time (DST) übernehmen , der Moment unseres Starts bleibt gleich. Wenn die Politiker, die unsere Büros leiten, beschließen, wegen diplomatischer Beziehungen die Uhr eine halbe Stunde früher stellen mit einem Nachbarland bleibt der Zeitpunkt unseres Starts derselbe.

Für einen solchen Termin, ja, Ihr Ansatz wäre richtig. Sie würden den Termin in UTC aufzeichnen mit einer Spalte vom Typ TIMESTAMP WITH TIME ZONE . Passen Sie sich beim Abrufen an eine beliebige Zeitzone an, die der Benutzer bevorzugt.

Datenbanken wie Postgres Verwenden Sie alle Zeitzoneninformationen, die einer Eingabe beigefügt sind, um sie an UTC anzupassen, und verwerfen Sie dann diese Zeitzoneninformationen. Wenn Sie den Wert von Postgres abrufen, stellt er immer ein Datum mit Uhrzeit in UTC dar. Beachten Sie, dass einige Tools oder Middleware möglicherweise die Antifunktion haben, eine Standardzeitzone zwischen dem Abrufen aus der Datenbank und der Lieferung an Sie, den Programmierer, anzuwenden. Aber seien Sie klar:Postgres speichert und ruft immer Werte vom Typ TIMESTAMP WITH TIME ZONE ab in UTC, immer UTC, und offset-from-UTC von null Stunden-Minuten-Sekunden.

Hier ist ein Beispiel für Java-Code.

LocalDate launchDateAsSeenInRome = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime launchTimeAsSeenInRome = LocalTime.of( 21 , 0 ) ;
ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;
// Assemble those three parts to determine a moment.
ZonedDateTime launchMomentAsSeenInRome = ZonedDateTime.of( launchDateAsSeenInRome , launchTimeAsSeenInRome , zoneEuropeRome ) ;

Um denselben Moment in UTC zu sehen, wandeln Sie ihn in einen Instant um . Ein Instant Objekt stellt immer einen Moment dar, wie er in UTC gesehen wird.

Instant launchInstant = launchMomentAsSeenInRome.toInstant() ;  // Adjust from Rome time zone to UTC.

Das Z am Ende des obigen Zeichenfolgenbeispiels ist die Standardnotation für UTC und wird „Zulu“ ausgesprochen.

Leider hat das JDBC 4.2-Team es versäumt, Unterstützung für entweder Instant oder ZonedDateTime Typen. Also Ihr JDBC-Treiber möglicherweise nicht in der Lage sein, solche Objekte in Ihrer Datenbank zu lesen/zu schreiben. Wenn nicht, konvertieren Sie einfach in OffsetDateTime . Alle drei Typen repräsentieren einen Moment, einen bestimmten Punkt auf der Zeitachse. Aber OffsetDateTime unterstützt von JDBC 4.2 aus Gründen, die mir entgehen.

OffsetDateTime odtLaunchAsSeenInRome = launchMomentAsSeenInRome.toOffsetDateTime() ;

In Datenbank schreiben.

myPreparedStatement.setObject( … , odtLaunchAsSeenInRome ) ;

Abruf aus der Datenbank.

OffsetDateTime launchMoment = myResultSet.getObject( … , OffsetDateTime.class ) ;

Passen Sie die von Ihrem Benutzer gewünschte New Yorker Zeitzone an.

ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime launchAsSeenInNewYork = launchMoment.atZoneSameInstant( zoneAmericaNewYork ) ;

Sie können den gesamten oben genannten Code live auf IdeOne.com ausführen .

Übrigens wird auch das Verfolgen vergangener Ereignisse als Moment behandelt. Wann ist der Patient tatsächlich zum Termin erschienen, wann hat der Kunde die Rechnung bezahlt, wann hat ein neuer Mitarbeiter seine Dokumente unterschrieben, wann ist der Server abgestürzt … all dies wird als Moment in UTC verfolgt. Wie oben besprochen, wäre das normalerweise ein Instant , obwohl ZonedDateTime &OffsetDateTime stellen auch einen Moment dar. Verwenden Sie für die Datenbank TIMESTAMP WITH TIME ZONE (nicht WITHOUT ).

Tageszeit

Ich gehe davon aus, dass sich die meisten geschäftsorientierten Apps auf Termine der anderen Art konzentrieren, bei denen wir eher auf ein Datum mit einer Tageszeit als auf einen bestimmten Zeitpunkt abzielen.

Wenn der Benutzer einen Termin mit seinem Gesundheitsdienstleister vereinbart, um die Ergebnisse eines Tests zu überprüfen, tut er dies für eine bestimmte Tageszeit an diesem Datum. Wenn die Politiker in der Zwischenzeit die Regeln ihrer Zeitzone ändern, indem sie die Uhr um eine oder eine halbe Stunde oder eine andere Zeitspanne vor- oder zurückstellen, bleiben Datum und Uhrzeit dieses Arzttermins gleich. Tatsächlich wird der Punkt der Zeitachse des ursprünglichen Termins geändert, nachdem die Politiker die Zeitzone geändert haben, und sich zu einem früheren/späteren Punkt auf der Zeitachse verschieben.

Für solche Termine tun wir nicht Speichern Sie das Datum und die Uhrzeit, wie sie in UTC angezeigt werden. Wir nicht Verwenden Sie den Datenbankspaltentyp TIMESTAMP WITH TIME ZONE .

Bei solchen Terminen speichern wir das Datum mit Uhrzeit ohne Rücksicht auf die Zeitzone. Wir verwenden eine Datenbankspalte vom Typ TIMESTAMP WITHOUT TIME ZONE (beachte WITHOUT statt WITH ). Der übereinstimmende Typ in Java ist LocalDateTime .

LocalDate medicalApptDate = LocalDate.of( 2021 , 1 , 23 ) ;
LocalTime medicalApptTime = LocalTime.of( 21 , 0 ) ;
LocalDateTime medicalApptDateTime = LocalDateTime.of( medicalApptDate , medicalApptTime ) ;

Schreiben Sie das in die Datenbank.

myPreparedStatement.setObject( … , medicalApptDateTime ) ;

Machen Sie sich klar:eine LocalDateTime Objekt nicht einen Moment darstellen, nicht ein bestimmter Punkt auf der Zeitachse. Eine LocalDateTime Objekt repräsentiert einen Bereich möglicher Momente entlang etwa 26-27 Stunden der Zeitachse (der Bereich der Zeitzonen rund um den Globus). Um einer LocalDateTime eine echte Bedeutung zu verleihen , müssen wir eine beabsichtigte Zeitzone zuordnen.

Verwenden Sie für diese beabsichtigte Zeitzone eine zweite Spalte, um die Zonenkennung zu speichern. Beispielsweise die Zeichenfolgen Europe/Rome oder America/New_York . Siehe Liste der Zonennamen .

ZoneId zoneEuropeRome = ZoneId.of( "Europe/Rome" ) ;

Schreiben Sie das als Text in die Datenbank.

myPreparedStatement.setString( … , zoneEuropeRome ) ;

Abruf. Rufen Sie den Zonennamen als Text ab und instanziieren Sie eine ZoneId Objekt.

LocalDateTime medicalApptDateTime = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId medicalApptZone = ZoneId.of( myResultSet.getString( … ) ) ;

Fügen Sie diese beiden Teile zusammen, um einen Moment zu bestimmen, der als ZonedDateTime dargestellt wird Objekt. Tun Sie dies dynamisch, wenn Sie einen Kalender planen müssen. Aber nicht Moment speichern. Wenn die Politiker in Zukunft die Zeitzone(n) neu definieren, muss dann ein anderer Zeitpunkt berechnet werden.

ZonedDateTime medicalApptAsSeenInCareProviderZone = ZonedDateTime.of( medicalApptDateTime , medicalApptZone ) ;

Der Benutzer reist nach New York USA. Sie müssen wissen, wann sie den Gesundheitsdienstleister in Mailand, Italien, gemäß den Uhren an der Wand an ihrem vorübergehenden Standort in New York anrufen müssen. Passen Sie also von einer Zeitzone zur anderen an. Gleicher Moment, andere Uhrzeit.

ZoneId zoneAmericaNewYork = ZoneId.of( "America/New_York" ) ;
ZonedDateTime medicalApptAsSeenInNewYork = medicalApptAsSeenInCareProviderZone.withZoneSameInstant( zoneAmericaNewYork ) ;

tzdata

Beachten Sie, dass Sie die Kopie der Zeitzonendefinitionen auf Ihren Computern aktualisieren müssen, wenn sich die Regeln Ihrer gewünschten Zeitzonen möglicherweise ändern.

Java enthält eine eigene Kopie des tzdata , ebenso wie die Postgres-Datenbank-Engine. Und auch Ihr Host-Betriebssystem. Dieser spezielle hier gezeigte Code erfordert nur Java, um auf dem neuesten Stand zu sein. Wenn Sie Postgres verwenden, um Zeitzonenanpassungen vorzunehmen, werden die tzdata müssen auch aktuell sein. Und für die Protokollierung und dergleichen sollte Ihr Host-Betriebssystem auf dem neuesten Stand gehalten werden. Damit der Benutzer die Uhrzeit richtig einsehen kann, muss auch das Betriebssystem seines Client-Rechners auf dem neuesten Stand sein.

Achtung:Politiker auf der ganzen Welt haben eine Vorliebe dafür gezeigt, ihre Zeitzonen mit überraschender Häufigkeit und oft ohne Vorwarnung zu ändern.

Über java.time

Der java.time Framework ist in Java 8 und höher integriert. Diese Klassen ersetzen das lästige alte Legacy Datumszeitklassen wie java.util.Date , Calendar , &SimpleDateFormat .

Weitere Informationen finden Sie im Oracle-Tutorial . Und durchsuchen Sie Stack Overflow nach vielen Beispielen und Erklärungen. Die Spezifikation ist JSR 310 .

Die Joda-Time Projekt, jetzt im Wartungsmodus , empfiehlt die Migration zu java.time Klassen.

Sie können java.time austauschen Objekte direkt mit Ihrer Datenbank. Verwenden Sie einen JDBC-Treiber kompatibel mit JDBC 4.2 oder später. Keine Notwendigkeit für Strings, keine Notwendigkeit für java.sql.* Klassen. Hibernate 5 und JPA 2.2 unterstützen java.time .

Wo erhalte ich die java.time-Klassen?