Pour l’évènement de la sortie de Java 8, Ippon vous propose plusieurs posts sur les nouveautés du langage. A lire également :- Java 8 – Interfaces fonctionnelles
Avec Java 8 vient une nouvelle API de gestion du temps nommée java.time qui est spécifiée par la JSR 310 et implémentée via le projet threeten.
Il y a ainsi 3 API différentes pour gérer le temps dans le JDK8.
- java.util.Date (depuis le jdk 1.0)
- java.util.Calendar (depuis le jdk 1.1)
- java.time (jdk 1.8)
Situation avant Java 8
Le but de cette nouvelle API est de pallier aux défauts des 2 premières :
- Les années sont stockées sur 2 chiffres à partir de 1900 ce qui conduit à des manipulations de type +1900/-1900 pour utiliser ces valeurs
- Mois commençant à 0 ce qui oblige à faire des +1 pour les convertir en date humainement lisible, et à des -1 pour l’opération inverse
- Objets Date et Calendar muables ce qui oblige à en faire une copie défensive (en faisant un clone()) pour éviter que le code appelant ne les modifie
- Problème de nommage : Date n’est pas une date mais une date et une heure (datetime)
- Nombreux constructeurs et méthodes dépréciés car buggés
- API peu facile à utiliser
- Pas thread-safe : source de bugs de concurrence
- Palette de concepts incomplète : pas de durée, période, intervalle, Heure(heures-minutes-secondes), Date(année-mois-jour)…
java.time est inspirée de la librairie Joda-Time et est en grande partie réalisée par son auteur, Stephen Colebourne. Il a été choisi de réécrire une librairie de gestion du temps from scratch au sein de Java 8 afin de pouvoir corriger les défauts constatés de Joda-Time.
- Mauvaise représentation interne des dates en tant qu’instant. Alors qu’une date&heure peut correspondre à plusieurs instants lors des changements “heure d’été/heure d’hiver” ou ne pas exister (passage de 3h à 2h ou de 2h à 3h du matin) ce qui amène également des calculs plus compliqués
- Trop flexible (chaque classe peut accepter un système calendaire différent du calendrier grégorien) -> utilisation d’un système calendaire global ISO (calendrier grégorien) par défaut dans java.time
- Accepte les null sans erreur pour la plupart de ses méthodes ce qui entraîne des bugs
java.time vise donc à apporter un design plus simple et plus robuste que celui de Joda-Time. Cette API sera également disponible via un backport pour Java 7 dispo sur le central maven. L’API fluide permet de manipuler des objets immuables et thread-safe afin d’éviter les problèmes. Les Classes de gestion du temps sont séparés selon les 2 visions du temps : temps machine et temps humain. Afin d’assurer une transition en douceur, on peut facilement transformer un objet java.util.Date ou java.util.GregorianCalendar depuis et vers cette API.
Temps Machine
Instant
Un point précis dans le temps.
La classe Instant représente le nombre de nanosecondes depuis l’Epoch (1er Janvier 1970). c’est ce qui se rapproche le plus de java.util.Date.
Instant.ofEpochSecond(1395100800); Instant.parse("2014-03-18T00:00:00.000Z");
Duration
Durée entre 2 Instants ou durée en jours/heures/minutes/secondes/millisecondes/nanosecondes.
Duration.ofDays(5); Duration.of(5, ChronoUnit.DAYS); Duration.between(Instant.parse("2011-07-28T00:00:00.000Z"), Instant.parse("2014-03-18T00:00:00.000Z")).getSeconds();
Temps Humain
Fuseau horaire
On peut l’utiliser pour créer des ZonedDateTime et des OffsetDateTime à partir d’un instant via les classes ZoneId et ZoneOffset.
ZonedDateTime et des OffsetDateTime sont équivalent à la classe GregorianCalendar.
ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("Europe/Paris")); OffsetDateTime.ofInstant(Instant.now(), ZoneId.of("GMT+1")); OffsetDateTime.ofInstant(Instant.now(), ZoneOffset.of("+01:00"));
LocalDateTime
Date et Heure (sans notion de fuseau horaire).
LocalDateTime date = LocalDateTime.of(LocalDate.of(2014, Month.MARCH, 18), LocalTime.MIDNIGHT); // formatage date.format(DateTimeFormatter.ISO_DATE); date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); //parsing LocalDateTime.parse("18/03/2014 00:00", DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); // manipulation date.withHour(16).plusWeeks(3); // on positionne à 16h et on ajoute 3 semaines date.with(TemporalAdjusters.firstDayOfNextMonth()); // le premier jour du mois prochain date.with(Temporals.nextWorkingDay()); // TemporalAdjuster de threeten-extra (non inclus dans le jdk)
Dates et heures partielles
Un LocalDateTime incomplet : 18/03 (18 Mars), 12:00 (midi).
LocalDate.of(2014, Month.MARCH, 18); LocalTime.of(12, 0); // égal à LocalTime.NOON DayOfWeek.of(2);// DayOfWeek.TUESDAY Month.of(3); // égal à Month.MARCH MonthDay.of(Month.MARCH, 18); YearMonth.of(2014, Month.MARCH); Year.of(2014);
Période
Représentation “humaine” d’une durée, par exemple 1 mois (qu’il ait 28 ou 31 jours)
Period.ofMonths(3); //période entre la sortie de java 7 et la sortie de java 8 Period.between(LocalDate.of(2011, Month.JULY, 28), LocalDate.of(2014, Month.MARCH, 18)); // 2 ans, 7 mois, 18 jours
Interoperabilité java.time, Date, GregorianCalendar.
Possibilité de transformer facilement un objet depuis et vers cette nouvelle API.
Date date = new Date(); date.toInstant(); Date.from(Instant.now()); // GregorianCalendar gregorianCalendar = new GregorianCalendar(TimeZone.getTimeZone("Europe/Paris")); gregorianCalendar.toZonedDateTime(); gregorianCalendar.toInstant(); GregorianCalendar.from(ZonedDateTime.ofInstant(Instant.now(), ZoneId.of("Europe/Paris")));
Conclusion
Comme on vient de le voir, cette API de gestion des données temporelles permet de corriger les défauts des précédentes APIs tout en étant très facile et agréable à utiliser, notamment grâce à sa syntaxe fluide et ses concepts clairs.
Un certain nombre de possibilités existant avec Joda-Time n’ont pas pu voir le jour au sein de java.time et ont rejoint le projet threeten-extra (dispo sur maven central) que l’on pourra donc inclure en complément dans ses projets.
Il faut également espérer que l’inclusion de cette nouvelle API au sein de Java 8 permette une intégration rapide au sein des nombreux frameworks que nous utilisons tous les jours.