Benoît Courtine



    Navigation
     » Accueil
     » A propos
     » XML Feed

    Le calendrier cinéma avec Java 8

    12 Feb 2017 » java

    Le contexte

    Travaillant pour Médiavision, nous avons plusieurs applications dont les dates et calendriers sont basés sur le calendrier cinéma des sorties de films, dont les semaines débutent le mercredi.

    La numérotation des semaines cinéma est définie de la même façon que pour les semaines civiles de la norme ISO 8601 : la première semaine cinéma de l’année N cinéma débute un mercredi et contient au moins 4 jours dans l’année civile N".

    dans la pratique, la première de l’année cinéma N est la première semaine dont le samedi appartient à l’année civile N. C’est également la première semaine de l’année qui inclut le 4 janvier.
    Example 1. Quelques exemples (cas aux limites)
    • Le mardi 3 janvier 2017, nous sommes en semaine 53 de l’année 2016 dans le calendrier cinéma.

    • Le mercredi 30 décembre 2015, nous sommes au premier jour le la semaine 1 de l’année cinéma 2016.

    Jusqu’ici, nous utilisions pour calculer les semaines cinéma un code maison historique, d’environ 100 lignes (qui utilisait uniquement l’API java.util.Date et java.util.Calendar… sinon ça serait trop facile), pour calculer la semaine cinéma correspondant à une date donnée.

    Java 8 à la rescousse

    Suite à la migration de toute notre base de code Java vers Java 8, je suis retombé sur cette fameuse classe de calcul et me suis demandé si la nouvelle API java.time ne permettrait pas de simplifier ce code…

    Les tests

    Avant tout refactoring, on commence par s’assurer que le code existant est bien couvert par les tests, afin de garantir qu’on ne casse rien dans l’opération.

    Je triche dans le code que je vais présenter ici, en utilisant uniquement les classes de l’API java.time. Dans la pratique, les méthodes refactorées utilisaient en entrée/sortie les classes de l’API java.util.Date, qui étaient converties ultérieurement. Cette partie ne nous intéressant pas ici, j’omets volontairement le code de conversion.
    Fichier CalendrierCinemaTest.java (contenant les exemples cités ci-dessus)
    package org.courtine.cinema;
    
    import org.testng.annotations.Test;
    
    import java.time.Month;
    import java.time.LocalDate;
    
    import static org.assertj.core.api.Assertions.assertThat;
    
    public class CalendrierCinemaTest {
    
      @Test
      public void le_03_janvier_2017_doit_etre_dans_la_semaine_cinema_53_de_2016() {
        LocalDate date = LocalDate.of(2017, Month.JANUARY, 3);
        assertThat(CalendrierCinema.semaineCinema(date)).isEqualTo(53);
        assertThat(CalendrierCinema.anneeCinema(date)).isEqualTo(2016);
      }
    
      @Test
      public void le_30_decembre_2015_doit_etre_dans_la_semaine_cinema_1_de_2016() {
        LocalDate date = LocalDate.of(2015, Month.DECEMBER, 30);
        assertThat(CalendrierCinema.semaineCinema(date)).isEqualTo(1);
        assertThat(CalendrierCinema.anneeCinema(date)).isEqualTo(2016);
      }
    
      // Autres cas de test…
    }

    La classe WeekFields

    Jusqu’à cette problématique, je n’avais jamais eu affaire à cette classe. Son but est de permettre de configurer la définition d’une "semaine", en fournissant :

    • Le jour de départ de la semaine.

    • Le nombre minimal de jours que la première semaine doit contenir dans l’année civile N.

    Ces contraintes correspondent justement à la façon dont nous avons défini "l’année cinéma" ci-dessus. Nous pouvons donc nous en servir pour remplacer l’implémentation maison du calcul. Celui-ci devient alors trivial :

    Fichier CalendrierCinema.java
    package org.courtine.cinema;
    
    import java.time.DayOfWeek;
    import java.time.LocalDate;
    import java.time.temporal.WeekFields;
    
    public class CalendrierCinema {
    
      // La première semaine cinéma de l'année débute un mercredi et contient au moins 4 jours inclus dans l'année.
      public static final WeekFields SEMAINE_CINEMA_WEEK_FIELDS = WeekFields.of(DayOfWeek.WEDNESDAY, 4);
    
      public static int semaineCinema(LocalDate date) {
        return date.get(SEMAINE_CINEMA_WEEK_FIELDS.weekOfWeekBasedYear());
      }
    
      public static int anneeCinema(LocalDate date) {
        return date.get(SEMAINE_CINEMA_WEEK_FIELDS.weekBasedYear());
      }
    }

    Conclusion

    Ce refactoring nous a permis de supprimer 100 lignes de code historique, difficilement maintenable, et de le remplacer par le code trivial ci-dessus. Il nous a également permis d’entamer une migration plus globale vers l’API java.time.

    Rendons tout de même à César ce qui est à Jules : bien que difficilement lisible/maintenable, le code historique avait été éprouvé par plusieurs année d’utilisation. Les tests mis en place ont d’ailleurs prouvé que le nouveau code donnait exactement le même résultat que l’ancien, y compris sur les cas aux limites.