Points Clés
- Comment utiliser l’API Date & Time ?
- Pourquoi utiliser l’API Date & Time ?
- Le clean code et un meilleur design pour la gestion des données temporelles
Traiter des dates ou toute opération qui traite du temps comme unité de mesure est toujours un grand défi. Ce n'est pas différent dans le monde Java. Cependant, depuis Java 8, la nouvelle API Date & Time apporte plusieurs améliorations.
Cet article vous en apprendra davantage sur la complexité de l'utilisation des dates et sur l'utilité de l'API Date & Time de Java.
Après tout, pourquoi la gestion des données temporelles est-elle si complexe ?
Les API de gestion de données temporelles sont considérées comme très complexes, mais quelle est la raison de cette complexité ? Contrairement aux autres unités de mesure, les dates ont une complexité physique, culturelle, politique, historique et religieuse.
La partie physique du temps est déjà délicate. Dans sa théorie de la relativité, Albert Einstein démontre que la dilatation du temps est possible. De cette façon, les voyages dans le temps du film Interstellar sont prouvés, et il est possible d'en savoir plus sur ces théories dans le livre "The Science of Interstellar".
En plus de la complexité naturelle, il existe plusieurs interventions politiques, telles que :
-
Inclusion de deux mois dans le calendrier en l'honneur de deux empereurs : Jules César et Gaius Otávio Augustos. En plus de laisser février avec beaucoup moins de jours, les mois des empereurs ne peuvent pas avoir moins de trente et un jours. La conséquence en est que nous perdons la sémantique : novembre représente le onzième mois du latin novem, neuf. Décembre, du latin decem, dix, représente le douzième mois
-
L'heure d'été était une intervention politique qui a été modifiée pendant une certaine période pour économiser l'énergie. Il peut être activé sur une année ou non. Au Brésil, par exemple, cette fonctionnalité n'a pas été utilisée ces dernières années
-
Le 30 décembre 2011 n'existait pas aux Samoa. C'est vrai, une journée entière n'est pas vécue dans ce pays
Il existe de nombreux impacts externes sur le temps, et cela ne se produit pas avec d'autres unités de mesure ; par exemple, il n'y a pas d'unité de mesure de distance modifiée tout au long de l'année, comme un « mètre d'été » ou une unité qui cesse d'exister par décret.
Comprendre les concepts temporelles
Il y a déjà une certaine complexité au sein des API de données. Pourtant, vous pouvez également voir un autre problème chez la plupart des gens qui travaillent avec le développement : ils ne comprennent pas l'unité de temps. Connaître ces concepts est déjà complexe ; cela les rend encore plus difficiles à comprendre.
Par exemple, si quelqu'un demande : « Quel jour sommes-nous aujourd'hui ? Ou quelle heure est-il ? la réponse sera cela dépend, comme les grands problèmes de l'histoire de l'informatique ou de l'architecture logicielle. Après tout, connaître le jour ou l'heure dépend de l'endroit auquel je fais référence : ils peuvent maintenant changer si je fais référence à Lisbonne ou à San Francisco.
L'objectif ici est de parler des notions minimales pour faciliter la compréhension des devs :
- LMT, Local Mean Time : L'heure locale d'une ville. C'était une tentative d'uniformiser les horaires. Cependant, comme chaque ville avait son programme, cela générait beaucoup de confusion.
-
GMT, Greenwich Mean Time : Une solution créée par les compagnies ferroviaires britanniques pour faciliter les horaires de voyage. L'heure des pays a été normalisée sur la base de l'heure de Londres, qui a été fixée à l'observatoire le plus fiable, l'Observatoire royal de Greenwich. Et les heures sont liées à GMT ; par exemple, San Francisco a sept heures de retard sur Greenwich, GMT-7.
-
UTC, Temps Universel Coordonné en français ou Coordinated Universal Time en anglais : C'est comme le nouveau GMT créé en 1972. L'unité de mesure au sein de l'UTC est le décalage, dont vous pouvez avoir une compensation de -3:00, soit trois heures de l'UTC. Nous pouvons considérer UTC comme GMT 2.0.
-
Fuseau horaire : la norme GMT/UTC a été mise en œuvre à des heures différentes pour chaque pays ; il est crucial d'avoir l'historique avant la mise en œuvre et l'heure d'été. Un fuseau horaire a précisément cet objectif : conserver cette histoire. Par exemple, la ville de São Paulo a utilisé UTC, -3:06:28, et ce n'est qu'en 1914 qu'elle a utilisé UTC -03:00.
-
Heure d'été: son fonctionnement consiste à avancer d'une heure et à en ajouter une au décalage. Cela varie selon les pays et l'hémisphère dans lequel il se trouve. Par exemple, dans le nord, cela se produit entre mars et octobre. Dans le sud, c'est entre novembre et février.
Apprendre à connaître l'API Java Date & Time
Après avoir contextualisé le besoin et le concept derrière le temps, parlons de l'API Date & Time de Java 8 pas si nouvelle. Il y a deux tentatives avec la gestion de données temporelles dans le monde Java. Cependant, ma recommandation concernant les nouveaux systèmes et les nouvelles fonctionnalités est de n'importer aucune API qui ne se trouve pas dans le package "java.time".
Les Entités
La première étape consiste à connaître les types d'entités prises en charge par l'API ; cet article ne couvrira pas tous les détails, mais vous pouvez rechercher une lecture plus détaillée.
Un point important : l'API apporte des représentations qui couvrent le jour, le mois, le jour de la semaine, l'année, le fuseau horaire et le décalage, en plus de les combiner tous !
DayOfWeek dayOfWeek = DayOfWeek.MONDAY;
Month month = Month.JANUARY;
MonthDay monthDay = MonthDay.now();
YearMonth yearMonth = YearMonth.now();
Year year = Year.now();
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now();
Clock clock = Clock.systemUTC();
Instant instant = Instant.now();
TimeZone timeZone = TimeZone.getDefault();
System.out.println("DayOfWeek: " + dayOfWeek);
System.out.println("month: " + month);
System.out.println("MonthDay: " + monthDay);
System.out.println("YearMonth: " + yearMonth);
System.out.println("Year: " + year);
System.out.println("LocalDate: " + localDate);
System.out.println("LocalTime: " + localTime);
System.out.println("LocalDateTime: " + localDateTime);
System.out.println("OffsetDateTime: " + offsetDateTime);
System.out.println("ZonedDateTime: " + zonedDateTime);
System.out.println("Clock: " + clock.getZone());
System.out.println("Instant: " + instant);
System.out.println("TimeZone: " + timeZone.getDisplayName());
Nous soulignons ici les possibilités que nous pouvons utiliser avec des types ou des entités représentant le temps dans l'API au lieu d'en utiliser un seul, comme cela se faisait auparavant. Par exemple :
-
YearMonth pour travailler avec la date d'expiration d'une carte de crédit
-
Year pour traiter de l'année de publication d'un livre
En plus d'apporter une meilleure sémantique au code, ces variables apportent de la clarté et simplifient la façon dont vous effectuez les validations. Il est basé sur le classique « When Make a Type » de Martin Fowler.
Assertions.assertThrows(DateTimeException.class, () -> Year.of(1_000_000_000));
Il est également possible de faire des combinaisons entre types pour créer une instance finale ; par exemple, nous commençons par une année pour construire une date.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
Donc, dans un premier temps, je vous suggère d'explorer et d'en savoir un peu plus sur les types pris en charge par l'API. Étant donné que l'API Date & Time a des validations chronométrées, ces API sont beaucoup plus efficaces pour représenter le temps que des types plus génériques comme int, long ou String.
Les opérations avec des données temporelles
En plus d'apporter plus de sémantique et de validation au traitement des données temporelles, l'API apporte également des opérations fascinantes qui visent à faciliter votre journée et à vous faire gagner du temps. Désolé pour le jeu de mots tentant.
Les opérations les plus élémentaires consistent à ajouter ou à supprimer des périodes, telles que des mois ou des jours, en utilisant des méthodes avec les suffixes « plus » ou « minus », respectivement.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate yesterday = myBirthday.minusDays(1);
LocalDate oneYear = myBirthday.plusDays(365);
L'interface TemporalAdjuster permet des ajustements personnalisés et certaines opérations plus complexes, et à partir de là, il est possible de créer de nombreuses procédures personnalisables et réutilisables. Par convention, une classe utilitaire apporte plusieurs fonctionnalités ; par exemple, en vérifiant le lundi suivant à partir de la date actuelle, je me réfère à la classe TemporalAdjusters.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate newYear = myBirthday.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = myBirthday.with(TemporalAdjusters.lastDayOfMonth());
LocalDate nextMonday = myBirthday.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
Il est également possible de faire des comparaisons entre les dates, rien de bien nouveau par rapport aux anciennes API. Cependant, il est toujours important de souligner que cela existe.
LocalDate myBirthday = Year.of(1988).atMonth(Month.JANUARY).atDay(9);
LocalDate now = LocalDate.now();
Assertions.assertTrue(now.isAfter(myBirthday));
Assertions.assertFalse(now.isBefore(myBirthday));
Les opérations primaires sont bénéfiques. Cependant, il est possible d'aller plus loin avec les classes Period et ChronoUnit. Avec elles, vous pouvez voir la différence entre une période spécifique.
LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.now().plusDays(1);
Assertions.assertEquals(1, ChronoUnit.DAYS.between(today, tomorrow));
LocalDate dateA = LocalDate.of(2012, Month.APRIL, 7);
LocalDate dateB = LocalDate.of(2015, Month.DECEMBER,5);
Period period = Period.between(dateA, dateB);
Assertions.assertEquals(3, period.getYears());
Assertions.assertEquals(7, period.getMonths());
Assertions.assertEquals(28, period.getDays());
Assertions.assertEquals(43, period.toTotalMonths());
La dernière étape de comparaison et de vérification est la classe TemporalQuery, qui vise à extraire des informations temporelles.
TemporalQuery<Boolean> weekend = temporal -> {
int dayOfWeek = temporal.get(ChronoField.DAY_OF_WEEK);
return dayOfWeek == DayOfWeek.SATURDAY.getValue()
|| dayOfWeek == DayOfWeek.SUNDAY.getValue();
};
LocalDate date = LocalDate.of(2018, 5, 4);
LocalDateTime sunday = LocalDateTime.of(2018, 5, 6, 17, 0);
Assertions.assertFalse(date.query(weekend));
Assertions.assertTrue(sunday.query(weekend));
L'API propose aussi un support des fuseaux horaires.
ZoneId saoPaulo = ZoneId.of("America/Sao_Paulo");
ZonedDateTime adenNow = ZonedDateTime.now(saoPaulo);
ZoneOffset offset = adenNow.getOffset();
Assertions.assertEquals(saoPaulo, adenNow.getZone());
Assertions.assertEquals("-03:00", offset.getId());
Il est possible de faire des comparaisons de différents fuseaux horaires, par exemple en comparant une heure au Brésil et une autre au Portugal, qui le 3 mai sont à quatre heures d'intervalle.
ZoneId saoPaulo = ZoneId.of("America/Sao_Paulo");
ZoneId portugal = ZoneId.of("Portugal");
LocalDateTime timeSP = Year.of(2021).atMonth(Month.MAY).atDay(3)
.atTime(12,0,0);
LocalDateTime timeLisbon = Year.of(2021).atMonth(Month.MAY).atDay(3)
.atTime(16,0,0);
ZonedDateTime zoneSaoPaulo = ZonedDateTime.of(timeSP, saoPaulo);
ZonedDateTime zoneLisbon = ZonedDateTime.of(timeLisbon, portugal);
Assertions.assertTrue(zoneSaoPaulo.isEqual(zoneLisbon));
La classe Clock permet d'accéder à un instant dans le temps en utilisant la meilleure horloge système disponible et d'être utilisé comme fournisseur d’un point dans le temps qui peut être remplacé efficacement à des fins de tests.
La date et l'heure actuelles dépendent du fuseau horaire et, pour les applications globales, un fournisseur d’un point dans le temps est nécessaire pour garantir que la date et l'heure sont créées avec le fuseau horaire correct.
Clock clock = Clock.systemDefaultZone();
Instant instant = clock.instant();
System.out.println(instant);
Clock baseClock = Clock.systemDefaultZone();
Clock clock = Clock.offset(baseClock, Duration.ofHours(72));
Il est important de souligner que ce que nous avons mentionné n'est qu'un aperçu de l'API, et il vaut la peine de lire la documentation du projet, qui est extrêmement riche et détaillée.
Conclusion
Bien qu'il ne s'agisse pas d'une nouvelle API, l'API Date & Time de Java est encore peu utilisée et peu explorée pour plusieurs raisons.
Soit en raison de la complexité de la gestion du temps, le nombre de documents disponibles est encore infime par rapport aux API héritées ou n'étant pas une API bien connue.
Il est essentiel de mentionner que cette API apporte de nombreux avantages, de sa clarté avec les méthodes à plusieurs fonctionnalités de fonctionnement qui valent la peine d'être connues. Cet article vise à faire revivre ce sujet et à vous mettre le pied à l’étrier pour que vous puissiez explorer davantage l'API.