web-dev-qa-db-fra.com

Java SE 8 TemporalAccessor.from pose des problèmes lorsqu'il est utilisé avec un objet Java.time.Instant

Java.time A une classe Instant qui encapsule une position (ou 'moment') sur la timeline. Bien que je comprenne qu'il s'agit d'une valeur en secondes/nanosecondes qui n'est donc pas directement liée aux fuseaux horaires ou aux décalages horaires, son toString renvoie une date et une heure formatées en date/heure UTC, par exemple 2014-05-13T20: 05 : 08.556Z. anInstant.atZone(zone) et anInstant.atOffset(offset) produisent également une valeur compatible avec le traitement de Instant comme ayant un décalage implicite de fuseau horaire UTC/'zéro'.

Je m'attendais donc à:

  • ZoneOffset.from(anInstant) pour produire un 'zéro' ZoneOffset
  • OffsetDateTime.from(anInstant) pour produire une date/heure avec un décalage "zéro"
  • ZoneId.from(anInstant) (probablement) pour produire un UTC ZoneId
  • ZonedDateTime.from(anInstant) (probablement) pour produire un ZonedDateTime avec un UTC ZoneId

La documentation pour ZonedDateTime.from , telle que je la lis, semble approuver cela.

En fait, ZoneOffset.from(anInstant) échoue avec DateTimeException, et je suppose que pour cette raison OffsetDateTime.from(anInstant) échoue également, tout comme les deux autres.

Est-ce le comportement attendu?

30
user3627702

Réponse courte:

Les concepteurs de JSR-310 ne veulent pas que les gens effectuent des conversions entre le temps machine et le temps humain via statique à partir de () - des méthodes dans des types comme ZoneId, ZoneOffset, OffsetDateTime , ZonedDateTime etc. Ceci est explicitement spécifié si vous étudiez attentivement le javadoc. Utilisez plutôt:

OffsetDateTime#toInstant():Instant
ZonedDateTime#toInstant():Instant
Instant#atOffset(ZoneOffset):OffsetDateTime
Instant#atZone(ZoneId):ZonedDateTime

Le problème avec les méthodes static from () - est que sinon les gens sont capables de faire des conversions entre un Instant et par exemple un LocalDateTime sans penser au fuseau horaire.

Réponse longue:

Que ce soit pour considérer un Instant comme compteur ou comme champ Tuple, la réponse donnée par l'équipe JSR-310 était une séparation stricte entre le temps dit machine et le temps humain. En effet, ils ont l'intention d'avoir une séparation stricte - voir leurs directives . Finalement, ils veulent que Instant soit uniquement interprété comme un compteur de temps machine. Ils ont donc intentionnellement une conception où vous ne pouvez pas demander à un Instant des champs comme l'année, l'heure, etc.

Mais en effet, l'équipe JSR-310 n'est pas du tout cohérente. Ils ont implémenté la méthode Instant.toString() sous la forme d'une vue Tuple de champ comprenant l'année, ..., l'heure, ... et le symbole de décalage Z (pour le fuseau horaire UTC) (note de bas de page: en dehors de JSR-310, il s'agit de assez courant pour avoir un aperçu sur le terrain de ces temps machine - voir par exemple sur Wikipedia ou sur d'autres sites à propos de TAI et UTC ). Une fois que le responsable des spécifications, S. Colebourne, a déclaré dans un commentaire sur un threeten-github-issue :

"Si nous étions vraiment durs, la chaîne toString d'un instant serait simplement le nombre de secondes à partir du 1970-01-01Z. Nous avons choisi de ne pas le faire, et de produire une chaîne toString plus conviviale pour aider les développeurs, mais elle ne change pas le fait fondamental qu'un instant n'est qu'un compte de secondes et ne peut pas être converti en année/mois/jour sans un fuseau horaire quelconque. "

Les gens peuvent aimer cette décision de conception ou non (comme moi), mais la conséquence est que vous ne pouvez pas demander un Instant pour l'année, ..., l'heure, ... et le décalage. Voir aussi la documentation des champs pris en charge:

NANO_OF_SECOND 
MICRO_OF_SECOND 
MILLI_OF_SECOND 
INSTANT_SECONDS 

Ici, il est intéressant de savoir ce qui manque, surtout un champ lié à une zone est manquant. Comme raison, nous entendons souvent l'affirmation selon laquelle des objets comme Instant ou Java.util.Date N'ont pas de fuseau horaire. À mon avis c'est une vue trop simpliste. Bien qu'il soit vrai que ces objets n'ont pas d'état de fuseau horaire en interne (et qu'il n'y a pas non plus besoin d'avoir une telle valeur interne), ces objets DOIVENT être liés au fuseau horaire UTC car c'est la base de chaque calcul de décalage de fuseau horaire et conversion en types locaux . La bonne réponse serait donc: Un Instant est un compteur de machine comptant les secondes et les nanosecondes depuis Epoque UNIX dans le fuseau horaire UTC (par spécification). La dernière partie - la relation avec la zone UTC - n'est pas bien spécifiée par l'équipe JSR-310 mais ils ne peuvent pas le nier. Les concepteurs veulent supprimer l'aspect fuseau horaire de Instant car il semble lié au temps humain. Cependant, ils ne peuvent pas complètement supprimer parce que c'est une partie fondamentale de tout calcul de décalage interne. Donc, votre observation concernant

"Aussi une Instant.atZone(zone) et une Instant.atOffset(offset) produisent toutes deux une valeur qui est cohérente avec le traitement de l'Instant comme ayant un décalage horaire UTC/'zéro' implicite."

est vrai.

Bien qu'il puisse être très intuitif que ZoneOffset.from(anInstant) puisse produire ZoneOffset.UTC, Il lève une exception car sa méthode from () - recherche une inexistante OFFSET_SECONDS-field . Les concepteurs de JSR-310 ont décidé de le faire dans la spécification pour la même raison, à savoir faire croire aux gens qu'un Instant n'a officiellement rien à voir avec le fuseau horaire UTC, c'est-à-dire "n'a pas de fuseau horaire" (mais en interne, ils doit accepter ce fait de base dans tous les calculs internes!).

Pour la même raison, OffsetDateTime.from(anInstant) et ZoneId.from(anInstant) échouent également.

À propos de ZonedDateTime.from(anInstant) nous lire :

"La conversion obtiendra d'abord un ZoneId de l'objet temporel, retombant à un ZoneOffset si nécessaire. Elle essaiera ensuite d'obtenir un Instant, retombant à un LocalDateTime si nécessaire. Le résultat sera soit la combinaison de ZoneId ou ZoneOffset avec Instant ou LocalDateTime. "

Cette conversion échouera donc à nouveau pour les mêmes raisons, car ni ZoneId ni ZoneOffset ne peuvent être obtenus à partir d'un Instant. Le message d'exception se lit comme suit:

"Impossible d'obtenir ZoneId de TemporalAccessor: 1970-01-01T00: 00: 00Z de type Java.time.Instant"

Enfin, nous voyons que toutes les méthodes statiques de () - souffrent de ne pas pouvoir faire une conversion entre le temps humain et le temps machine même si cela semble intuitif. Dans certains cas, une conversion entre disons LocalDate et Instant est discutable. Ce comportement est spécifié, mais je prédis que votre question n'est pas la dernière question de ce type et de nombreux utilisateurs continueront d'être confus.

Le vrai problème de conception à mon avis est que:

a) Il ne devrait pas y avoir de séparation nette entre le temps humain et le temps machine. Les objets temporels comme Instant devraient mieux se comporter comme les deux. Une analogie en mécanique quantique: vous pouvez voir un électron à la fois comme une particule et une onde.

b) Toutes les méthodes statiques de () - sont trop publiques. Il est trop facilement accessible à mon avis et aurait mieux valu être supprimé de l'API publique ou utiliser des arguments plus spécifiques que TemporalAccessor. La faiblesse de ces méthodes est que les utilisateurs peuvent oublier de penser aux fuseaux horaires associés dans de telles conversions car ils démarrent la requête avec un type local. Considérez par exemple: LocalDate.from(anInstant) (dans quel fuseau horaire ???). Cependant, si vous demandez directement à un Instant sa date comme instant.getDate(), personnellement, je considérerais la date dans le fuseau horaire UTC comme une réponse valide car ici la requête commence dans une perspective UTC.

c) En conclusion: je partage absolument avec l'équipe JSR-310 la bonne idée d'éviter les conversions entre types locaux et types globaux comme Instant sans spécifier de fuseau horaire. Je diffère simplement en ce qui concerne la conception de l'API pour empêcher les utilisateurs de faire une telle conversion sans fuseau horaire. Ma façon préférée aurait été de restreindre les méthodes from () - plutôt que de dire que les types globaux ne devraient avoir aucune relation avec les formats d'heure humaine comme la date du calendrier ou l'heure du mur ou le décalage de fuseau horaire UTC.

Quoi qu'il en soit, cette conception (inconséquente) de la séparation entre le temps machine et le temps humain est désormais figée en raison de la préservation de la compatibilité descendante, et tous ceux qui souhaitent utiliser la nouvelle API Java.time doivent vivre avec.

Désolé pour une longue réponse, mais il est assez difficile d'expliquer la conception choisie du JSR-310.

41
Meno Hochschild

La classe Instant n'a pas de fuseau horaire. Il est imprimé comme s'il était dans la zone UTC car il doit être imprimé dans une zone (vous ne voudriez pas qu'il soit imprimé en ticks, n'est-ce pas?), Mais ce n'est pas son fuseau horaire - comme le montre l'exemple suivant:

Instant instant=Instant.now();
System.out.println(instant);//prints 2014-05-14T06:18:48.649Z
System.out.println(instant.atZone(ZoneId.of("UTC")));//prints 2014-05-14T06:18:48.649Z[UTC]

Les signatures de ZoneOffset.from et OffsetDateTime.from accepte tout objet temporel, mais il échoue pour certains types. Java.time ne semble pas avoir d'interface pour les temporels qui ont un fuseau horaire ou un décalage. Une telle interface aurait pu déclarer getOffset et getZone. Puisque nous n'avons pas une telle interface, ces méthodes sont déclarées séparément à plusieurs endroits.

Si vous aviez un ZonedDateTime vous pourriez appeler c'est getOffset. Si vous aviez un OffsetDateTime, vous pourriez aussi appeler son offset - mais ce sont deux méthodes getOffset différentes, car les deux classes ne reçoivent pas cette méthode d'une interface commune. Cela signifie que si vous avez un objet Temporal qui pourrait être l'un ou l'autre, vous devrez tester s'il s'agit d'un instanceof les deux pour obtenir le décalage.

OffsetDateTime.from simplifie le processus en faisant cela pour vous - mais comme il ne peut pas non plus s'appuyer sur une interface commune, il doit accepter n'importe quel objet Temporal et lever une exception pour ceux qui n'ont pas de décalage.

17
Idan Arye

Juste un exemple de conversions w.r.t, je pense que certaines personnes seront en dessous de l'exception

(Java.time.DateTimeException: impossible d'obtenir LocalDateTime de TemporalAccessor: 2014-10-24T18: 22: 09.800Z de type Java.time.Instant)

s'ils essaient -

LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant());

pour résoudre le problème, veuillez passer dans la zone -

LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant().atZone(ZoneId.of("UTC")));

9
rohtakdev