J'ai Java 8 application web Spring qui prendra en charge plusieurs régions. J'ai besoin de créer des événements de calendrier pour un emplacement client. Disons donc que mon serveur Web et Postgres est hébergé dans le fuseau horaire MST (mais je suppose cela peut être n'importe où si nous passons au cloud.) Mais le client est en EST. Donc, en suivant certaines des meilleures pratiques que j'ai lues, je pensais que je stockerais toutes les heures de la date au format UTC. Tous les champs de date et d'heure de la base de données sont déclarés comme TIMESTAMP.
Voici donc comment prendre un LocalDateTime et le convertir en UTC:
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(dto.getStartDateTime(), ZoneOffset.UTC, null);
//appointment startDateTime is a LocalDateTime
appointment.setStartDateTime( startZonedDT.toLocalDateTime() );
Maintenant, par exemple, lorsqu'une recherche de date arrive, je dois convertir la date et l'heure demandées en UTC, obtenir les résultats et convertir le fuseau horaire de l'utilisateur (stocké dans la base de données):
ZoneId userTimeZone = ZoneId.of(timeZone.getID());
ZonedDateTime startZonedDT = ZonedDateTime.ofLocal(appointment.getStartDateTime(), userTimeZone, null);
dto.setStartDateTime( startZonedDT.toLocalDateTime() );
Maintenant, je ne suis pas sûr que ce soit la bonne approche. Je me demande également si, parce que je passe de LocalDateTime à ZonedDateTime et vice versa, je risque de perdre des informations de fuseau horaire.
Voici ce que je vois et cela ne me semble pas correct. Lorsque je reçois le LocalDateTime de l'interface utilisateur, j'obtiens ceci:
2016-04-04T08:00
The ZonedDateTime =
dateTime=2016-04-04T08:00
offset="Z"
zone="Z"
Et puis quand j'attribue cette valeur convertie à mon rendez-vous LocalDateTime je stocke:
2016-04-04T08:00
J'ai l'impression que parce que je stocke dans LocalDateTime, je perds le fuseau horaire que j'ai converti en ZonedDateTime.
Dois-je faire en sorte que mon entité (rendez-vous) utilise ZonedDateTime au lieu de LocalDateTime pour que Postgres ne perde pas ces informations?
---------------- Modifier --------------- -
Après l'excellente réponse de Basils, j'ai réalisé que j'avais le luxe de ne pas me soucier du fuseau horaire des utilisateurs - tous les rendez-vous se rapportent à un emplacement spécifique afin que je puisse stocker toutes les heures en UTC, puis les convertir en fuseau horaire lors de la récupération. J'ai fait le suivi suivant question
Postgres n'a pas de type de données tel que TIMESTAMP
. Postgres a deux types pour la date plus l'heure: TIMESTAMP WITH TIME ZONE
et TIMESTAMP WITHOUT TIME ZONE
. Ces types ont un comportement très différent en ce qui concerne les informations de fuseau horaire.
WITH
utilise toutes les informations de décalage ou de fuseau horaire pour régler la date-heure sur UTC , puis supprime ce décalage ou ce fuseau horaire; Postgres n'enregistre jamais les informations de décalage/zone. WITHOUT
ignore les informations de décalage ou de zone éventuellement présentes. Vous voulez pratiquement toujours le type WITH
, comme expliqué ici par l'expert David E. Wheeler. Le WITHOUT
n'a de sens que lorsque vous avez la vague idée d'une date-heure plutôt que d'un point fixe sur la chronologie. Par exemple, "Noël cette année commence le 2016-12-25T00: 00: 00" serait stocké dans le WITHOUT
tel qu'il s'applique au fuseau horaire any, sans avoir encore été appliqué à un seul fuseau horaire pour obtenir un moment réel sur la chronologie. Si les elfes du Père Noël suivaient l'heure de début pour Eugene Oregon US, alors ils utiliseraient le type WITH
et une entrée qui incluait un décalage ou un fuseau horaire tel que 2016-12-25T00:00:00-08:00
qui est enregistré dans Postgres en tant que 2016-12-25T08:00.00Z
(où Z
signifie zoulou ou UTC).
L'équivalent de Postgres ’TIMESTAMP WITHOUT TIME ZONE
dans Java.time est Java.time.LocalDateTime
. Comme votre intention était de travailler en UTC (une bonne chose), vous devriez pas utiliser LocalDateTime
(une mauvaise chose). Cela peut être le principal point de confusion et de problèmes pour vous. Vous pensez toujours à utiliser LocalDateTime
ou ZonedDateTime
mais vous ne devriez utiliser ni l'un ni l'autre; à la place, vous devez utiliser Instant
(décrit ci-dessous).
Je me demande également si, parce que je passe de LocalDateTime à ZonedDateTime et vice versa, je risque de perdre des informations de fuseau horaire.
En effet, vous l'êtes. Le point entier vers LocalDateTime
est vers perdre info sur le fuseau horaire. Nous utilisons donc rarement cette classe dans la plupart des applications. Encore une fois, l'exemple de Noël. Ou un autre exemple, "Politique de l'entreprise: toutes nos usines du monde entier déjeunent à 12h30". Ce serait LocalTime
, et pour une date particulière, LocalDateTime
. Mais cela n'a aucune signification réelle, pas un point réel sur la timeline, jusqu'à ce que vous appliquiez un fuseau horaire pour obtenir un ZonedDateTime
. Cette pause déjeuner aura lieu à des moments différents de la chronologie dans l'usine de Delhi que dans l'usine de Düsseldorf et à nouveau dans l'usine de Détroit.
Le mot "local" dans LocalDateTime
peut être contre-intuitif car il signifie pas de particulier localité. Lorsque vous lisez "Local" dans un nom de classe, pensez "Pas un instant… pas sur la chronologie… juste une idée floue sur une date-heure un peu-sorta".
Vos serveurs doivent presque toujours être définis sur UTC dans le fuseau horaire de leur système d'exploitation. Mais votre programmation ne devrait jamais dépendre de cette externalité car il est trop facile pour un administrateur système de la changer ou pour toute autre application Java pour changer le fuseau horaire par défaut actuel au sein de la JVM. Spécifiez donc toujours votre fuseau horaire souhaité/prévu (il en va de même pour Locale
, soit dit en passant).
Résultat:
Pendant la journée de travail tout en portant votre casque geek, pensez à UTC. Ce n’est qu’à la fin de la journée, lors du passage à votre laïc, que vous devriez revenir à l’heure locale de votre ville.
Votre logique métier doit se concentrer sur l'UTC. Le stockage de votre base de données, la logique métier, l'échange de données, la sérialisation, la journalisation et votre propre réflexion doivent tous être effectués dans le fuseau horaire UTC (et 24 heures, au fait). Lorsque vous présentez des données aux utilisateurs, n'appliquez ensuite qu'un fuseau horaire particulier. Considérez les dates-heures zonées comme une chose externe, et non comme une partie fonctionnelle des internes de votre application.
Du côté Java côté, utilisez Java.time.Instant
(un instant sur la chronologie en UTC) dans une grande partie de votre logique métier.
Instant now = Instant.now();
Espérons que les pilotes JDBC seront éventuellement mis à jour pour traiter directement les types Java.time comme Instant
. Jusque-là, nous devons utiliser les types Java.sql. L'ancienne classe Java.sql a de nouvelles méthodes de conversion de/vers Java.time.
Java.sql.TimeStamp ts = Java.sql.TimeStamp.valueOf( instant );
Maintenant, passez ça Java.sql.TimeStamp
objet via setTimestamp
sur un PreparedStatement
à enregistrer dans une colonne définie comme TIMESTAMP WITH TIME ZONE
dans Postgres.
Pour aller dans l'autre sens:
Instant instant = ts.toInstant();
C'est donc facile, de passer de Instant
à Java.sql.Timestamp
à TIMESTAMP WITH TIME ZONE
, tous en UTC. Aucun fuseau horaire impliqué. Le fuseau horaire par défaut actuel de votre système d'exploitation serveur, de votre machine virtuelle Java et de vos clients n'est pas pertinent.
Pour présenter à l'utilisateur, appliquez un fuseau horaire. Utilisez noms de fuseau horaire appropriés , jamais les codes à 3-4 lettres tels que EST
ou IST
.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
Vous pouvez vous ajuster dans une zone différente selon vos besoins.
ZonedDateTime zdtKolkata = zdt.withZoneSameInstant( ZoneId.of( "Asia/Kolkata" ) );
Pour revenir à un Instant
, un instant sur la timeline en UTC, vous pouvez extraire du ZonedDateTime
.
Instant instant = zdt.toInstant();
Aucun endroit où nous avons utilisé LocalDateTime
.
Si vous obtenez un élément de données sans décalage par rapport à UTC ou fuseau horaire, tel que 2016-04-04T08:00
, ces données vous sont entièrement inutiles (en supposant que nous ne parlons pas des scénarios de type Noël ou Déjeuner d'entreprise discutés ci-dessus). Une date-heure sans décalage/info de zone est comme un montant monétaire sans indication de devise: 142.70
ou même $142.70
-- inutile. Mais USD 142.70
, ou CAD 142.70
, ou MXN 142.70
… Ceux-ci sont utiles.
Si vous obtenez cela 2016-04-04T08:00
valeur, et vous êtes absolument certain du contexte offset/zone prévu, alors:
LocalDateTime
.OffsetDateTime
, ou (mieux) appliquez un fuseau horaire pour obtenir un ZonedDateTime
.Comme ce 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.
Votre question est vraiment un double de beaucoup d'autres. Ces questions ont été discutées à plusieurs reprises dans d'autres questions et réponses. Je vous invite à rechercher et à étudier Stack Overflow pour en savoir plus sur ce sujet.
Depuis JDBC 4.2, nous pouvons directement échanger des objets Java.time avec la base de données. Pas besoin d'utiliser jamais Java.sql.Timestamp
encore une fois, ni ses classes apparentées.
Stockage, en utilisant OffsetDateTime
comme défini dans la spécification JDBC.
myPreparedStatement.setObject( … , instant.atOffset( ZoneOffset.UTC ) ) ; // The JDBC spec requires support for `OffsetDateTime`.
… Ou utilisez éventuellement Instant
directement, si votre pilote JDBC le prend en charge.
myPreparedStatement.setObject( … , instant ) ; // Your JDBC driver may or may not support `Instant` directly, as it is not required by the JDBC spec.
Récupération.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent l'ancien hérité) gênant classes date-heure telles que Java.util.Date
, Calendar
, & SimpleDateFormat
.
Le projet Joda-Time , maintenant en mode maintenance , conseille la migration vers les classes Java.time .
Pour en savoir plus, consultez le Tutoriel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 31 .
Vous pouvez échanger des objets Java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou version ultérieure. Pas besoin de chaînes, pas besoin de Java.sql.*
Des classes.
Où obtenir les classes Java.time?
Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d'essai pour de futurs ajouts possibles à Java.time. Vous pouvez trouver ici des classes utiles telles que Interval
, YearWeek
, YearQuarter
, et plus .