lundi 17 décembre 2012
Afficher la date du jour

Nous allons réaliser un programme simple affichant la date du jour sur la sortie standard.

Des fonctions de manipulations de date sont disponibles dans le module Unix fourni avec la distribution OCaml. Il existe d'autres bibliothèques pour manipuler les dates mais le but de ce billet, comme les suivants, est de montrer des exemples de code OCaml pour se familiariser à la fois avec les constructions du langage et à la fois avec des modules de la distribution standard.

Le programme suivra un algorithme très simple:

  1. Récupérer la date du jour,
  2. Transformer la date en chaîne de caractères,
  3. Afficher la chaîne sur la sortie standard.

Nous montrons d'abord les différentes parties du programme comme si elles étaient entrées dans le topevel ocaml. Le programme final est à la fin.

Récupérer la date du jour

La fonction Unix.time retourne le nombre de secondes écoulées depuis le 1 janvier 1970, tandis que la fonction Unix.gmtime prend un tel nombre de secondes et renvoie une structure du type Unix.tm, contenant le détail de la date associée à ce nombre de secondes: année, mois, jour du mois, heure, minute, ...

# Unix.time ;;
- : unit -> float = <fun>
# Unix.gmtime;;
- : float -> Unix.tm = <fun>

Pour obtenir le détail de la date du jour, nous pourrons écrire:

# let date = Unix.gmtime (Unix.time ()) ;;
val date : Unix.tm =
  {Unix.tm_sec = 17; tm_min = 35; tm_hour = 21; tm_mday = 7; tm_mon = 11;
   tm_year = 116; tm_wday = 3; tm_yday = 341; tm_isdst = false}
Transformer la date en chaîne de caractères

Pour avoir une représentation lisible, nous allons transformer cette date en chaîne de caractères, de la forme "jour du mois" "mois" "année".

Le jour du mois est représenté dans le champ tm_mon du type Unix.tm par un entier de 0 (janvier) à 11 (décembre).

Pour obtenir une chaîne représentant le mois, nous allons donc créer une fonction string_of_month procédant par cas sur la valeur entière du mois:

# let string_of_month = function
  0 -> "janvier"
| 1 -> "février"
| 2 -> "mars"
| 3 -> "avril"
| 4 -> "mai"
| 5 -> "juin"
| 6 -> "juillet"
| 7 -> "août"
| 8 -> "septembre"
| 9 -> "octobre"
| 10 -> "novembre"
| 11 -> "décembre"
| _ -> assert false (* on ne doit pas avoir une autre valeur *)
;;
val string_of_month : int -> string = <fun>

Le jour du mois et l'année sont des entiers. Pour l'année, il faut rajouter 1900 au contenu de tm_year pour avoir l'année correspondante. Nous utilisons la fonction Printf.sprintf pour créer une chaîne d'après un format et des arguments (comme la fonction sprintf en C). Nous pouvons donc définir la fonction suivante, prenant en paramètre une date au format Unix.tm et retournant une chaîne représentant la date:

# let date_string_of_tm tm =
  Printf.sprintf "%d %s %d"
    tm.Unix.tm_mday
    (string_of_month tm.Unix.tm_mon)
    (1900 + tm.Unix.tm_year)
;;
val date_string_of_tm : Unix.tm -> string = <fun>

Si nous ne voulions pas avoir à préfixer les noms des champs par Unix., il nous suffirait d'ajouter

open Unix;;

plus haut dans le code, rendant tous les éléments (valeurs, types, ...) définis dans le module Unix visibles dans l'environnement sans besoin de les préfixer.

Afficher la chaîne sur la sortie standard

L'affichage sur la sortie standard peut se faire simplement en utilisant la fonction print_endline : string -> unit qui affiche une chaîne de caractères suivi d'un retour-charriot sur la sortie standard.

Le programme complet

Le programme complet, placé par exemple dans un fichier date_du_jour.ml:

let date = Unix.gmtime (Unix.time ()) ;;

let string_of_month = function
  0 -> "janvier"
| 1 -> "février"
| 2 -> "mars"
| 3 -> "avril"
| 4 -> "mai"
| 5 -> "juin"
| 6 -> "juillet"
| 7 -> "août"
| 8 -> "septembre"
| 9 -> "octobre"
| 10 -> "novembre"
| 11 -> "décembre"
| _ -> assert false (* on ne doit pas avoir une autre valeur *)
;;

let date_string_of_tm tm =
  Printf.sprintf "%d %s %d"
    tm.Unix.tm_mday
    (string_of_month tm.Unix.tm_mon)
    (1900 + tm.Unix.tm_year)
;;

print_endline (date_string_of_tm date);;

Pour le compiler et l'exécuter en byte code, la commande est la suivante, sachant que comme le programme utilise la bibliotèque Unix, il faut ajouter cette bibliothèque sur la ligne de commande, avant le fichier de notre programme puisque ce dernier en dépend:

$ ocamlc -o date_du_jour.byte unix.cma date_du_jour.ml
$ ./date_du_jour.byte
17 décembre 2012

Pour compiler en code natif:

$ ocamlopt -o date_du_jour unix.cmxa date_du_jour.ml
$ ./date_du_jour
17 décembre 2012
Variante

Plutôt que définir une fonction par cas sur l'entier représentant le mois pour retourner le nom du mois, il est possible de définir un tableau des noms de mois et d'utiliser l'entier représentant le mois dans le type Unix.tm comme indice dans ce tableau. La fonction deviendrait alors:

let date_string_of_tm =
  let names = [|
     "janvier" ; "février" ; "mars" ; "avril" ; "mai" ; "juin" ;
     "juillet" ; "août" ; "septembre" ; "octobre" ; "novembre" ; "décembre"
     |]
   in
   fun i -> names.(i)
;;

Le tableau n'est défini qu'une seule fois.

Cette version est plus efficace puisque la valeur entière du mois n'est pas testée contre les différents filtres de la première version de la fonction.

Exercice
Exercice 1: Affichage du jour

Modifier le programme pour afficher également le jour de la semaine, en utilisant le champ tm_wday.