Article publié dans Linux Magazine 123, janvier 2010.
La perle de ce mois-ci a été rédigée par Guillaume Rousse
(<Guillaume.Rousse@inria.fr>
), de Lyon.pm et Paris.pm.
Le système des shadow passwords (cf shadow(5)) permet de renforcer la sécurité d'un système Unix, en séparant les informations sensibles des comptes utilisateurs, c'est-à-dire les mots de passe, des informations publiques, telles que les identifiants numériques (UID) ou les répertoires personnels (home directory). Ce système permet également de gérer le renouvellement du mot de passe, ainsi que son expiration, entraînant de fait l'invalidation du compte associé. Ceci permet de mettre facilement en place une politique d'attribution de comptes informatiques pour une durée limitée, par exemple celui du séjour d'un étudiant au sein d'un laboratoire de recherche.
Néanmoins, et aussi bizarre que cela paraisse, tout le monde ne compte pas couramment les dates à partir du premier janvier 1970, et les demandes d'ouverture de compte expriment assez rarement cette durée de validité sous cette forme, qui est pourtant celle attendue par la spécification des shadow passwords... Il s'ensuit donc des besoins de conversion assez récurrents, qui m'ont poussé à écrire un script dédié.
Comme pour d'autres domaines, il existe de multiples modules dédiés à la gestion des dates et des durées dans le monde Perl :
Date::Calc
Date::Manip
Class::Date
etc.
Trop même, puisque cette floraison ne facilite ni la maintenance, ni le choix
de l'utilisateur. Le projet DateTime
vise donc à réunifier l'ensemble des
fonctionnalités au sein d'un nombre réduit de modules à l'interface cohérente.
C'est donc l'approche recommandée aujourd'hui pour ce type de besoins.
À l'énoncé de la spécification, la première idée consiste à vouloir considérer
ce nombre de jours comme une durée, et donc d'utiliser la classe
DateTime::Format::Duration
pour la représenter. C'est très simple à faire,
effectivement. Malheureusement, le bât blesse au moment de la conversion de
cette durée, exprimée dans une unité quelconque, en nombre de jours. En effet,
si le nombre d'heures dans une journée est constant, ceci n'est pas vrai pour
le nombre de jours dans un mois. Il n'est donc pas possible de convertir une
durée comme "un an" en un nombre de jours. Il faut donc faire autrement, et
passer par l'approche bas niveau classique : toute date dans le monde Unix est
un nombre de secondes écoulées depuis le 1er janvier 1970.
On commence par la documentation, ce qui permet d'une part de ne pas l'oublier, d'autre part de spécifier l'interface de l'outil.
#!/usr/bin/perl =head1 NAME compute-expiration =head1 SYNOPSIS compute-expiration <format> <date> format can be either: =over =item B<duration>: account duration from now (days/months/years) =item B<date>: account expiration date (day/month/year) =item B<count>: account expiration date (days count since epoch) =back =cut
On passe ensuite au chargement des modules nécessaires, ainsi que par la définition d'une constante correspondant au nombre de secondes dans une journée.
use strict; use constant SECONDS_BY_DAY => 60 * 60 * 24; use feature 'switch'; use DateTime; use DateTime::Format::Duration; use DateTime::Format::Strptime; use Pod::Usage; use 5.010;
L'analyse des arguments est triviale, et renvoie à la documentation si ceux-ci
sont manquants, grâce à Pod::Usage
. On passe ensuite à la partie principale
du programme, sous la forme d'une directive given
, une des nouveautés de
Perl 5.10, permettant de traiter chacun des formats d'entrée.
my $format = $ARGV[0] || pod2usage("No format given, aborting"); my $date = $ARGV[1] || pod2usage("No date given, aborting"); my ($future, $count); given($format) {
Dans le premier cas, la date d'expiration est définie par une durée à partir de
la date actuelle. La chaîne donnée en entrée, sous la forme
"jours/mois/années", est convertie en une instance de la classe
DateTime::Format::Duration
. La date actuelle, elle, est une instance de la
classe DateTime
. L'addition des deux, possible grâce à la surcharge de
l'opérateur "+"
, fournit une nouvelle instance de la classe DateTime
correspondant à la date d'expiration. À défaut de méthode dédiée dans la
classe, il suffit alors de convertir cette date en nombre de secondes depuis le
1er janvier 1970, puis de diviser par le nombre de secondes par jour.
En cas d'erreur d'analyse, le programme s'arrête avec un message d'erreur.
Celui-ci se termine par un retour chariot, pour éviter l'ajout automatique par
la fonction die()
de la ligne du programme à laquelle l'erreur se produit.
Autant ce genre d'information est intéressant pour un développeur, autant elle
concerne peu l'utilisateur final.
when ('duration') { my $now = DateTime->now(); my $pattern = DateTime::Format::Duration->new(pattern => '%d/%m/%Y'); my $duration = $pattern->parse_duration($date); die "Unrecognized date pattern $date\n" unless $duration; $future = $now + $duration; $count = int($future->epoch() / SECONDS_BY_DAY); }
Dans le second cas, la date d'expiration est donnée directement. Il suffit
alors d'analyser la chaîne fournie, grâce à la classe
DateTime::Format::Strptime
, pour se retrouver dans le cas précédent.
when ('date') { my $pattern = DateTime::Format::Strptime->new(pattern => '%d/%m/%Y'); $future = $pattern->parse_datetime($date); die "Unrecognized date pattern $date\n" unless $future; $count = int($future->epoch() / SECONDS_BY_DAY); }
Enfin, le dernier cas correspond à la traduction du nombre de jours en date. Il suffit d'appliquer l'opération inverse, c'est à dire de convertir ce nombre en secondes d'abord, pour obtenir la date ensuite.
when ('count') { $future = DateTime->from_epoch(epoch => $date * SECONDS_BY_DAY); $count = $date; }
Il reste encore à traiter le cas de l'argument incorrect, ne correspondant à aucune des valeurs précédentes. Puis à afficher les valeurs calculées précédemment.
default { die "Unknown format $format\n"; } } printf "expiration date: %s\n", $future->strftime('%d/%m/%Y'); printf "days since 1970: %s\n", $count;
Au final, 62 lignes de code (documentation comprise) permettent donc de
convertir des dates, exprimées dans des formats différents, sans sacrifier
à la lisibilité du code, grâce à l'utilisation de modules issus du projet
DateTime
.
Envoyez vos perles à perles@mongueurs.net
, elles seront peut-être
publiées dans un prochain numéro de Linux Magazine.
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.