Etape 2 – Dates et heures

1 Introduction

Le but de cette étape est de définir des classes permettant de représenter des dates et des heures. Ces classes sont indispensables à la modélisation des horaires de transports publics.

Il faut noter que Java fournit déjà des classes permettant de représenter ces concepts, à savoir les classes Date et GregorianCalendar du paquetage java.util. Autant que possible, ces classes ne seront pas utilisées dans ce projet, pour deux raisons. Premièrement, elles sont terriblement mal conçues, au point que la future version 8 de Java inclut une toute nouvelle bibliothèque de gestion de date et d'heure. Deuxièmement, le développement de telles classes constitue pour vous un excellent exercice.

1.1 Dates

Une date, qui est toujours spécifiée dans le cadre d'un calendrier, permet d'identifier un jour. Encore aujourd'hui, plusieurs calendriers sont en usage dans le monde, mais le calendrier grégorien est de loin le plus utilisé. Il s'agit du calendrier en vigueur dans la quasi-totalité du monde actuel, le seul nécessaire à ce projet et donc le seul considéré dans ce qui suit.

Le calendrier grégorien identifie une date par trois valeurs : une année, un mois et un jour dans le mois. Tous les mois sauf celui de février comportent 30 ou 31 jours. Le mois de février a quant à lui une longueur variable et comporte 28 jours les années « normales » et 29 les années bissextiles. Une année est bissextile si elle est un multiple de 4 mais pas de 100, ou alors un multiple de 400.

La représentation des dates par un triplet (jour, mois, année) est utile dans de nombreux cas mais elle ne se prête pas bien aux calculs. En effet, avec cette représentation, il n'est pas totalement trivial de déterminer combien de jours séparent deux dates, ou si une date est antérieure ou ultérieure à une autre. Dans ce genre de cas, il est préférable d'utiliser, au moins pour la durée des calculs, une représentation plus appropriée.

Une représentation convenant bien aux calculs consiste à utiliser un entier pour représenter une date. Cet entier est simplement le nombre, positif ou négatif, de jours écoulés depuis un « jour origine » arbitraire. Par exemple, si le 1er janvier 1900 est utilisé comme origine, alors le 28 décembre 1899 est représenté par l'entier -4 et le 12 février 1900 par l'entier 42. Avec une telle représentation, il devient trivial d'effectuer les calculs sus-mentionnés. Par exemple, pour calculer la date du lendemain d'une date donnée, il suffit d'ajouter 1 à l'entier qui la représente. La principale difficulté avec une telle représentation est la transformation d'un triplet (jour, mois, année) en l'entier correspondant et inversement.

Pour ce projet, vous utiliserez les formules données ci-dessous pour effectuer ces deux transformations. Elles sont extraites du livre Calendrical Calculations de Edward M. Reingold et Nachum Dershowitz, auquel les personnes intéressées par plus de détails se reporteront. Ces formules sont conçues pour faire correspondre l'entier 1 avec le 1er janvier de l'an 1.

Dans les formules ci-dessous, les opérateurs \(\newcommand{\bdiv}{\mathop{\rm div}\nolimits}\bdiv\) et \(\bmod\) représentent la division entière et son reste. Comme il existe plusieurs définitions de la division entière (et donc de son reste), il importe de dire laquelle est utilisée. Ici, il s'agit de la division par défaut, définie plus bas.

Date grégorienne vers entier

Pour passer d'une date grégorienne exprimée sous la forme d'un triplet (jour, mois, année), ici anglicisé et abrégé \((d, m, y)\), en un nombre de jour \(n\), on utilise la fonction \(g\) définie ainsi :

\[g(d, m, y) = 365\cdot y_0 + y_0 \bdiv 4 - y_0 \bdiv 100 + y_0 \bdiv 400\\ + (367\cdot m - 362) \bdiv 12 + c + d \] où \(y_0 = y - 1\) et \(c\) est un terme correctif défini ainsi :

\begin{aligned} c &= \begin{cases} 0 & \text{si } m \le 2\\ -1 & \text{si } m > 2 \wedge l\\ -2 & \text{sinon} \end{cases}\\ l &= (y\bmod 4 = 0 \wedge y\bmod 100 \ne 0) \vee y\bmod 400 = 0 \end{aligned}

Notez que l'expression \(l\) est vraie si et seulement si l'année \(y\) est bissextile.

Au moyen de la formule ci-dessus, on peut aisément calculer l'entier correspondant au 17 février 2014, \(g(17, 2, 2014)\), qui vaut 735281.

Entier vers date grégorienne

Pour passer d'un nombre de jours \(n\) en une date grégorienne exprimée sous la forme d'un triplet \((d, m, y)\), on utilise les formules ci-dessous.

Pour commencer, l'année \(y\) se calcule ainsi :

\[y = \begin{cases} y_0 & \text{si } n_{100} = 4 \text{ ou } n_1 = 4\\ y_0 + 1 & \text{sinon} \end{cases} \] où :

\begin{aligned} d_0 &= n - 1\\ n_{400} &= d_0 \bdiv 146097\\ d_1 &= d_{0} \bmod 146097\\ n_{100} &= d_1 \bdiv 36524\\ d_2 &= d_1 \bmod 36524\\ n_4 &= d_2 \bdiv 1461\\ d_3 &= d_2 \bmod 1461\\ n_1 &= d_3 \bdiv 365\\ y_0 &= 400\cdot n_{400} + 100\cdot n_{100} + 4\cdot n_4 + n_1 \end{aligned}

Le mois \(m\) du calendrier grégorien peut être calculé au moyen de la formule suivante :

\[ m = \left(12\cdot(p + c) + 373\right) \bdiv 367 \] où \(p\) est le nombre de jours écoulés depuis le début de l'année, et \(c\) est un terme correctif, calculés ainsi :

\begin{aligned} p &= n - g(1, 1, y)\\ c &= \begin{cases} 0 & \text{si } n < g(1, 3, y)\\ 1 & \text{si } n \ge g(1, 3, y)\wedge l\\ 2 & \text{sinon} \end{cases}\\ l &= (y\bmod 4 = 0 \wedge y\bmod 100 \ne 0) \vee y\bmod 400 = 0 \end{aligned}

Notez l'utilisation dans ces formules de la fonction \(g\) définie dans la section précédente.

Finalement, le jour \(d\) peut être calculé au moyen de la formule suivante :

\[ d = n - g(1, m, y) + 1 \]

Au moyen de ces formules, on peut calculer les valeurs de \(d\), \(m\) et \(y\) lorsque \(n\) vaut 735281 et vérifier qu'on obtient bien 17, 2 et 2014 (dans cet ordre).

Division entière

Comme mentionné brièvement plus haut, il existe plusieurs définitions possibles de la division entière, et donc de son reste. Deux d'entre-elles doivent être mentionnées ici : la division tronquée et la division par défaut.

La division (entière) tronquée d'un numérateur \(n\) par un dénominateur \(d\), notée \(n \bdiv_{\rm T} d\), et son reste, noté \(n \bmod_{\rm T} d\), sont définis ainsi :

\begin{aligned} \newcommand{\trunc}{\mathop{\rm trunc}\nolimits} n \bdiv_{\rm T} d &= \trunc\left(\frac{n}{d}\right)\\ n \bmod_{\rm T} d &= n - d\cdot(n \bdiv_{\rm T} d) \end{aligned}

où \(\trunc(x)\) est la troncature de \(x\), c-à-d \(x\) amputé de ses décimales.

La division (entière) par défaut d'un numérateur \(n\) par un dénominateur \(d\), notée \(n\bdiv_{\rm F} d\), et son reste, noté \(n \bmod_{\rm F} d\), sont définis quant à eux ainsi :

\begin{aligned} n \bdiv_{\rm F} d &= \left\lfloor \frac{n}{d}\right\rfloor\\ n \bmod_{\rm F} d &= n - d\cdot(n \bdiv_{\rm F} d) \end{aligned}

où \(\left\lfloor x\right\rfloor\) est la partie entière par défaut (floor en anglais) de \(x\).

Comme on le voit, la seule différence entre les deux formes de division est la fonction utilisée pour transformer le résultat de la division réelle en un entier. Pour les nombres positifs, les deux donnent le même résultat. Par contre, pour les nombres négatifs, ce n'est pas le cas puisque la troncation et la partie entière par défaut diffèrent. Par exemple, \(\trunc(-1.9) = -1\) alors que \(\left\lfloor -1.9\right\rfloor = -2\).

La division par défaut est mathématiquement supérieure à la division tronquée, dans le sens où elle possède plus de propriétés intéressantes. Toutes les formules de calcul de dates données plus haut tirent justement parti de ces propriétés, et ne sont correctes que si la division par défaut, et son reste, sont utilisés.

Malheureusement, comme on le verra plus loin, Java n'offre que la division tronquée. Cela n'est toutefois pas très problématique car il est facile de calculer le quotient \(q_F\) de la division par défaut d'un numérateur \(n\) par un dénominateur \(d\), de même que son reste \(r_F\), en fonction du quotient de la division tronquée, \(q_T\), et de son reste \(r_T\) :

\begin{aligned} \newcommand{\sgn}{\mathop{\rm sgn}\nolimits} q_F &= q_T - I\\ r_F &= r_T + I\cdot d\\ \end{aligned}

où :

\begin{aligned} I &= \begin{cases} 1 & \text{si } \sgn(r_T) = -\sgn(d)\\ 0 & \text{sinon} \end{cases} \end{aligned}

La fonction \(\sgn\) est la fonction signe, définie ainsi :

\begin{aligned} \sgn(x) = \begin{cases} -1 & \text{si } x < 0\\ 0 & \text{si } x = 0\\ 1 & \text{sinon} \end{cases} \end{aligned}

1.2 Heures

Une heure permet d'identifier un instant dans un jour, ici avec une précision d'une seconde.

Tout comme une date, une heure est généralement représentée par trois valeurs : l'heure, la minute et la seconde. L'heure est un entier compris entre 0 et 23 (inclus), et les minutes et secondes un entier compris entre 0 et 59 (inclus). Une telle heure spécifie le temps écoulé depuis minuit un jour donné.

Pour la même raison qu'il est difficile d'effectuer des calculs sur une date représentée par son triplet (jour, mois, année), il est difficile d'effectuer des calculs sur une heure représentée par son triplet (heure, minute, seconde). Pour simplifier les calculs avec les heures, on utilise donc généralement exactement la même technique que celle utilisée avec les dates : on représente une heure par un simple entier, qui est très naturellement le nombre de secondes écoulées depuis minuit.

Il faut noter que dans le domaine des transports publics, le passage d'un jour à un autre à minuit est ennuyant, car il est fréquent que des véhicules circulent à cette heure-là. Cela complique les choses dans la mesure où certaines courses peuvent commencer un jour donné et se terminer le lendemain.

Heureusement, il existe une solution simple à ce problème, puisqu'il y a généralement une période durant laquelle aucun véhicule ne circule, souvent entre 3 et 4 heures du matin. Dès lors, il suffit d'étendre quelque peu la plage des heures valides afin de s'assurer que toutes les courses se terminent le même jour qu'elles ont commencé.

Par exemple, une entreprise de transports en commun pourra considérer qu'une course commençant à 23h50 et durant 20 minutes ne se termine pas à 0h10 le lendemain comme elle le devrait, mais bien à 24h10 le même jour. Pour accommoder cela, les heures sont autorisées à dépasser 23 dans ce projet.

2 Mise en œuvre Java

La mise en œuvre Java des concepts présentés ci-dessus se fait principalement dans deux classes du paquetage ch.epfl.isochrone.timetable, prévu pour contenir tout ce qui a trait aux horaires. Toutefois, il convient aussi d'ajouter deux méthodes à la classe Math définie lors de l'étape précédente.

2.1 Classe Math

Avant de se lancer dans l'écriture de la classe des dates, il faut définir des fonctions permettent de calculer le quotient et le reste de la division entière par défaut.

Interface publique

Assez logiquement, les fonctions liées à la division entière par défaut s'ajoutent comme méthodes publiques statiques à la classe Math commencée lors de la séance précédente :

  • int divF(int n, int d) retourne le quotient de la division par défaut de n par d.
  • int modF(int n, int d) retourne le reste de la division par défaut de n par d.

Conseils de programmation

Java fournit déjà les opérateurs / et % pour calculer le quotient et le reste de la division tronquée, respectivement. En les utilisant, il est facile d'écrire les deux méthodes sus-mentionnées.

A noter que la fonction signe (notée \(\sgn\) plus haut) est fournie en Java sous la forme de la fonction (statique) signum de la classe Integer.

2.2 Classe Date

La classe Date du paquetage ch.epfl.isochrone.timetable, publique et finale, modélise les dates dans le calendrier grégorien. Ses instances doivent être immuables, c-à-d non modifiables après construction.

Interface publique

La classe Date contient deux énumérations publiques :

  • DayOfWeek, qui énumère les jours de la semaine, du lundi au dimanche, en anglais et en majuscules (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY).
  • Month, qui énumère les mois de l'année du calendrier grégorien, de janvier à décembre, en anglais et en majuscules (JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER).

La classe Date possède trois constructeurs publics :

  • Date(int day, Month month, int year), qui construit une date du calendrier grégorien étant donnés un jour, un mois et une année. Lève l'exception IllegalArgumentException si le jour est invalide, c-à-d inférieur à 1 ou supérieur au nombre de jours dans le mois donné.
  • Date(int day, int month, int year), qui construit une date du calendrier grégorien étant donnés un jour, un numéro de mois et une année. Lève l'exception IllegalArgumentException si le jour est invalide (voir ci-dessus) ou si le numéro de mois est invalide, c-à-d hors de l'intervalle [1;12].
  • Date(java.util.Date date), qui construit une date en fonction d'une date Java, c-à-d d'une instance de la classe java.util.Date.

Le premier de ces trois constructeurs est le constructeur principal, ce qui signifie que les deux autres ne doivent rien faire d'autre que de l'appeler avec les bons paramètres.

En plus de ces trois constructeurs, la classe Date possède les méthodes publiques suivantes :

  • int day(), qui retourne le jour du mois de la date, compris entre 1 et 31.
  • Month month(), qui retourne le mois de la date.
  • int intMonth(), qui retourne le mois de la date sous forme d'entier (1 pour janvier, 2 pour février, etc.).
  • int year(), qui retourne l'année de la date.
  • DayOfWeek dayOfWeek(), qui retourne le jour de la semaine de la date.
  • Date relative(int daysDiff), qui retourne la date distante du nombre de jours donnés de la date à laquelle on l'applique. Par exemple, en appelant relative avec le paramètre 1 on obtient la date du lendemain, avec -1 celle du jour précédent, etc.
  • java.util.Date toJavaDate(), qui retourne la date Java (c-à-d une instance de java.util.Date) correspondant à cette date.
  • String toString(), qui retourne la représentation textuelle de la date, qui doit être formée de l'année, du numéro du mois (sur deux chiffres) et du jour (sur deux chiffres), séparés par un tiret (-). Par exemple, la représentation textuelle du 17 février 2014 doit être 2014-02-17. Redéfinit la méthode toString héritée de Object.
  • boolean equals(Object that), qui compare la date à laquelle on l'applique avec l'objet passé en argument et qui retourne vrai si et seulement si cet objet est une date (c-à-d une instance de ch.epfl.isochrone.timetable.Date) désignant le même jour. Redéfinit la méthode equals héritée de Object.
  • int hashCode(), qui retourne l'entier correspondant à la date. (Comme nous n'avons pas encore examiné les tables de hachage dans le cours, il est difficile d'expliquer ici l'utilité de cette méthode. Contentez-vous pour l'instant de l'écrire, vous comprendrez son but dans quelques semaines.) Redéfinit la méthode hashCode héritée de Object.
  • int compareTo(Date that), qui compare la date à laquelle on l'applique avec la date passée en argument et qui retourne -1 lorsque la première est strictement inférieure à la seconde, 0 lorsque les deux sont égales, et 1 lorsque la première est strictement supérieure à la seconde.

Conseils de programmation

En interne, la classe Date doit bien entendu stocker une représentation du jour qu'elle désigne. Comme dit plus haut, cela peut se faire de différentes manières : on peut soit stocker les trois valeurs (jour, mois, année), ou alors un simple entier représentant le nombre de jours écoulés depuis le 1er janvier de l'an 1.

Nous vous conseillons ici de choisir la première de ces deux solutions, et donc d'équiper la classe Date de trois champs privés et finaux contenant le jour (type int), le mois (type Month) et l'année (type int). Mais vous êtes libres de choisir l'autre représentation interne si vous le préférez, la seule chose qui importe est que l'interface publique de votre classe soit conforme à la description ci-dessus.

A noter que, quelle que soit la solution que vous choisissez, vous devrez à un moment ou à un autre effectuer la transformation entre les deux représentations. En effet, certaines méthodes (p.ex. day ou month) requièrent la représentation (jour, mois, année), tandis que d'autres (p.ex. relative, equals ou compareTo) sont beaucoup plus simples à écrire avec la représentation sous forme d'entier.

Indépendemment de la représentation interne que vous choisissez, nous vous suggérons d'ajouter les méthodes statiques privées suivantes à votre classe. Elles facilitent grandement l'écriture des constructeurs et/ou méthodes publiques :

  • Month intToMonth(int m), qui transforme un numéro de mois en mois, avec la convention que l'entier 1 correspond au mois de janvier, l'entier 2 au mois de février, et ainsi de suite. Lève l'exception IllegalArgumentException si le numéro de mois est invalide, c-à-d hors de l'intervalle [1;12].
  • int monthToInt(Month m), l'inverse de la méthode intToMonth, qui transforme un mois en numéro de mois.
  • boolean isLeapYear(int y), qui retourne vrai si et seulement si l'année passée en argument est bissextile (voir la formule pour \(l\) donnée plus haut).
  • int daysInMonth(Month m, int y), qui retourne le nombre de jours dans le mois m de l'année y. Attention à bien retourner 29 pour le mois de février des années bissextiles !
  • int dateToFixed(int d, Month m, int y), qui transforme un triplet de valeurs (jour, mois, année) en un entier. Il s'agit simplement de la fonction \(g\) décrites plus haut.
  • Date fixedToDate(int n), l'inverse de la méthode dateToFixed, qui transforme un entier en date du calendrier grégorien.

En plus de ces méthodes statiques, il est recommandé d'offrir la méthode privée suivante :

  • int fixed(), qui retourne la date sous forme d'un entier. Cette méthode consiste en un simple appel à la méthode dateToFixed décrite ci-dessus, mais s'avère bien utile en pratique.

En écrivant le code qui manipule les dates Java (java.util.Date), vous constaterez que plusieurs méthodes et constructeurs de cette classe, p.ex. la méthode getYear, sont marqués deprecated. Cela signifie que ces entités sont obsolètes et que leur utilisation est désormais déconseillée, ce qu'Eclipse indique en raturant leur nom. Nous vous conseillons néanmoins de les utiliser ici, exceptionnellement, dans le but de simplifier les choses.

2.3 Classe SecondsPastMidnight

Pour représenter une heure, la solution propre et conforme aux principes de la programmation orientée-objet serait de définir une classe contenant le nombre de secondes après minuit dans un champ privé, et exposant des méthodes du même genre que celles définies sur les dates.

Ici, nous faisons toutefois une entorse à ces principes et représentons directement une heure par un entier contenant le nombre de secondes après minuit. En d'autres termes, plutôt que de définir une classe pour les heures et de représenter les heures comme des instances de cette classe, nous utilisons le type int de Java.

Cette solution détruit totalement l'encapsulation et possède énormément de défauts, mais aussi une qualité qui nous fait la choisir ici : elle est beaucoup moins gourmande en mémoire que la solution propre.

Pourquoi faire cela pour les heures alors que cela n'a pas été fait pour les dates ? Simplement parce que notre programme contiendra un très grand nombre d'heures, car à chaque départ d'un véhicule depuis un arrêt sera associé une heure. Le gain en mémoire, et donc en temps, que l'on peut obtenir en évitant des objets superflus est donc intéressant.

Cela dit, même si aucune classe n'est définie pour représenter les heures, il est utile d'en définir une contenant des fonctions permettant de manipuler les heures représentées sous la forme d'un entier. C'est le but de la classe SecondsPastMidnight du paquetage ch.epfl.isochrone.timetable, publique et finale, qui contient un certain nombre de méthodes statiques.

Interface publique

La classe SecondsPastMidnight possède un champ public statique final :

  • int INFINITE, qui contient la valeur 200000, un nombre de secondes après minuit qui est garanti plus grand que toutes les valeurs valides. En effet, les fonctions ci-dessous bornent l'heure à 30, donc le plus grand nombre de secondes après minuit valide est 29 h 59 min 59 s, soit 107999 secondes.

Cette classe possède de plus les méthodes publiques et statiques suivantes :

  • int fromHMS(int hours, int minutes, int seconds), qui convertit un triplet (heure, minutes, secondes) en un nombre de secondes après minuit. Par exemple, le triplet (9, 15, 33), à savoir 9 h 15 min 33 s, est converti en l'entier 33333. Lève l'exception IllegalArgumentException si l'une des trois valeurs est invalide. Les secondes et les minutes sont valides si elles sont comprises dans l'intervalle [0;60[. Quant aux heures, elles sont valides si elles sont comprises dans l'intervalle [0;30[ (la borne supérieure de cet intervalle est quelque peu arbitraire).
  • int fromJavaDate(java.util.Date date), qui convertit l'heure d'une date Java (c-à-d une instance de java.util.Date) en un nombre de secondes après minuit.
  • int hours(int spm), qui retourne le nombre d'heures de l'heure (représentée par un nombre de secondes après minuit) passée en argument. Par exemple, appliqué à 33333, cette méthode retourne 9. Lève l'exception IllegalArgumentException si le nombre de secondes après minuit passé en argument est négatif ou représente une heure au-delà de 29 h 59 min 59 s.
  • int minutes(int spm), qui retourne le nombre de minutes de l'heure (représentée par un nombre de secondes après minuit) passée en argument. Par exemple, appliqué à 33333, cette méthode retourne 15. Lève l'exception IllegalArgumentException si le nombre de secondes après minuit passé en argument est négatif ou représente une heure au-delà de 29 h 59 min 59 s.
  • int seconds(int spm), qui retourne le nombre de secondes de l'heure (représentée par un nombre de secondes après minuit) passée en argument. Par exemple, appliqué à 33333, cette méthode retourne 33. Lève l'exception IllegalArgumentException si le nombre de secondes après minuit passé en argument est négatif ou représente une heure au-delà de 29 h 59 min 59 s.
  • String toString(int spm), qui retourne la représentation textuelle du nombre de secondes après minuit passé en argument. Cette représentation consiste en un nombre d'heures, de minutes et de secondes, chacun représenté par deux chiffres, séparés par un double point (:). Par exemple, appliquée à 33333, cette méthode doit retourner la chaîne 09:15:33. Lève l'exception IllegalArgumentException si le nombre de secondes après minuit passé en argument est négatif ou représente une heure au-delà de 29 h 59 min 59 s.

Conseils de programmation

Tout comme la classe Math de l'étape précédente, la classe SecondsPastMidnight ne doit pas être instantiable, et il faut donc également l'équiper d'un constructeur par défaut vide et privé.

Pour écrire la méthode toString, il faut pouvoir formatter un nombre sur deux chiffres, avec un éventuel zéro en tête. La manière la plus simple de faire cela consiste à utiliser la méthode statique format de la classe String. Le premier argument de cette méthode est une chaîne de formattage qui décrit comment représenter la valeur des arguments qui suivent. La syntaxe de cette chaîne de formattage est documentée, mais comme elle est assez complexe, voici un exemple d'utilisation qui devrait vous aider :

String.format("%02d:%02d:%02d", 3, 8, 11);

Cet appel produit la chaîne 03:08:11, car chacune des trois occurrences du texte %02d dans la chaîne de formattage spécifie que le prochain argument doit être représentée sous forme décimale (le d), sur une largeur de 2 caractères et avec un éventuel 0 au début.

2.4 Tests

Comme pour l'étape précédente, nous vous fournissons des tests complets pour les trois classes que vous devez écrire ou compléter pour cette étape. Vous pouvez les obtenir sous la forme d'une archive Zip, tests-e02.zip, dont le contenu doit être importé dans votre projet. Pour ce faire, suivez la même procédure que pour l'étape précédente, mais seulement jusqu'au point 5, la suite n'étant plus nécessaire.

2.5 Documentation

Comme pour l'étape précédente, une fois le code écrit et testé, il reste à le documenter au moyen de commentaires JavaDoc.

3 Résumé

Pour cette étape, vous devez :

  1. Compléter la classe Math en lui ajoutant les méthodes divF et modF comme décrit plus haut.
  2. Ecrire les classes Date et SecondsPastMidnight en fonctions des spécifications ci-dessus.
  3. Exécuter les tests fournis et vérifier qu'ils ne produisent aucune erreur. S'ils échouent, il vous faut déterminer pourquoi et corriger les erreurs dans votre code.
  4. Documenter la totalité des entités publiques que vous avez définies.