Site WWW de Laurent Bloch
Slogan du site

ISSN 2271-3905
Cliquez ici si vous voulez visiter mon autre site, orienté vers des sujets moins techniques.

Pour recevoir (au plus une fois par semaine) les nouveautés de ce site, indiquez ici votre adresse électronique :

Petit exercice de programmation
Jour, mois, année : quel jour tombe cette date ?
Article mis en ligne le 21 février 2018
dernière modification le 7 avril 2018

par Laurent Bloch

Spécification du programme

Étant donnée une date de la forme « jour mois année », il faut écrire un programme qui donne le jour de la semaine correspondant. Le programme compilé devra être invoqué de la façon suivante sur la ligne de commande :

On rappelle qu’à l’invite du shell chaque élément de texte délimité par des espaces est transmis au programme appelé sous forme de chaîne de caractères, et que le programme Scheme les reçoit sous forme d’une liste de chaînes de caractères, ainsi :

Il faudra donc convertir en nombres les chaînes "27", "10" et "2003".

Afin de rendre l’exercice plus stimulant, nous voudrions que le programme soit également capable d’effectuer ce calcul pour le calendrier julien, en vigueur dans certaines églises orthodoxes et orientales, ainsi que dans les livres d’histoire de la révolution russe. Nous ajouterons donc un argument facultatif "J" [1] pour calculer le jour de la semaine qui correspond à une date selon le calendrier julien :

Ce quatrième argument sera converti en symbole au sens de Scheme.

Selon le calendrier julien, les années multiples de 4 sont bissextiles. Selon le calendrier grégorien, en vigueur depuis le 15 octobre 1582 dans les pays dont la religion d’État était à l’époque le catholicisme, les années multiples de 4 sont bissextiles, sauf les années séculaires (multiples de 100), cependant que les années multiples de 400 restent bissextiles.

On donne deux dates de référence qui sont tombées un lundi : le 1er janvier 2018 pour le calendrier grégorien, le 23 janvier 2018 pour le calendrier julien.

Un excellent site calendaire suisse permettra de vérifier la justesse des calculs, et de découvrir d’autres calendriers plus ou moins compliqués qui pourront fournir des exercices amusants.

Récupération de la ligne de commande

Le programme sera réalisé sous la forme d’un module Bigloo, dont voici le début :

  1. (module JourMois
  2.         (main GetArgs))
  3.  
  4. (define (GetArgs Args)
  5.   (let* ((calendrier (cond ((= 5 (length Args))
  6.                             (string->symbol (car (reverse Args))))
  7.                            ((= 4 (length Args))
  8.                             'G)
  9.                            (else 'Erreur)))
  10.          (la-date (cons calendrier
  11.                         (cond ((= 4 (length Args))
  12.                                (map string->number (cdr Args)))
  13.                               ((= 5 (length Args))
  14.                                (map string->number
  15.                                     (reverse (cdr (reverse (cdr Args))))))
  16.                               (else '(Erreur)))))
  17.          (date-ref (if (eq? 'J calendrier)
  18.                        (list 'J 23 1 2018)
  19.                        (list 'G 1 1 2018))))
  20.     (print (calcul-jour la-date date-ref))))

Télécharger

Les éléments de la ligne de commande sont transmis à un programme Scheme sous la forme d’une liste. Les opérations disponibles pour les listes permettent d’en extraire le premier élément (car) ou d’en conserver la fin privée du premier élément (cdr). Pour extraire ou supprimer le dernier élément d’une liste, il faut opérer sur la liste retournée, par la procédure (reverse).

Année bissextile

Les calendriers julien et grégorien diffèrent par les règles de détermination des années bissextiles. Nous pouvons en déduire le prédicat Bissextile? qui calcule si une année est bissextile ou non, et la procédure Bissextile qui calcule la valeur à ajouter au nombre de jours de l’année selon que l’année est bissextile ou non :

  1. (define (Bissextile? la-date)
  2.   (let ((le-calendrier (calendrier la-date))
  3.         (l-annee (annee la-date)))
  4.     (case le-calendrier
  5.       ((J) ;; calendrier julien
  6.        (zero? (modulo l-annee 4)))
  7.       ((G) ;; calendrier grégorien
  8.        (or (and (zero? (modulo l-annee 4))
  9.                 (not (zero? (modulo l-annee 100))))
  10.            (zero? (modulo l-annee 400)))))))
  11.  
  12. (define (Bissextile la-date)
  13.    (if (Bissextile? la-date) 1 0))

Télécharger

Munis de ces procédures nous pouvons, étant donnés une année et un mois, calculer le nombre de jours de ce mois :

  1. (define (nbJoursMois la-date)
  2.   (let ((le-mois (mois la-date)))
  3.     (if (= le-mois 2)
  4.         (+ 28 (Bissextile la-date))
  5.         (+ 30
  6.            (modulo
  7.             (+ le-mois (quotient le-mois 8))
  8.             2)))))

Télécharger

Type abstrait date

Les dates seront représentées dans le programme par des listes de quatre éléments :

  1. (<calendrier> <jour> <mois> <année>)

calendrier vaut J si on calcule dans le calendrier julien, G pour le calendrier grégorien.

Nous pouvons considérer cette représentation comme un type abstrait date, et voici son constructeur et les accesseurs pour les différents champs :

  1. (define (fab-date calendrier jour mois annee)
  2.    (list calendrier jour mois annee))
  3.  
  4. (define (annee la-date)
  5.    (cadddr la-date))
  6.  
  7. (define (mois la-date)
  8.    (caddr la-date))
  9.  
  10. (define (jour la-date)
  11.    (cadr la-date))
  12.  
  13. (define (calendrier la-date)
  14.    (car la-date))

Télécharger

Procédures utilitaires

Calculer le nombre de jours d’un mois donné d’une année donnée :

  1. (define (nbJoursMois la-date)
  2.   (let ((le-mois (mois la-date)))
  3.    (if (= le-mois 2)
  4.       (+ 28 (Bissextile la-date))
  5.       (+ 30
  6.          (modulo
  7.           (+ le-mois (quotient le-mois 8))
  8.           2)))))

Télécharger

Déterminer si une date donnée est antérieure à une date de référence :

  1. (define (avant? la-date date-ref)
  2.   (or (< (annee la-date) (annee date-ref))
  3.       (and (= (annee la-date) (annee date-ref))
  4.            (< (mois la-date) (mois date-ref)))
  5.       (and (= (annee la-date) (annee date-ref))
  6.            (= (mois la-date) (mois date-ref))
  7.            (< (jour la-date) (jour date-ref)))))

Télécharger

Pour deux dates dont les millésimes diffèrent de 2 ou plus (années non égales et non consécutives), calculer la somme des nombres de jours des années entières entre ces deux dates :

  1. (define (nbJoursAnnees date1 date2)
  2.   (let ((annee1 (annee date1))
  3.         (annee2 (annee date2))
  4.         (le-calendrier (calendrier date1)))
  5.     (do ((l-annee (+ annee1 1) (+ 1 l-annee))
  6.          (nbJours 0 (+ nbJours 365
  7.                        (Bissextile
  8.                           (fab-date le-calendrier 1 1 l-annee)))))
  9.         ((= l-annee annee2)
  10.          nbJours))))

Télécharger

Pour une date donnée, quel est le rang du jour dans l’année ?

  1. (define (posJourAnnee la-date)
  2.   (let ((le-calendrier (calendrier la-date))
  3.         (l-annee (annee la-date)))
  4.    (do ((le-mois 1 (+ le-mois 1))
  5.         (nbJours 0 (+ nbJours
  6.                       (nbJoursMois
  7.                          (fab-date
  8.                             le-calendrier
  9.                             (jour la-date) le-mois l-annee)))))
  10.        ((= le-mois (mois la-date))
  11.         (+ (jour la-date) nbJours)))))

Télécharger

Pour une date donnée, le nombre de jours restants jusqu’à la fin de l’année :

  1. (define (nbJoursRestants la-date)
  2.   (- (+ 365 (Bissextile la-date))
  3.      (posJourAnnee la-date)))

Télécharger

Calcul final

Pour deux dates données, combien de jours entre elles ?

  1. (define (diff-dates date1 date2)
  2.   (let ((le-calendrier (calendrier date1)))
  3.     (cond
  4.      ((equal? date1 date2) 0)
  5.      ((= (annee date1) (annee date2))
  6.       (- (posJourAnnee date2)
  7.          (posJourAnnee date1)))
  8.      ((= (annee date2) (+ 1 (annee date1)))
  9.       (+ (nbJoursRestants date1)
  10.          (posJourAnnee date2)))
  11.      (else
  12.       (+ (nbJoursAnnees date1 date2)
  13.          (nbJoursRestants date1)
  14.          (posJourAnnee date2))))))

Télécharger

Et enfin, étant données une date et une date de référence dont on connaît le jour de la semaine, calculer le jour de la semaine auquel elle tombe :

  1. (define (calcul-jour la-date date-ref)
  2.    (let ((NomsJours (vector "Lundi" "Mardi"
  3.                         "Mercredi" "Jeudi" "Vendredi"
  4.                         "Samedi" "Dimanche"))
  5.          (intervalle
  6.           (if (equal? la-date date-ref)
  7.               0
  8.               (if (avant? la-date date-ref)
  9.                   (- (diff-dates la-date date-ref))
  10.                   (diff-dates date-ref la-date)))))
  11.      (vector-ref NomsJours (modulo intervalle 7))))

Télécharger