Conversion entre java.time.LocalDateTime et java.util.Date

485

Java 8 dispose d'une toute nouvelle API pour la date et l'heure. L'une des classes les plus utiles de cette API consiste LocalDateTimeà conserver une valeur de date-heure indépendante du fuseau horaire.

Il existe probablement des millions de lignes de code utilisant la classe héritée java.util.Dateà cet effet. En tant que tel, lors de l'interfaçage de l'ancien et du nouveau code, il sera nécessaire de convertir entre les deux. Comme il ne semble pas y avoir de méthode directe pour y parvenir, comment le faire?

Knut Arne Vedaa
la source

Réponses:

706

Réponse courte:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

Explication: (basé sur cette question concernant LocalDate)

Malgré son nom, java.util.Datereprésente un instant sur la ligne du temps, pas une "date". Les données réelles stockées dans l'objet sont un longdécompte des millisecondes depuis le 1970-01-01T00: 00Z (minuit au début de 1970 GMT / UTC).

La classe équivalente à java.util.DateJSR-310 est Instant, il existe donc des méthodes pratiques pour effectuer la conversion de va-et-vient:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

Une java.util.Dateinstance n'a pas de concept de fuseau horaire. Cela peut sembler étrange si vous appelez toString()un java.util.Date, car le toStringest relatif à un fuseau horaire. Cependant, cette méthode utilise en fait le fuseau horaire par défaut de Java à la volée pour fournir la chaîne. Le fuseau horaire ne fait pas partie de l'état réel de java.util.Date.

Un Instantne contient pas non plus d'informations sur le fuseau horaire. Ainsi, pour convertir d'un Instanten une date-heure locale, il est nécessaire de spécifier un fuseau horaire. Il peut s'agir du fuseau par défaut - ZoneId.systemDefault()- ou il peut s'agir d'un fuseau horaire contrôlé par votre application, tel qu'un fuseau horaire issu des préférences de l'utilisateur. LocalDateTimea une méthode d'usine pratique qui prend à la fois l'instant et le fuseau horaire:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

À l'inverse, le LocalDateTimefuseau horaire est spécifié en appelant la atZone(ZoneId)méthode. Le ZonedDateTimepeut ensuite être converti directement en Instant:

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

Notez que la conversion de LocalDateTimeà ZonedDateTimea le potentiel d'introduire un comportement inattendu. En effet, toutes les dates et heures locales n'existent pas en raison de l'heure d'été. En automne / automne, il y a un chevauchement dans la chronologie locale où la même date-heure locale se produit deux fois. Au printemps, il y a un écart, où une heure disparaît. Voir le Javadoc de atZone(ZoneId)pour plus de définition de ce que fera la conversion.

En résumé, si vous faites un aller-retour entre un et java.util.Dateun LocalDateTimeretour, java.util.Datevous pouvez vous retrouver avec un instant différent en raison de l'heure d'été.

Informations supplémentaires: Il existe une autre différence qui affectera les dates très anciennes. java.util.Dateutilise un calendrier qui change au 15 octobre 1582, avec des dates antérieures à celles du calendrier julien au lieu du calendrier grégorien. En revanche, java.time.*utilise le système de calendrier ISO (équivalent au grégorien) pour tous les temps. Dans la plupart des cas d'utilisation, le système de calendrier ISO est ce que vous voulez, mais vous pouvez voir des effets étranges lors de la comparaison des dates avant l'année 1582.

JodaStephen
la source
5
Merci beaucoup pour une explication claire. Surtout pour pourquoi java.util.Datene contient pas de fuseau horaire, mais imprimez-le pendant toString(). La documentation officielle de l' événement ne le dit pas clairement lorsque vous postez.
Cherry
Attention: LocalDateTime.ofInstant(date.toInstant()... ne se comporte pas comme on pourrait s'y attendre naïvement. Par exemple new Date(1111-1900,11-1,11,0,0,0);deviendra en 1111-11-17 23:53:28utilisant cette approche. Jetez un œil à l'implémentation de java.sql.Timestamp#toLocalDateTime()si vous aviez besoin que le résultat soit 1111-11-11 00:00:00dans l'exemple précédent.
chien
2
J'ai ajouté une section sur les dates très anciennes (avant 1582). FWIW, votre correction suggérée pourrait bien être erronée, car 1111-11-11 dans java.util.Date est le même jour dans l'histoire que 1111-11-18 dans java.time en raison des différents systèmes de calendrier (la différence de 6,5 minutes se produit dans de nombreux fuseaux horaires avant 1900)
JodaStephen
2
Il convient également de noter que java.sql.Date#toInstantjette un UnsupportedOperationException. Ne l'utilisez donc pas toInstantdans un RowMapper java.sql.ResultSet#getDate.
LazerBass
132

Voici ce que j'ai trouvé (et comme toutes les énigmes de la date et de l'heure, cela va probablement être réfuté sur la base d'un ajustement étrange du fuseau horaire, du pas de jour et de la lumière du jour: D)

Déclenchement aller-retour: Date<<->>LocalDateTime

Donné: Date date = [some date]

(1) LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

Exemple:

Donné:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1) LocalDateTime<< Instant<< Date:

Créer à Instantpartir de Date:

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

Créer à Datepartir de Instant(pas nécessaire, mais à titre d'illustration):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

Créer à LocalDateTimepartir deInstant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2) Date<< Instant<<LocalDateTime

Créer à Instantpartir de LocalDateTime:

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Créer à Datepartir de Instant:

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

La sortie est:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574
Saint Hill
la source
2
@scottb m'adressez-vous cela ou la personne qui a posé la question? En ce qui concerne mes pensées, eh bien, ce serait bien d'avoir la conversion indiquée explicitement dans l'API jdk 8 - Ils auraient pu au moins le faire. Dans tous les cas, il y a beaucoup de bibliothèques qui seront refactorisées pour inclure les nouvelles fonctionnalités Java-8 et savoir comment le faire est important.
Le Coordinateur
4
@scottb Pourquoi le cas de tierce partie serait-il rare? C'est sacrément commun. Un exemple: JDBC 4 et moins (si tout va bien pas 5).
Raman
5
En règle générale JSR-310, il n'est pas nécessaire de convertir entre les types en utilisant epoch-millis. De meilleures alternatives existent en utilisant des objets, voir ma réponse complète ci-dessous. La réponse ci-dessus n'est également pleinement valable que si vous utilisez un décalage de zone comme UTC - certaines parties de la réponse ne fonctionneront pas pour un fuseau horaire complet comme America / New_York.
JodaStephen
2
Au lieu de Instant.ofEpochMilli(date.getTime())fairedate.toInstant()
chèvre
1
@goat a l' toInstant()air bien, sauf qu'il échoue java.sql.Date, arggggh! Il est donc finalement plus facile à utiliser Instant.ofEpochMilli(date.getTime()).
vadipp
22

Un moyen beaucoup plus pratique si vous êtes sûr d'avoir besoin d'un fuseau horaire par défaut:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
Petrychenko
la source
25
Bien sûr, c'est plus facile, mais je n'aime pas mélanger des choses liées à jdbc avec une gestion simple de la date, du moins à mon humble avis.
Enrico Giurin
9
Je suis désolé, c'est une terrible solution à un problème simple. C'est comme définir 5 comme étant égal au nombre d'anneaux dans le logo olympique.
Madbreaks
7

ce qui suit semble fonctionner lors de la conversion de la nouvelle API LocalDateTime en java.util.date:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

la conversion inverse peut être (espérons-le) réalisée de manière similaire ...

J'espère que cela aide...

étourdi
la source
5

Tout est ici: http://blog.progs.be/542/date-to-java-time

La réponse avec "aller-retour" n'est pas exacte: quand vous le faites

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

si le fuseau horaire de votre système n'est pas UTC / GMT, vous changez l'heure!

joe-mojo
la source
Seulement si vous le faites pendant 1 heure par an, à l'automne, lorsque deux fois depuis LocalDateTime se chevauchent. Le mouvement du printemps vers l'avant ne pose pas de problème. La plupart du temps, les deux directions seront converties correctement.
David
4

Le moyen le plus rapide pour LocalDateTime-> Dateest:

Date.from(ldt.toInstant(ZoneOffset.UTC))

Lukasz Czerwinski
la source
3

Je ne sais pas si c'est le moyen le plus simple ou le meilleur, ou s'il y a des pièges, mais cela fonctionne:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}
Knut Arne Vedaa
la source
3
Il y a certainement un piège allant de LocalDateTimeà Date. Lors des transitions à l'heure d'été, a LocalDateTimepeut être inexistant ou se produire deux fois. Vous devez déterminer ce que vous voulez faire dans chaque cas.
Jon Skeet
1
BTW, GregorianCalendarappartient à une ancienne API maladroite que la nouvelle java.timeAPI vise à remplacer
Vadzim
3

Si vous êtes sur Android et utilisez Threetenbp, vous pouvez utiliser à la DateTimeUtilsplace.

ex:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

vous ne pouvez pas l'utiliser Date.fromcar il n'est pris en charge que sur api 26+

humazed
la source