Blog

Migration de Joda-Time vers java.time

Cameron Gregor
Publié le 12 novembre 2021

La migration d'un code (lire : d'un code hérité) n'est pas une partie de plaisir. Il faut énormément de planification et d'efforts pour le faire passer. Bien que ce ne soit pas le travail le plus excitant ou le plus motivant pour les développeurs, il faut de la détermination et une bonne expérience pour migrer le code existant vers les nouvelles versions de la bibliothèque. Joda-Time vers java.time est l'une de ces migrations qui nécessite une planification et une exécution méticuleuses.

Si votre projet Java a débuté avant Java SE 8, et qu'il utilise le traitement de la date et de l'heure, il a probablement utilisé Joda-Time - une excellente bibliothèque et un standard de facto pour gérer les fonctions de date et d'heure avant SE 8. Si votre projet utilise toujours Joda-Time mais que vous souhaitez migrer vers java.time, lisez ce qui suit.

La version de Java SE 8 comprenait une nouvelle API standard améliorée pour la date et l'heure, communément appelée java.time (JSR-310). Le projet Joda-Time recommande désormais de migrer vers java.time (JSR-310).

Bien que java.time (JSR-310) ait été fortement inspiré par Joda-Time, il n'est pas rétrocompatible et les concepts et terminologies ont changé. C'est pourquoi la migration de Joda-Time vers java.time nécessite une attention particulière sur chaque ligne de code que vous modifiez. Cela peut prendre beaucoup de temps et vous ferait presque regretter qu'il n'y ait pas un moyen plus facile et automatisé de migrer.

Il existe une meilleure façon de migrer et nous l'avons créée en utilisant Sensei - un plugin IntelliJ pour effectuer automatiquement des transformations de code selon les recettes (règles) que vous avez définies. Passez votre temps à définir des recettes réutilisables, plutôt que d'effectuer des tâches de migration répétitives. L'automatisation ne transformera pas seulement votre code Joda-Time hérité, mais aidera également les équipes à suivre les directives directement dans l'IDE lorsqu'elles écrivent du nouveau code.

Pour vous aider à prendre de l'avance, nous avons créé un livre de recettes public Sensei Standardization on java.time (JSR-310) qui comprend des recettes pour migrer de Joda-Time à java.time d'une manière moins douloureuse. Il s'agit d'un ensemble croissant de recettes que nous continuerons à développer afin d'ajouter plus de couverture avec plus de recettes.

Voici un exemple de migration qui pourrait vous aider à voir comment Sensei facilite la migration du code existant.

Vidéo de la définition de l'heure de la date java.time zoned

De la migration manuelle répétitive à la transformation automatisée du code

Regardons un exemple de création d'une nouvelle DateTime qui démontre quelques pièges cachés lors de la migration d'une simple ligne de code de Joda-Time vers java.time. Nous examinerons ensuite l'une des recettes Sensei de notre livre de recettes Standardization on java.time (JSR-310) et montrerons comment elle capture toutes ces informations, de sorte que cette même migration puisse être réutilisée à l'infini par n'importe quel développeur.

Dans cet exemple, nous construisons un Joda-Time DateTime à partir de 7 arguments int représentant les valeurs des champs DateTime.

Comment migrer vers un équivalent java.time ?

La javadoc de Joda-Time pour ce constructeur dit :

Construit une instance à partir des valeurs du champ date en utilisant ISOChronology dans le fuseau horaire par défaut.

Au départ, on pourrait penser qu'il existe une classe DateTime dans java.time, mais ce n'est pas le cas. Si vous tapez sur Google "migrate from joda time to java time", vous trouverez très probablement le billet de Stephen Colebourne intitulé Converting from Joda-Time to java.time.

Cela vous donne un bon début, et nous oriente vers l'utilisation de java.time.ZonedDateTime ou de java.time.OffsetDateTime. Voici notre première question : lequel dois-je utiliser ? Probablement ZonedDateTime d'après les commentaires de Stephen.

En consultant la javadoc de ZonedDateTime, nous ne voyons aucun constructeur. En revenant à l'article de Stephen, nous lisons plus loin :

‍Construction. Joda-Time a un constructeur qui accepte un Objet et effectue la conversion de type. java.time n'a que des méthodes de fabrique, donc la conversion est un problème d'utilisateur, bien qu'une méthode parse() soit fournie pour les chaînes de caractères.

Il doit donc exister une méthode d'usine statique. En cherchant dans les méthodes statiques, nous en trouvons une qui semble assez proche, mais qui n'est pas exactement la même.

Il a 7 paramètres int comme notre constructeur DateTime original de Joda-Time, cependant si vous ne faites pas attention vous manquerez un détail important. Le 7ème paramètre ne représente plus des millisecondes, mais des nanosecondes, car java.time a gagné en précision par rapport à Joda-Time et mesure les instants à la nano-seconde. Un détail important que vous auriez pu facilement manquer. En outre, cette méthode attend un ZoneId, ce qui vous amène à vous demander pourquoi vous n'en aviez pas besoin auparavant et pourquoi vous en avez besoin maintenant.

En se rappelant la javadoc de notre constructeur original qui mentionnait qu'il utiliserait le fuseau horaire par défaut, peut-être existe-t-il un moyen d'obtenir le ZoneId par défaut ?

La javadoc de ZoneId ne mentionne aucun constructeur, mais en regardant les méthodes statiques, nous voyons que nous pouvons utiliser systemDefault()

Maintenant que nous avons résolu le problème du ZoneId, que devons-nous faire pour convertir les millisecondes en nanoSecondes ? Peut-être pouvons-nous utiliser java.util.concurrent.TimeUnit pour effectuer la conversion.

Cette méthode renvoie un long, et notre méthode attend un int, donc nous avons maintenant un problème de conversion à résoudre. Nous pourrions peut-être essayer quelque chose de simple. Une multiplication ?

Cela fonctionnera, mais ne semble pas tout à fait à sa place. Si vous ne l'avez pas encore remarqué, nous avons consacré beaucoup de temps et d'efforts à la migration d'une seule ligne de code. Mais comme vous pouvez l'imaginer, nous avons beaucoup de modifications de ce type à faire à la main et cela ne s'améliore pas.

Cependant, si nous nous penchons un peu plus sur l'API java.time, nous pouvons découvrir une solution qui semble un peu plus fluide.

Bien que ZonedDateTime n'ait pas de moyen évident de définir les millisecondes, il est possible de le faire à l'aide de la méthode with(TemporalField field, long newValue), en utilisant ChronoField.MILLI_OF_SECOND comme champ temporel.

Et la documentation Java mentionne qu'elle effectuera la conversion en nanosecondes pour nous :

Lorsque ce champ est utilisé pour définir une valeur, il doit se comporter de la même manière que la valeur NANO_OF_SECOND multipliée par 1 000 000.

Nous pouvons donc simplement spécifier 0 pour nos nanosecondes dans la méthode factory, puis utiliser la méthode with pour créer un ZonedDateTime qui possède toutes les valeurs d'origine ainsi que les millisecondes.

En regardant notre résultat final, on a l'impression que nous n'avons changé qu'une seule ligne de code, ce qui ne montre pas vraiment l'effort qui a été consacré à la recherche d'une seule migration !

Créez une recette pour migrer plus rapidement et plus facilement

Sensei nous permet de partager ces informations durement acquises avec d'autres développeurs. La création d'une recette reprenant toutes ces exigences permettra aux utilisateurs de Sensei d'effectuer cette migration d'un simple clic de souris.

Une recette Sensei se compose de trois parties principales :

  • Métadonnées
  • Recherche
  • Réparations disponibles

Examinons une recette Sensei (qui peut également être considérée comme une recette YAML) qui nous aidera à migrer cet appel vers son équivalent java.time.

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond) ;

Section des métadonnées

La section des métadonnées contient des informations sur la recette et son utilisation.

Section Recherche

La section de recherche d'une recette Sensei spécifie les éléments de code auxquels cette recette doit s'appliquer.

search :
instanceCreation :
args :
1 :
type : int
2 :
type : int
3 :
type : int
4 :
type : int
5 :
type : int
6 :
type : int
7 :
type : int
argCount : 7
type : org.joda.time.DateTime

Dans cette section de recherche, nous voyons que nous sommes :

  • Recherche d'une instanceCreation, c'est-à-dire d'une utilisation d'un Constructeur. Remarque : il existe de nombreuses autres cibles de recherche
  • Le constructeur doit avoir 7 arguments, ce qui est spécifié par la propriété argCount
  • les arguments 1 à 7 doivent être de type int
  • Nous recherchons des constructeurs de type org.joda.time.DateTime

Section des correctifs disponibles

La section availableFixes peut spécifier un ou plusieurs correctifs qui peuvent être appliqués à l'élément de code correspondant. Chaque correctif peut avoir plusieurs actions, et dans notre cas nous avons un seul correctif, qui effectue 2 actions.

  • Le nom de la correction est indiqué à l'utilisateur dans le menu "Corrections rapides" et décrit ce qui se passera si l'utilisateur applique cette correction rapide.
  • La liste des actions indique les actions qui seront effectuées par cette correction rapide.
  • L'action de réécriture réécrit l'élément de code à l'aide d'un modèle Mustache. Elle peut utiliser des variables et des fonctions de remplacement de chaînes.
  • L'action modifyAssignedVariable vérifie si ce constructeur est utilisé pour assigner une valeur à une variable. Si c'est le cas, cette action modifiera la variable pour qu'elle soit déclarée comme étant du type spécifié par le type

Utiliser la recette pour transformer le code

Une fois notre recette écrite et activée, il analyse notre code et met en évidence les segments auxquels il peut s'appliquer.

Dans la capture d'écran ci-dessous, nous pouvons voir que le constructeur cible a été marqué par Sensei. En survolant le constructeur marqué, nous voyons la recette shortDescription et l'option Quickfix Migrate to java.time.ZonedDateTime.

Migrer la nouvelle date et l'heure

Après avoir sélectionné la solution rapide Migrate to java.time.ZonedDateTime, le code est transformé selon les actions que nous avons spécifiées dans la recette.

Zonage Date Heure Avec Année Mois Jour Heure

Une migration unique et des pratiques de codage uniformes dans toutes les équipes - avec Sensei

Nous pouvons voir dans notre exemple ci-dessus que la migration d'une seule ligne de code peut impliquer des connaissances durement acquises. Sensei peut transformer ces connaissances en recettes exploitables ou en livres de cuisine qui peuvent être partagés au sein des équipes. Vous pouvez planifier un sprint de migration unique ou adopter l'approche consistant à effectuer des transformations instantanées incrémentales vers java.time au fur et à mesure que vous rencontrez du code Joda-Time. Vous pouvez activer/désactiver les recettes afin d'effectuer des migrations par étapes logiques et même étendre ou réduire la portée des fichiers analysés par Sensei - la flexibilité qui rend les migrations de code moins pénibles.

La migration des bibliothèques n'est qu'un exemple des nombreuses façons dont Sensei peut être utilisé pour standardiser vos projets. Vous pouvez toujours être à l'affût des anti-modèles ou de certaines transformations de code manuelles que vous rencontrez fréquemment dans les demandes d'extraction ou lorsque vous codez vous-même. Si vous disposez d'un ensemble de directives de codage qui sont souvent ignorées par les développeurs, vous pouvez les convertir en recettes, ce qui permettra aux développeurs d'appliquer les transformations de code approuvées en toute confiance.

Si vous avez des questions, n'hésitez pas à nous contacter ! Rejoignez-nous sur Slack à l'adresse suivante sensei-scw.slack.com

Voir la ressource
Voir la ressource

Migrer Joda-Time vers java.time de manière pratique

Vous souhaitez en savoir plus ?

Cameron est développeur de logiciels senior à Secure Code Warrior. Il a plus de 15 ans d'expérience dans la fourniture de logiciels. Il est passionné par la productivité des développeurs et contribue activement aux logiciels libres.

Secure Code Warrior est là pour vous aider à sécuriser le code tout au long du cycle de vie du développement logiciel et à créer une culture dans laquelle la cybersécurité est une priorité. Que vous soyez responsable AppSec, développeur, CISO ou toute autre personne impliquée dans la sécurité, nous pouvons aider votre organisation à réduire les risques associés à un code non sécurisé.

Réservez une démonstration
Partager sur :
Auteur
Cameron Gregor
Publié le 12 novembre 2021

Cameron est développeur de logiciels senior à Secure Code Warrior. Il a plus de 15 ans d'expérience dans la fourniture de logiciels. Il est passionné par la productivité des développeurs et contribue activement aux logiciels libres.

Partager sur :

La migration d'un code (lire : d'un code hérité) n'est pas une partie de plaisir. Il faut énormément de planification et d'efforts pour le faire passer. Bien que ce ne soit pas le travail le plus excitant ou le plus motivant pour les développeurs, il faut de la détermination et une bonne expérience pour migrer le code existant vers les nouvelles versions de la bibliothèque. Joda-Time vers java.time est l'une de ces migrations qui nécessite une planification et une exécution méticuleuses.

Si votre projet Java a débuté avant Java SE 8, et qu'il utilise le traitement de la date et de l'heure, il a probablement utilisé Joda-Time - une excellente bibliothèque et un standard de facto pour gérer les fonctions de date et d'heure avant SE 8. Si votre projet utilise toujours Joda-Time mais que vous souhaitez migrer vers java.time, lisez ce qui suit.

La version de Java SE 8 comprenait une nouvelle API standard améliorée pour la date et l'heure, communément appelée java.time (JSR-310). Le projet Joda-Time recommande désormais de migrer vers java.time (JSR-310).

Bien que java.time (JSR-310) ait été fortement inspiré par Joda-Time, il n'est pas rétrocompatible et les concepts et terminologies ont changé. C'est pourquoi la migration de Joda-Time vers java.time nécessite une attention particulière sur chaque ligne de code que vous modifiez. Cela peut prendre beaucoup de temps et vous ferait presque regretter qu'il n'y ait pas un moyen plus facile et automatisé de migrer.

Il existe une meilleure façon de migrer et nous l'avons créée en utilisant Sensei - un plugin IntelliJ pour effectuer automatiquement des transformations de code selon les recettes (règles) que vous avez définies. Passez votre temps à définir des recettes réutilisables, plutôt que d'effectuer des tâches de migration répétitives. L'automatisation ne transformera pas seulement votre code Joda-Time hérité, mais aidera également les équipes à suivre les directives directement dans l'IDE lorsqu'elles écrivent du nouveau code.

Pour vous aider à prendre de l'avance, nous avons créé un livre de recettes public Sensei Standardization on java.time (JSR-310) qui comprend des recettes pour migrer de Joda-Time à java.time d'une manière moins douloureuse. Il s'agit d'un ensemble croissant de recettes que nous continuerons à développer afin d'ajouter plus de couverture avec plus de recettes.

Voici un exemple de migration qui pourrait vous aider à voir comment Sensei facilite la migration du code existant.

Vidéo de la définition de l'heure de la date java.time zoned

De la migration manuelle répétitive à la transformation automatisée du code

Regardons un exemple de création d'une nouvelle DateTime qui démontre quelques pièges cachés lors de la migration d'une simple ligne de code de Joda-Time vers java.time. Nous examinerons ensuite l'une des recettes Sensei de notre livre de recettes Standardization on java.time (JSR-310) et montrerons comment elle capture toutes ces informations, de sorte que cette même migration puisse être réutilisée à l'infini par n'importe quel développeur.

Dans cet exemple, nous construisons un Joda-Time DateTime à partir de 7 arguments int représentant les valeurs des champs DateTime.

Comment migrer vers un équivalent java.time ?

La javadoc de Joda-Time pour ce constructeur dit :

Construit une instance à partir des valeurs du champ date en utilisant ISOChronology dans le fuseau horaire par défaut.

Au départ, on pourrait penser qu'il existe une classe DateTime dans java.time, mais ce n'est pas le cas. Si vous tapez sur Google "migrate from joda time to java time", vous trouverez très probablement le billet de Stephen Colebourne intitulé Converting from Joda-Time to java.time.

Cela vous donne un bon début, et nous oriente vers l'utilisation de java.time.ZonedDateTime ou de java.time.OffsetDateTime. Voici notre première question : lequel dois-je utiliser ? Probablement ZonedDateTime d'après les commentaires de Stephen.

En consultant la javadoc de ZonedDateTime, nous ne voyons aucun constructeur. En revenant à l'article de Stephen, nous lisons plus loin :

‍Construction. Joda-Time a un constructeur qui accepte un Objet et effectue la conversion de type. java.time n'a que des méthodes de fabrique, donc la conversion est un problème d'utilisateur, bien qu'une méthode parse() soit fournie pour les chaînes de caractères.

Il doit donc exister une méthode d'usine statique. En cherchant dans les méthodes statiques, nous en trouvons une qui semble assez proche, mais qui n'est pas exactement la même.

Il a 7 paramètres int comme notre constructeur DateTime original de Joda-Time, cependant si vous ne faites pas attention vous manquerez un détail important. Le 7ème paramètre ne représente plus des millisecondes, mais des nanosecondes, car java.time a gagné en précision par rapport à Joda-Time et mesure les instants à la nano-seconde. Un détail important que vous auriez pu facilement manquer. En outre, cette méthode attend un ZoneId, ce qui vous amène à vous demander pourquoi vous n'en aviez pas besoin auparavant et pourquoi vous en avez besoin maintenant.

En se rappelant la javadoc de notre constructeur original qui mentionnait qu'il utiliserait le fuseau horaire par défaut, peut-être existe-t-il un moyen d'obtenir le ZoneId par défaut ?

La javadoc de ZoneId ne mentionne aucun constructeur, mais en regardant les méthodes statiques, nous voyons que nous pouvons utiliser systemDefault()

Maintenant que nous avons résolu le problème du ZoneId, que devons-nous faire pour convertir les millisecondes en nanoSecondes ? Peut-être pouvons-nous utiliser java.util.concurrent.TimeUnit pour effectuer la conversion.

Cette méthode renvoie un long, et notre méthode attend un int, donc nous avons maintenant un problème de conversion à résoudre. Nous pourrions peut-être essayer quelque chose de simple. Une multiplication ?

Cela fonctionnera, mais ne semble pas tout à fait à sa place. Si vous ne l'avez pas encore remarqué, nous avons consacré beaucoup de temps et d'efforts à la migration d'une seule ligne de code. Mais comme vous pouvez l'imaginer, nous avons beaucoup de modifications de ce type à faire à la main et cela ne s'améliore pas.

Cependant, si nous nous penchons un peu plus sur l'API java.time, nous pouvons découvrir une solution qui semble un peu plus fluide.

Bien que ZonedDateTime n'ait pas de moyen évident de définir les millisecondes, il est possible de le faire à l'aide de la méthode with(TemporalField field, long newValue), en utilisant ChronoField.MILLI_OF_SECOND comme champ temporel.

Et la documentation Java mentionne qu'elle effectuera la conversion en nanosecondes pour nous :

Lorsque ce champ est utilisé pour définir une valeur, il doit se comporter de la même manière que la valeur NANO_OF_SECOND multipliée par 1 000 000.

Nous pouvons donc simplement spécifier 0 pour nos nanosecondes dans la méthode factory, puis utiliser la méthode with pour créer un ZonedDateTime qui possède toutes les valeurs d'origine ainsi que les millisecondes.

En regardant notre résultat final, on a l'impression que nous n'avons changé qu'une seule ligne de code, ce qui ne montre pas vraiment l'effort qui a été consacré à la recherche d'une seule migration !

Créez une recette pour migrer plus rapidement et plus facilement

Sensei nous permet de partager ces informations durement acquises avec d'autres développeurs. La création d'une recette reprenant toutes ces exigences permettra aux utilisateurs de Sensei d'effectuer cette migration d'un simple clic de souris.

Une recette Sensei se compose de trois parties principales :

  • Métadonnées
  • Recherche
  • Réparations disponibles

Examinons une recette Sensei (qui peut également être considérée comme une recette YAML) qui nous aidera à migrer cet appel vers son équivalent java.time.

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond) ;

Section des métadonnées

La section des métadonnées contient des informations sur la recette et son utilisation.

Section Recherche

La section de recherche d'une recette Sensei spécifie les éléments de code auxquels cette recette doit s'appliquer.

search :
instanceCreation :
args :
1 :
type : int
2 :
type : int
3 :
type : int
4 :
type : int
5 :
type : int
6 :
type : int
7 :
type : int
argCount : 7
type : org.joda.time.DateTime

Dans cette section de recherche, nous voyons que nous sommes :

  • Recherche d'une instanceCreation, c'est-à-dire d'une utilisation d'un Constructeur. Remarque : il existe de nombreuses autres cibles de recherche
  • Le constructeur doit avoir 7 arguments, ce qui est spécifié par la propriété argCount
  • les arguments 1 à 7 doivent être de type int
  • Nous recherchons des constructeurs de type org.joda.time.DateTime

Section des correctifs disponibles

La section availableFixes peut spécifier un ou plusieurs correctifs qui peuvent être appliqués à l'élément de code correspondant. Chaque correctif peut avoir plusieurs actions, et dans notre cas nous avons un seul correctif, qui effectue 2 actions.

  • Le nom de la correction est indiqué à l'utilisateur dans le menu "Corrections rapides" et décrit ce qui se passera si l'utilisateur applique cette correction rapide.
  • La liste des actions indique les actions qui seront effectuées par cette correction rapide.
  • L'action de réécriture réécrit l'élément de code à l'aide d'un modèle Mustache. Elle peut utiliser des variables et des fonctions de remplacement de chaînes.
  • L'action modifyAssignedVariable vérifie si ce constructeur est utilisé pour assigner une valeur à une variable. Si c'est le cas, cette action modifiera la variable pour qu'elle soit déclarée comme étant du type spécifié par le type

Utiliser la recette pour transformer le code

Une fois notre recette écrite et activée, il analyse notre code et met en évidence les segments auxquels il peut s'appliquer.

Dans la capture d'écran ci-dessous, nous pouvons voir que le constructeur cible a été marqué par Sensei. En survolant le constructeur marqué, nous voyons la recette shortDescription et l'option Quickfix Migrate to java.time.ZonedDateTime.

Migrer la nouvelle date et l'heure

Après avoir sélectionné la solution rapide Migrate to java.time.ZonedDateTime, le code est transformé selon les actions que nous avons spécifiées dans la recette.

Zonage Date Heure Avec Année Mois Jour Heure

Une migration unique et des pratiques de codage uniformes dans toutes les équipes - avec Sensei

Nous pouvons voir dans notre exemple ci-dessus que la migration d'une seule ligne de code peut impliquer des connaissances durement acquises. Sensei peut transformer ces connaissances en recettes exploitables ou en livres de cuisine qui peuvent être partagés au sein des équipes. Vous pouvez planifier un sprint de migration unique ou adopter l'approche consistant à effectuer des transformations instantanées incrémentales vers java.time au fur et à mesure que vous rencontrez du code Joda-Time. Vous pouvez activer/désactiver les recettes afin d'effectuer des migrations par étapes logiques et même étendre ou réduire la portée des fichiers analysés par Sensei - la flexibilité qui rend les migrations de code moins pénibles.

La migration des bibliothèques n'est qu'un exemple des nombreuses façons dont Sensei peut être utilisé pour standardiser vos projets. Vous pouvez toujours être à l'affût des anti-modèles ou de certaines transformations de code manuelles que vous rencontrez fréquemment dans les demandes d'extraction ou lorsque vous codez vous-même. Si vous disposez d'un ensemble de directives de codage qui sont souvent ignorées par les développeurs, vous pouvez les convertir en recettes, ce qui permettra aux développeurs d'appliquer les transformations de code approuvées en toute confiance.

Si vous avez des questions, n'hésitez pas à nous contacter ! Rejoignez-nous sur Slack à l'adresse suivante sensei-scw.slack.com

Voir la ressource
Voir la ressource

Remplissez le formulaire ci-dessous pour télécharger le rapport

Nous aimerions que vous nous autorisiez à vous envoyer des informations sur nos produits et/ou sur des sujets liés au codage sécurisé. Nous traiterons toujours vos données personnelles avec le plus grand soin et ne les vendrons jamais à d'autres entreprises à des fins de marketing.

Soumettre
Pour soumettre le formulaire, veuillez activer les cookies "Analytics". N'hésitez pas à les désactiver à nouveau une fois que vous aurez terminé.

La migration d'un code (lire : d'un code hérité) n'est pas une partie de plaisir. Il faut énormément de planification et d'efforts pour le faire passer. Bien que ce ne soit pas le travail le plus excitant ou le plus motivant pour les développeurs, il faut de la détermination et une bonne expérience pour migrer le code existant vers les nouvelles versions de la bibliothèque. Joda-Time vers java.time est l'une de ces migrations qui nécessite une planification et une exécution méticuleuses.

Si votre projet Java a débuté avant Java SE 8, et qu'il utilise le traitement de la date et de l'heure, il a probablement utilisé Joda-Time - une excellente bibliothèque et un standard de facto pour gérer les fonctions de date et d'heure avant SE 8. Si votre projet utilise toujours Joda-Time mais que vous souhaitez migrer vers java.time, lisez ce qui suit.

La version de Java SE 8 comprenait une nouvelle API standard améliorée pour la date et l'heure, communément appelée java.time (JSR-310). Le projet Joda-Time recommande désormais de migrer vers java.time (JSR-310).

Bien que java.time (JSR-310) ait été fortement inspiré par Joda-Time, il n'est pas rétrocompatible et les concepts et terminologies ont changé. C'est pourquoi la migration de Joda-Time vers java.time nécessite une attention particulière sur chaque ligne de code que vous modifiez. Cela peut prendre beaucoup de temps et vous ferait presque regretter qu'il n'y ait pas un moyen plus facile et automatisé de migrer.

Il existe une meilleure façon de migrer et nous l'avons créée en utilisant Sensei - un plugin IntelliJ pour effectuer automatiquement des transformations de code selon les recettes (règles) que vous avez définies. Passez votre temps à définir des recettes réutilisables, plutôt que d'effectuer des tâches de migration répétitives. L'automatisation ne transformera pas seulement votre code Joda-Time hérité, mais aidera également les équipes à suivre les directives directement dans l'IDE lorsqu'elles écrivent du nouveau code.

Pour vous aider à prendre de l'avance, nous avons créé un livre de recettes public Sensei Standardization on java.time (JSR-310) qui comprend des recettes pour migrer de Joda-Time à java.time d'une manière moins douloureuse. Il s'agit d'un ensemble croissant de recettes que nous continuerons à développer afin d'ajouter plus de couverture avec plus de recettes.

Voici un exemple de migration qui pourrait vous aider à voir comment Sensei facilite la migration du code existant.

Vidéo de la définition de l'heure de la date java.time zoned

De la migration manuelle répétitive à la transformation automatisée du code

Regardons un exemple de création d'une nouvelle DateTime qui démontre quelques pièges cachés lors de la migration d'une simple ligne de code de Joda-Time vers java.time. Nous examinerons ensuite l'une des recettes Sensei de notre livre de recettes Standardization on java.time (JSR-310) et montrerons comment elle capture toutes ces informations, de sorte que cette même migration puisse être réutilisée à l'infini par n'importe quel développeur.

Dans cet exemple, nous construisons un Joda-Time DateTime à partir de 7 arguments int représentant les valeurs des champs DateTime.

Comment migrer vers un équivalent java.time ?

La javadoc de Joda-Time pour ce constructeur dit :

Construit une instance à partir des valeurs du champ date en utilisant ISOChronology dans le fuseau horaire par défaut.

Au départ, on pourrait penser qu'il existe une classe DateTime dans java.time, mais ce n'est pas le cas. Si vous tapez sur Google "migrate from joda time to java time", vous trouverez très probablement le billet de Stephen Colebourne intitulé Converting from Joda-Time to java.time.

Cela vous donne un bon début, et nous oriente vers l'utilisation de java.time.ZonedDateTime ou de java.time.OffsetDateTime. Voici notre première question : lequel dois-je utiliser ? Probablement ZonedDateTime d'après les commentaires de Stephen.

En consultant la javadoc de ZonedDateTime, nous ne voyons aucun constructeur. En revenant à l'article de Stephen, nous lisons plus loin :

‍Construction. Joda-Time a un constructeur qui accepte un Objet et effectue la conversion de type. java.time n'a que des méthodes de fabrique, donc la conversion est un problème d'utilisateur, bien qu'une méthode parse() soit fournie pour les chaînes de caractères.

Il doit donc exister une méthode d'usine statique. En cherchant dans les méthodes statiques, nous en trouvons une qui semble assez proche, mais qui n'est pas exactement la même.

Il a 7 paramètres int comme notre constructeur DateTime original de Joda-Time, cependant si vous ne faites pas attention vous manquerez un détail important. Le 7ème paramètre ne représente plus des millisecondes, mais des nanosecondes, car java.time a gagné en précision par rapport à Joda-Time et mesure les instants à la nano-seconde. Un détail important que vous auriez pu facilement manquer. En outre, cette méthode attend un ZoneId, ce qui vous amène à vous demander pourquoi vous n'en aviez pas besoin auparavant et pourquoi vous en avez besoin maintenant.

En se rappelant la javadoc de notre constructeur original qui mentionnait qu'il utiliserait le fuseau horaire par défaut, peut-être existe-t-il un moyen d'obtenir le ZoneId par défaut ?

La javadoc de ZoneId ne mentionne aucun constructeur, mais en regardant les méthodes statiques, nous voyons que nous pouvons utiliser systemDefault()

Maintenant que nous avons résolu le problème du ZoneId, que devons-nous faire pour convertir les millisecondes en nanoSecondes ? Peut-être pouvons-nous utiliser java.util.concurrent.TimeUnit pour effectuer la conversion.

Cette méthode renvoie un long, et notre méthode attend un int, donc nous avons maintenant un problème de conversion à résoudre. Nous pourrions peut-être essayer quelque chose de simple. Une multiplication ?

Cela fonctionnera, mais ne semble pas tout à fait à sa place. Si vous ne l'avez pas encore remarqué, nous avons consacré beaucoup de temps et d'efforts à la migration d'une seule ligne de code. Mais comme vous pouvez l'imaginer, nous avons beaucoup de modifications de ce type à faire à la main et cela ne s'améliore pas.

Cependant, si nous nous penchons un peu plus sur l'API java.time, nous pouvons découvrir une solution qui semble un peu plus fluide.

Bien que ZonedDateTime n'ait pas de moyen évident de définir les millisecondes, il est possible de le faire à l'aide de la méthode with(TemporalField field, long newValue), en utilisant ChronoField.MILLI_OF_SECOND comme champ temporel.

Et la documentation Java mentionne qu'elle effectuera la conversion en nanosecondes pour nous :

Lorsque ce champ est utilisé pour définir une valeur, il doit se comporter de la même manière que la valeur NANO_OF_SECOND multipliée par 1 000 000.

Nous pouvons donc simplement spécifier 0 pour nos nanosecondes dans la méthode factory, puis utiliser la méthode with pour créer un ZonedDateTime qui possède toutes les valeurs d'origine ainsi que les millisecondes.

En regardant notre résultat final, on a l'impression que nous n'avons changé qu'une seule ligne de code, ce qui ne montre pas vraiment l'effort qui a été consacré à la recherche d'une seule migration !

Créez une recette pour migrer plus rapidement et plus facilement

Sensei nous permet de partager ces informations durement acquises avec d'autres développeurs. La création d'une recette reprenant toutes ces exigences permettra aux utilisateurs de Sensei d'effectuer cette migration d'un simple clic de souris.

Une recette Sensei se compose de trois parties principales :

  • Métadonnées
  • Recherche
  • Réparations disponibles

Examinons une recette Sensei (qui peut également être considérée comme une recette YAML) qui nous aidera à migrer cet appel vers son équivalent java.time.

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond) ;

Section des métadonnées

La section des métadonnées contient des informations sur la recette et son utilisation.

Section Recherche

La section de recherche d'une recette Sensei spécifie les éléments de code auxquels cette recette doit s'appliquer.

search :
instanceCreation :
args :
1 :
type : int
2 :
type : int
3 :
type : int
4 :
type : int
5 :
type : int
6 :
type : int
7 :
type : int
argCount : 7
type : org.joda.time.DateTime

Dans cette section de recherche, nous voyons que nous sommes :

  • Recherche d'une instanceCreation, c'est-à-dire d'une utilisation d'un Constructeur. Remarque : il existe de nombreuses autres cibles de recherche
  • Le constructeur doit avoir 7 arguments, ce qui est spécifié par la propriété argCount
  • les arguments 1 à 7 doivent être de type int
  • Nous recherchons des constructeurs de type org.joda.time.DateTime

Section des correctifs disponibles

La section availableFixes peut spécifier un ou plusieurs correctifs qui peuvent être appliqués à l'élément de code correspondant. Chaque correctif peut avoir plusieurs actions, et dans notre cas nous avons un seul correctif, qui effectue 2 actions.

  • Le nom de la correction est indiqué à l'utilisateur dans le menu "Corrections rapides" et décrit ce qui se passera si l'utilisateur applique cette correction rapide.
  • La liste des actions indique les actions qui seront effectuées par cette correction rapide.
  • L'action de réécriture réécrit l'élément de code à l'aide d'un modèle Mustache. Elle peut utiliser des variables et des fonctions de remplacement de chaînes.
  • L'action modifyAssignedVariable vérifie si ce constructeur est utilisé pour assigner une valeur à une variable. Si c'est le cas, cette action modifiera la variable pour qu'elle soit déclarée comme étant du type spécifié par le type

Utiliser la recette pour transformer le code

Une fois notre recette écrite et activée, il analyse notre code et met en évidence les segments auxquels il peut s'appliquer.

Dans la capture d'écran ci-dessous, nous pouvons voir que le constructeur cible a été marqué par Sensei. En survolant le constructeur marqué, nous voyons la recette shortDescription et l'option Quickfix Migrate to java.time.ZonedDateTime.

Migrer la nouvelle date et l'heure

Après avoir sélectionné la solution rapide Migrate to java.time.ZonedDateTime, le code est transformé selon les actions que nous avons spécifiées dans la recette.

Zonage Date Heure Avec Année Mois Jour Heure

Une migration unique et des pratiques de codage uniformes dans toutes les équipes - avec Sensei

Nous pouvons voir dans notre exemple ci-dessus que la migration d'une seule ligne de code peut impliquer des connaissances durement acquises. Sensei peut transformer ces connaissances en recettes exploitables ou en livres de cuisine qui peuvent être partagés au sein des équipes. Vous pouvez planifier un sprint de migration unique ou adopter l'approche consistant à effectuer des transformations instantanées incrémentales vers java.time au fur et à mesure que vous rencontrez du code Joda-Time. Vous pouvez activer/désactiver les recettes afin d'effectuer des migrations par étapes logiques et même étendre ou réduire la portée des fichiers analysés par Sensei - la flexibilité qui rend les migrations de code moins pénibles.

La migration des bibliothèques n'est qu'un exemple des nombreuses façons dont Sensei peut être utilisé pour standardiser vos projets. Vous pouvez toujours être à l'affût des anti-modèles ou de certaines transformations de code manuelles que vous rencontrez fréquemment dans les demandes d'extraction ou lorsque vous codez vous-même. Si vous disposez d'un ensemble de directives de codage qui sont souvent ignorées par les développeurs, vous pouvez les convertir en recettes, ce qui permettra aux développeurs d'appliquer les transformations de code approuvées en toute confiance.

Si vous avez des questions, n'hésitez pas à nous contacter ! Rejoignez-nous sur Slack à l'adresse suivante sensei-scw.slack.com

Accès aux ressources

Cliquez sur le lien ci-dessous et téléchargez le PDF de cette ressource.

Secure Code Warrior est là pour vous aider à sécuriser le code tout au long du cycle de vie du développement logiciel et à créer une culture dans laquelle la cybersécurité est une priorité. Que vous soyez responsable AppSec, développeur, CISO ou toute autre personne impliquée dans la sécurité, nous pouvons aider votre organisation à réduire les risques associés à un code non sécurisé.

Voir le rapportRéservez une démonstration
Télécharger le PDF
Voir la ressource
Partager sur :
Vous souhaitez en savoir plus ?

Partager sur :
Auteur
Cameron Gregor
Publié le 12 novembre 2021

Cameron est développeur de logiciels senior à Secure Code Warrior. Il a plus de 15 ans d'expérience dans la fourniture de logiciels. Il est passionné par la productivité des développeurs et contribue activement aux logiciels libres.

Partager sur :

La migration d'un code (lire : d'un code hérité) n'est pas une partie de plaisir. Il faut énormément de planification et d'efforts pour le faire passer. Bien que ce ne soit pas le travail le plus excitant ou le plus motivant pour les développeurs, il faut de la détermination et une bonne expérience pour migrer le code existant vers les nouvelles versions de la bibliothèque. Joda-Time vers java.time est l'une de ces migrations qui nécessite une planification et une exécution méticuleuses.

Si votre projet Java a débuté avant Java SE 8, et qu'il utilise le traitement de la date et de l'heure, il a probablement utilisé Joda-Time - une excellente bibliothèque et un standard de facto pour gérer les fonctions de date et d'heure avant SE 8. Si votre projet utilise toujours Joda-Time mais que vous souhaitez migrer vers java.time, lisez ce qui suit.

La version de Java SE 8 comprenait une nouvelle API standard améliorée pour la date et l'heure, communément appelée java.time (JSR-310). Le projet Joda-Time recommande désormais de migrer vers java.time (JSR-310).

Bien que java.time (JSR-310) ait été fortement inspiré par Joda-Time, il n'est pas rétrocompatible et les concepts et terminologies ont changé. C'est pourquoi la migration de Joda-Time vers java.time nécessite une attention particulière sur chaque ligne de code que vous modifiez. Cela peut prendre beaucoup de temps et vous ferait presque regretter qu'il n'y ait pas un moyen plus facile et automatisé de migrer.

Il existe une meilleure façon de migrer et nous l'avons créée en utilisant Sensei - un plugin IntelliJ pour effectuer automatiquement des transformations de code selon les recettes (règles) que vous avez définies. Passez votre temps à définir des recettes réutilisables, plutôt que d'effectuer des tâches de migration répétitives. L'automatisation ne transformera pas seulement votre code Joda-Time hérité, mais aidera également les équipes à suivre les directives directement dans l'IDE lorsqu'elles écrivent du nouveau code.

Pour vous aider à prendre de l'avance, nous avons créé un livre de recettes public Sensei Standardization on java.time (JSR-310) qui comprend des recettes pour migrer de Joda-Time à java.time d'une manière moins douloureuse. Il s'agit d'un ensemble croissant de recettes que nous continuerons à développer afin d'ajouter plus de couverture avec plus de recettes.

Voici un exemple de migration qui pourrait vous aider à voir comment Sensei facilite la migration du code existant.

Vidéo de la définition de l'heure de la date java.time zoned

De la migration manuelle répétitive à la transformation automatisée du code

Regardons un exemple de création d'une nouvelle DateTime qui démontre quelques pièges cachés lors de la migration d'une simple ligne de code de Joda-Time vers java.time. Nous examinerons ensuite l'une des recettes Sensei de notre livre de recettes Standardization on java.time (JSR-310) et montrerons comment elle capture toutes ces informations, de sorte que cette même migration puisse être réutilisée à l'infini par n'importe quel développeur.

Dans cet exemple, nous construisons un Joda-Time DateTime à partir de 7 arguments int représentant les valeurs des champs DateTime.

Comment migrer vers un équivalent java.time ?

La javadoc de Joda-Time pour ce constructeur dit :

Construit une instance à partir des valeurs du champ date en utilisant ISOChronology dans le fuseau horaire par défaut.

Au départ, on pourrait penser qu'il existe une classe DateTime dans java.time, mais ce n'est pas le cas. Si vous tapez sur Google "migrate from joda time to java time", vous trouverez très probablement le billet de Stephen Colebourne intitulé Converting from Joda-Time to java.time.

Cela vous donne un bon début, et nous oriente vers l'utilisation de java.time.ZonedDateTime ou de java.time.OffsetDateTime. Voici notre première question : lequel dois-je utiliser ? Probablement ZonedDateTime d'après les commentaires de Stephen.

En consultant la javadoc de ZonedDateTime, nous ne voyons aucun constructeur. En revenant à l'article de Stephen, nous lisons plus loin :

‍Construction. Joda-Time a un constructeur qui accepte un Objet et effectue la conversion de type. java.time n'a que des méthodes de fabrique, donc la conversion est un problème d'utilisateur, bien qu'une méthode parse() soit fournie pour les chaînes de caractères.

Il doit donc exister une méthode d'usine statique. En cherchant dans les méthodes statiques, nous en trouvons une qui semble assez proche, mais qui n'est pas exactement la même.

Il a 7 paramètres int comme notre constructeur DateTime original de Joda-Time, cependant si vous ne faites pas attention vous manquerez un détail important. Le 7ème paramètre ne représente plus des millisecondes, mais des nanosecondes, car java.time a gagné en précision par rapport à Joda-Time et mesure les instants à la nano-seconde. Un détail important que vous auriez pu facilement manquer. En outre, cette méthode attend un ZoneId, ce qui vous amène à vous demander pourquoi vous n'en aviez pas besoin auparavant et pourquoi vous en avez besoin maintenant.

En se rappelant la javadoc de notre constructeur original qui mentionnait qu'il utiliserait le fuseau horaire par défaut, peut-être existe-t-il un moyen d'obtenir le ZoneId par défaut ?

La javadoc de ZoneId ne mentionne aucun constructeur, mais en regardant les méthodes statiques, nous voyons que nous pouvons utiliser systemDefault()

Maintenant que nous avons résolu le problème du ZoneId, que devons-nous faire pour convertir les millisecondes en nanoSecondes ? Peut-être pouvons-nous utiliser java.util.concurrent.TimeUnit pour effectuer la conversion.

Cette méthode renvoie un long, et notre méthode attend un int, donc nous avons maintenant un problème de conversion à résoudre. Nous pourrions peut-être essayer quelque chose de simple. Une multiplication ?

Cela fonctionnera, mais ne semble pas tout à fait à sa place. Si vous ne l'avez pas encore remarqué, nous avons consacré beaucoup de temps et d'efforts à la migration d'une seule ligne de code. Mais comme vous pouvez l'imaginer, nous avons beaucoup de modifications de ce type à faire à la main et cela ne s'améliore pas.

Cependant, si nous nous penchons un peu plus sur l'API java.time, nous pouvons découvrir une solution qui semble un peu plus fluide.

Bien que ZonedDateTime n'ait pas de moyen évident de définir les millisecondes, il est possible de le faire à l'aide de la méthode with(TemporalField field, long newValue), en utilisant ChronoField.MILLI_OF_SECOND comme champ temporel.

Et la documentation Java mentionne qu'elle effectuera la conversion en nanosecondes pour nous :

Lorsque ce champ est utilisé pour définir une valeur, il doit se comporter de la même manière que la valeur NANO_OF_SECOND multipliée par 1 000 000.

Nous pouvons donc simplement spécifier 0 pour nos nanosecondes dans la méthode factory, puis utiliser la méthode with pour créer un ZonedDateTime qui possède toutes les valeurs d'origine ainsi que les millisecondes.

En regardant notre résultat final, on a l'impression que nous n'avons changé qu'une seule ligne de code, ce qui ne montre pas vraiment l'effort qui a été consacré à la recherche d'une seule migration !

Créez une recette pour migrer plus rapidement et plus facilement

Sensei nous permet de partager ces informations durement acquises avec d'autres développeurs. La création d'une recette reprenant toutes ces exigences permettra aux utilisateurs de Sensei d'effectuer cette migration d'un simple clic de souris.

Une recette Sensei se compose de trois parties principales :

  • Métadonnées
  • Recherche
  • Réparations disponibles

Examinons une recette Sensei (qui peut également être considérée comme une recette YAML) qui nous aidera à migrer cet appel vers son équivalent java.time.

DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond) ;

Section des métadonnées

La section des métadonnées contient des informations sur la recette et son utilisation.

Section Recherche

La section de recherche d'une recette Sensei spécifie les éléments de code auxquels cette recette doit s'appliquer.

search :
instanceCreation :
args :
1 :
type : int
2 :
type : int
3 :
type : int
4 :
type : int
5 :
type : int
6 :
type : int
7 :
type : int
argCount : 7
type : org.joda.time.DateTime

Dans cette section de recherche, nous voyons que nous sommes :

  • Recherche d'une instanceCreation, c'est-à-dire d'une utilisation d'un Constructeur. Remarque : il existe de nombreuses autres cibles de recherche
  • Le constructeur doit avoir 7 arguments, ce qui est spécifié par la propriété argCount
  • les arguments 1 à 7 doivent être de type int
  • Nous recherchons des constructeurs de type org.joda.time.DateTime

Section des correctifs disponibles

La section availableFixes peut spécifier un ou plusieurs correctifs qui peuvent être appliqués à l'élément de code correspondant. Chaque correctif peut avoir plusieurs actions, et dans notre cas nous avons un seul correctif, qui effectue 2 actions.

  • Le nom de la correction est indiqué à l'utilisateur dans le menu "Corrections rapides" et décrit ce qui se passera si l'utilisateur applique cette correction rapide.
  • La liste des actions indique les actions qui seront effectuées par cette correction rapide.
  • L'action de réécriture réécrit l'élément de code à l'aide d'un modèle Mustache. Elle peut utiliser des variables et des fonctions de remplacement de chaînes.
  • L'action modifyAssignedVariable vérifie si ce constructeur est utilisé pour assigner une valeur à une variable. Si c'est le cas, cette action modifiera la variable pour qu'elle soit déclarée comme étant du type spécifié par le type

Utiliser la recette pour transformer le code

Une fois notre recette écrite et activée, il analyse notre code et met en évidence les segments auxquels il peut s'appliquer.

Dans la capture d'écran ci-dessous, nous pouvons voir que le constructeur cible a été marqué par Sensei. En survolant le constructeur marqué, nous voyons la recette shortDescription et l'option Quickfix Migrate to java.time.ZonedDateTime.

Migrer la nouvelle date et l'heure

Après avoir sélectionné la solution rapide Migrate to java.time.ZonedDateTime, le code est transformé selon les actions que nous avons spécifiées dans la recette.

Zonage Date Heure Avec Année Mois Jour Heure

Une migration unique et des pratiques de codage uniformes dans toutes les équipes - avec Sensei

Nous pouvons voir dans notre exemple ci-dessus que la migration d'une seule ligne de code peut impliquer des connaissances durement acquises. Sensei peut transformer ces connaissances en recettes exploitables ou en livres de cuisine qui peuvent être partagés au sein des équipes. Vous pouvez planifier un sprint de migration unique ou adopter l'approche consistant à effectuer des transformations instantanées incrémentales vers java.time au fur et à mesure que vous rencontrez du code Joda-Time. Vous pouvez activer/désactiver les recettes afin d'effectuer des migrations par étapes logiques et même étendre ou réduire la portée des fichiers analysés par Sensei - la flexibilité qui rend les migrations de code moins pénibles.

La migration des bibliothèques n'est qu'un exemple des nombreuses façons dont Sensei peut être utilisé pour standardiser vos projets. Vous pouvez toujours être à l'affût des anti-modèles ou de certaines transformations de code manuelles que vous rencontrez fréquemment dans les demandes d'extraction ou lorsque vous codez vous-même. Si vous disposez d'un ensemble de directives de codage qui sont souvent ignorées par les développeurs, vous pouvez les convertir en recettes, ce qui permettra aux développeurs d'appliquer les transformations de code approuvées en toute confiance.

Si vous avez des questions, n'hésitez pas à nous contacter ! Rejoignez-nous sur Slack à l'adresse suivante sensei-scw.slack.com

Table des matières

Télécharger le PDF
Voir la ressource
Vous souhaitez en savoir plus ?

Cameron est développeur de logiciels senior à Secure Code Warrior. Il a plus de 15 ans d'expérience dans la fourniture de logiciels. Il est passionné par la productivité des développeurs et contribue activement aux logiciels libres.

Secure Code Warrior est là pour vous aider à sécuriser le code tout au long du cycle de vie du développement logiciel et à créer une culture dans laquelle la cybersécurité est une priorité. Que vous soyez responsable AppSec, développeur, CISO ou toute autre personne impliquée dans la sécurité, nous pouvons aider votre organisation à réduire les risques associés à un code non sécurisé.

Réservez une démonstrationTélécharger
Partager sur :
Centre de ressources

Ressources pour vous aider à démarrer

Plus d'articles
Centre de ressources

Ressources pour vous aider à démarrer

Plus d'articles