2 Utilisation élémentaire de DateTime
4 Conversion avec d'autres formats de date et d'heure
5 Groupes et plages de dates : durées, ensembles, plages et ensembles de plages
6 Exemples de calculs de dates
Cette FAQ traite des modules Perl de la série DateTime (cf. http://datetime.perl.org/ pour de plus amples renseignements, en anglais). Son but n'est pas de traiter des problèmes de date et d'heure en général, mais seulement de la façon d'utiliser les modules Perl de la famille DateTime .
La version originale de cette FAQ est en élaboration et a fortiori il en va de même pour cette traduction. De nombreux éléments sont énumérés dans le « reste à faire » (todo).
Si vous vous posez des questions qui ne figurent pas dans la FAQ, posez-les (traduites en anglais) à la liste de diffusion.
Vous pouvez utiliser
http://www.google.com/
pour effectuer vos recherches dans les archives de la liste
de diffusion
(cf. http://datetime.perl.org/mailing_list.html).
Toutefois, vous pouvez limiter votre recherche au site
http://archive.develooper.com/datetime@perl.org/.
Il suffit de copier-coller l'URL ci-dessous dans votre navigateur web
puis de remplacer « QUERY » par votre requête :
http://www.google.com/search?$q=site%3Aarchive.develooper.com
+inurl%3Adatetime%40perl.org%3AQUERY
La famille de modules DateTime offre une gestion unifiée des dates et des heures en Perl. Pour connaître l'état de l'art des modules de date et d'heure avant le développement de DateTime, reportez-vous à cet article (en anglais) : http://www.perl.com/pub/a/2003/03/13/datetime.html.
Le résumé de cet article est qu'il existe différents modules dont les fonctionnalités se recouvrent, mais qu'il est difficile de prendre une date provenant d'un module et de la convertir pour l'utiliser dans un autre module, dans le cas où l'on aurait besoin d'une fonction existant dans ce deuxième module.
Avantages :
DateTime::Calendar
, y compris
le calendrier grégorien implémenté par le module de base
DateTime.
strptime
(DateTime::Format::Builder).
Inconvénients :
Si vous avez consulté http://datetime.perl.org/modules.html et que rien ne semble correspondre à votre requête, essayez sur la liste de diffusion. Il manque encore quelques calendriers importants (islamique, hébreu et chinois si je me souviens bien). Et vous aurez notre gratitude si vous en écrivez un (mais vérifiez d'abord auprès de la liste de diffusion que personne n'a commencé à écrire le même que vous).
# C'est la ligne de code essentielle use DateTime;
Il existe différentes méthodes de créer un objet DateTime. Une façon consiste à spécifier chacun des éléments de la date. Tous les paramètres sont facultatifs sauf l'année. Voici la liste des paramètres, avec les valeurs par défaut :
my $dt1 = DateTime->new( year => 2003, month => 1, day => 1, hour => 0, minute => 0, second => 0, nanosecond => 0, time_zone => "floating", );
Vous pouvez créer un objet DateTime à partir d'une epoch
Unix (c'est-à-dire à partir d'une valeur semblable à ce que
renvoie la fonction Perl time()
) :
# Créer un objet DateTime s'appliquant à un instant donné my $dt2 = DateTime->from_epoch( epoch => time() ); # Et pour recalculer l'epoch à partir de l'objet my $time = $dt2->epoch();
Cf. DateTime::Format::Epoch si vous voulez une maîtrise plus fine de la conversion de l'epoch.
Pour l'heure donnée par l'horloge système, vous avez une syntaxe simplifiée :
my $dt3 = DateTime->now(); # La date et l'heure my $dt4 = DateTime->today(); # La date seulement
Dans chaque cas, il est possible de spécifier les paramètres facultatifs
time_zone
(fuseau horaire) et locale
(changement de langue).
Pour plus de précisions sur le fuseau horaire flottant, cf.
3.1 Qu'est-ce que le fuseau horaire flottant ?.
Pour des constructeurs plus évolués, voir la documentation du module
DateTime.
Étant donné qu'un objet DateTime représente un instant précis à la nanoseconde près, lorsque vous voulez comparer deux dates, vous devez décider du degré de précision, c'est-à-dire de l'unité de temps à utiliser. Par exemple :
my $une_date = ...; my $maintenant = DateTime->now(); # Comparaison naïve if ($une_date == $maintenant) { # MAUVAIS ! } my $une_date2 = $une_date->clone()->truncate(to => 'days'); my $maintenant = DateTime->today(); # Comme now() mais tronqué au jour près if ($une_date == $maintenant) { # Bon ! }
Si vous n'avez pas changé les deux objets pour les tronquer au jour près, il y a peu de chances qu'ils coïncident. Bien sûr, si vous voulez savoir si une réunion prévue pour durer une heure est encore en train de se dérouler, il faudrait tronquer à l'heure près. Mais pour ce genre de question, il est préférable d'utiliser un objet intervalle DateTime::Span.
Il faut utiliser clone
sur une date si vous avez
copié la variable et si vous comptez changer la valeur.
Étant donné qu'en interne, une date est une référence à une table de hachage,
copier la variable ne crée par un nouvel objet date, donc modifier
la valeur référencée par une variable revient à modifier la valeur
référencée par l'autre variable.
my $dt1 = DateTime->new( year => 2000 ); # "copier" la date et changer la "copie" $dt2 = $dt1; $dt2->set( year => 2003 ); print $dt1->year(); # Surprise, on obtient 2003
La bonne façon de procéder consiste à utiliser clone pour copier la variable :
my $dt1 = DateTime->new( year => 2000 ); # copie la date et change la copie $dt2 = $dt1->clone(); $dt2->set( year => 2003 ); print $dt1->year(); # on obtient : 2000
Il y a quelques façons de faire. Vous pouvez le faire explicitement :
$dt1 = DateTime->new( year => 1999 ); $dt2 = DateTime->new( year => 2000 ); my $cmp = DateTime->compare($dt1, $dt2); # $cmp vaut -1, 0, ou 1 selon que $dt1 est <, ==, or > à $dt2 respectivement
Ou bien, en utilisant la syntaxe Perl habituelle :
if ($dt1 > $dt2) { $foo = 3; } if ($dt1 == $dt2) { $foo = 4; } if ($dt1 < $dt2) { $foo = 5; } my @dates_triees = sort ($dt2, $dt1);
Des problèmes peuvent toutefois se produire si l'un des objets est affecté au fuseau horaire flottant. Cf. 3.1 Qu'est-ce que le fuseau horaire flottant ?
D'abord, vérifiez s'il existe un module approprié DateTime::Format
,
ceux-ci ont en général un filtre en entrée et un filtre en sortie qui vous
permettent de lire et d'écrire des dates vers des sources externes.
S'il n'existe pas de module approprié, vous pouvez utiliser DateTime::Format::Builder pour constituer sans peine un analyseur syntaxique.
Utilisez
DateTime::Format::Strptime.
Ce module implémente la fonction POSIX strptime
qui est la réciproque de strftime
.
Il existe aussi
DateTime::Format::Builder
qui a la possibilité de créer un analyseur à partir des formats spécifiques à
strptime
.
La différence entre ces deux modules est que
DateTime::Format::Strptime
sert à analyser quelques chaînes lors de l'exécution du programme, tandis que
DateTime::Format::Builder
permet de créer un nouveau module
DateTime::Format
et peut également enchaîner plusieurs formats qui seront essayés
tour à tour, jusqu'à que l'analyse de la chaîne réussisse, ou bien
que toutes les définitions ont échoué.
use DateTime::Format::Strptime; my $analyseur = DateTime::Format::Strptime->new( pattern => '%Y-%m-%d %H:%M:%S' ); my $dt = $analyseur->parse_datetime( '2003-05-04 12:55:10' ); # imprime '2003-05-04 12:55:10'; print $dt->ymd . ' ' . $dt->hms;
my $dt = DateTime->new(year => 1998, month => 4, day => 7, hour => 13, minute => 55); # Quelques cas courants my $s1 = $dt->date(); # 1998-04-07 my $s2 = $dt->mdy('|'); # 04|07|1998 my $s3 = $dt->datetime(); # 1998-04-07T13:55:00 my $s4 = $dt->time(); # 13:55:00 my $s5 = $dt->hms('x'); # 13x55x00 # Vous pouvez utiliser des formats un peu plus exotiques (voir la documentation # de DateTime pour les détails). # 1998-04-07 01:55:00 PM my $s6 = $dt->strftime("%F %r"); # Tue, 07 Apr 1998 13:55:00 +0000 (RFC 2925) my $s7 = $dt->strftime("%a, %d %b %Y %H:%M:%S %z");
DateTime
peut représenter des nanosecondes. Vous pouvez créer des objets avec cette
résolution en fournissant un paramètre
nanosecond
aux fonctions new
et set
et il existe un accesseur nanosecond
associé.
La valeur du paramètre doit être une valeur entière.
La milliseconde est la millième partie de la seconde (10^-3 ou 0,001). L'abréviation est ms. La microseconde est la millionnième partie de la seconde (10^-6 ou 0,000001). L'abréviation est µs si votre affichage admet les lettres grecques, us sinon. La nanoseconde est la milliardième partie de la seconde (10^-9 ou 0,000000001). L'abréviation est ns.
# Ci-dessous, la partie fractionnaire des secondes est 0,000000230 my $dt_ns = DateTime->new(year => 2003, month => 3, day => 1, hour => 6, minute => 55, second => 23, nanosecond => 230); print "ns: ", $dt_ns->nanosecond, "\n"; # Imprime : "ns: 230\n" # En supposant que l'on reçoive un argument en millisecondes my $ms = 42; my $dt_ms = DateTime->new(year => 2003, month => 3, day => 1, hour => 6, minute => 55, second => 23, nanosecond => $ms * 1_000_000); print "ns: ", $dt_ms->nanosecond, "\n"; # Imprime : "ns: 42000000\n"
Dû à Iain Truskett
Si votre date peut correspondre à un format parmi plusieurs, vous pouvez utiliser DateTime::Format::Builder pour construire un analyseur unique qui essaie les divers formats en séquence (et vous pouvez même ajouter vos règles si vous le souhaitez) :
package DateTime::Format::Fall; use DateTime::Format::HTTP; use DateTime::Format::Mail; use DateTime::Format::IBeat; use DateTime::Format::Builder ( parsers => { parse_datetime => [ sub { eval { DateTime::Format::HTTP->parse_datetime( $_[1] ) } }, sub { eval { DateTime::Format::Mail->parse_datetime( $_[1] ) } }, sub { eval { DateTime::Format::IBeat->parse_datetime( $_[1] ) } }, ] } );
Puis, dans un autre package :
package main; for ( '20030719T155345', 'Sat, 19 Jul 2003 15:53:45 -0500', '@d19.07.03 @704' ) { print DateTime::Format::Fall->parse_datetime($_)->datetime(), "\n"; } # Imprime : "2003-07-19T15:53:45 # 2003-07-19T15:53:45 # 2003-07-19T15:53:45"
Si vous connaissez le format de la chaîne, vous pouvez utiliser DateTime::Format::Strptime. Cf. 2.7 J'ai imprimé une date avec strftime, comment la réinterpréter ?.
Si vous ne connaissez pas le format, essayez avec DateTime::Format::HTTP, qui parvient à analyser un bon nombre de formats date-heure communément rencontrés.
TODO Talk about eval if needed...
Le fuseau horaire flottant correspond au cas où vous ne savez pas
quel est le véritable fuseau horaire associé à l'objet DateTime,
ou bien au cas où vous n'avez pas besoin de vous intéresser
aux fuseaux horaires.
Si vous comparez une heure flottante avec une heure associée à un
vrai fuseau horaire, que ce soit avec la méthode
compare
ou avec une comparaison surchargée
(==
, etc.), alors le fuseau flottant est
assimilé à l'autre fuseau dans le cadre de la comparaison :
my $dt1 = DateTime->new(year => 2002, month => 4, day => 7, hour => 13, minute => 55, time_zone => 'America/New_York'); my $dt2 = DateTime->new(year => 2002, month => 4, day => 7, hour => 13, minute => 55, time_zone => 'America/Los_Angeles'); my $dt_float = DateTime->new(year => 2002, month => 4, day => 7, hour => 13, minute => 55, time_zone => 'floating'); print "date fixe 1 == date flottante\n" if $dt1 == $dt_float; print "date fixe 2 == date flottante\n" if $dt2 == $dt_float; print "date fixe 1 != date fixe 2\n" if $dt1 != $dt2;
Si vous voulez traiter le fuseau flottant comme s'il s'agissait du fuseau
horaire UTC (c'est-à-dire avec un décalage horaire nul), alors utiliser
la méthode de classe compare_ignore_floating
à la place.
Toutefois, étant donné que le résultat de la comparaison dépend des différents
fuseaux horaires lorsque vous comparez des dates flottantes avec des dates
appartenant à des fuseaux horaires différents, cela risque de perturber
fortement la fonction sort()
. Dans ce cas, soit vous convertissez
toutes les heures flottantes vers un fuseau fixe, soit vous utilisez la
méthode de classe compare_ignore_floating
dans le bloc de
comparaison du tri, de manière que tous les fuseaux flottants soient assimilés
au fuseau UTC.
Sauf si vous savez exactement ce que vous faites, vous n'avez aucun intérêt à mélanger les fuseaux horaires flottants avec les fuseaux fixes. Convertissez toujours les heures flottantes vers le fuseau horaire approprié (il vous faudra décider si la valeur correcte est le fuseau local, UTC ou quelque chose d'autre) :
# Convertit toutes les heures flottantes vers le fuseau $time_zone # Arguments : # $dates est la référence à un tableau de DateTime # $time_zone est soit une chaîne, soit un objet DateTime::TimeZone # $clone indique s'il faut cloner les éléments de la liste ou pas # Renvoie : la référence à un tableau contenant les DateTime nettoyés (notez que le # tableau source est modifié si vous n'avez pas spécifié un $clone à « vrai ») sub unfloat_dates { my ($dates, $time_zone, $clone) = @_; $time_zone = "UTC" unless $time_zone; my @clean_dates = (); foreach my $d (@$dates) { $d = $d->clone() if $clone; $d->set_time_zone($time_zone) if $d->time_zone()->is_floating(); push @clean_dates, $d; } return \@clean_dates; } my %time = (year => 2003, month => 3, day => 1, hour => 1, minute => 32); my @dates = (DateTime->new(%time, time_zone => "America/New_York"), DateTime->new(%time, time_zone => "floating"), DateTime->new(%time, time_zone => "UTC"), ); if ($dates[0] == $dates[1] and $dates[2] == $dates[1]) { # La condition est vérifiée print "L'heure flottante est égale aux deux autres\n"; } unfloat_dates(\@dates, "UTC", 0); if ($dates[0] != $dates[1] and $dates[2] == $dates[1]) { # Cette nouvelle condition est vérifiée print "L'heure flottante est maintenant une heure UTC\n"; }
Par exemple, MySQL ne stocke pas les informations de fuseau horaire avec les dates, de sorte que DateTime::Format::MySQL renvoie des dates générées avec un fuseau horaire flottant. C'est à l'utilisateur de savoir à quel fuseau horaire l'objet DateTime appartient. Nous espérons que les développeurs du système basé sur MySQL ont réfléchi à la question est qu'ils utilisent des dates appartenant au même fuseau horaire.
Veuillez également noter que si le fuseau horaire de l'objet est le fuseau horaire flottant, alors les calculs sur cet objet ne tiennent pas compte des secondes intercalaires, car si l'on ne connaît pas le fuseau horaire, il est impossible de savoir quand s'appliquent les secondes intercalaires.
Vous serez amenés à utiliser le fuseau flottant comme étape intermédiaire si vous voulez changer un objet de fuseau horaire sans modifier l'heure locale. Cf. 3.3 Comment changer de fuseau horaire sans modifier l'heure locale ?.
my $source = DateTime->new(year => 1998, month => 4, day => 7, hour => 13, minute => 55, time_zone => 'America/New_York'); my $resultat = $source->clone() ->set_time_zone( 'America/Los_Angeles' ); print $source->strftime("%F %r %Z"), " devient ", $resultat->strftime("%F %r %Z"); # imprime : 1998-04-07 01:55:00 PM EDT devient 1998-04-07 10:55:00 AM PDT
Changez d'abord le fuseau horaire pour adopter le fuseau flottant (cf. 3.1 Qu'est-ce que le fuseau horaire flottant ?) sinon le temps affiché serait ajusté (en conservant la même heure interne, au lieu de conserver la même heure locale).
my $source = DateTime->new(year => 1998, month => 4, day => 7, hour => 13, minute => 55, time_zone => 'America/New_York'); my $resultat = $source->clone() ->set_time_zone( 'floating' ) ->set_time_zone( 'America/Los_Angeles' ); print $source->strftime("%F %r %Z"), " devient ", $result->strftime("%F %r %Z"); # imprime : 1998-04-07 01:55:00 PM EDT devient 1998-04-07 01:55:00 PM PDT
En fait, il existe plusieurs fuseaux EST
...
l'un aux États-Unis, l'autre en Australie. Si vous voulez le fuseau
horaire américain, spécifiez EST5EDT
, ou mieux, America/New_York
.
Les noms abrégés des fuseaux horaires ne sont pas discriminants. Toute tentative pour déterminer le fuseau horaire en fonction du nom abrégé implique une certaine dose de devinette. Utilisez plutôt les noms complets.
DateTime
procure un constructeur
from_epoch(...)
qui reçoit un argument epoch
dont la valeur est le nombre de secondes
depuis la date origine. La définition exacte de cette date origine dépend du
système, mais si vous utilisez une fonction time()
ou assimilée
pour obtenir cette valeur, le constructeur fonctionnera correctement.
En outre, notez que ce constructeur admet également les paramètres
time_zone
et locale
.
Cf. DateTime::Format::Epoch pour une maîtrise plus fine de la définition exacte de la date origine à utiliser. Vous pouvez également avoir besoin de ce module si vous importez une valeur epoch d'un autre système. Par exemple, la date origine sous Unix est fixée au 1er janvier 1970 à 00:00:00 et la valeur epoch est le nombre de secondes depuis cet instant, sans tenir compte des secondes intercalaires.
Pour connaître la date origine associée à un objet
DateTime,
utilisez la méthode epoch()
.
Si vous la voulez avec une haute précision incluant les nanosecondes,
utilisez hires_epoch()
.
Lorque vous créez un objet DateTime à partir d'une valeur epoch,
le fuseau horaire est implicitement fixé à
'UTC', à moins que vous n'ayez spécifié un paramètre time_zone
explicitement.
my $time = 1057632876; my $dt = DateTime->from_epoch(epoch => $time); print $dt->datetime(), "\n"; my $epoch = $dt->epoch(); print $dt->epoch(), "\n";
Cf. 4.1 Comment convertir entre les epochs et les objets DateTime ?
pour la solution. Utilisez la fonction time()
pour obtenir une valeur
epoch ou le module
Time::Local
si vous avez la liste de valeurs renvoyées par gmtime
ou localtime
.
Cf. 4.1 Comment convertir entre les epochs et les objets DateTime ? pour la solution.
Cf. 4.1 Comment convertir entre les epochs et les objets DateTime ?
pour la solution. Notez que la méthode
from_epoch(...)
de DateTime
conservera le nombre de nanosecondes, mais il faut utiliser hires_epoch()
pour la conversion inverse.
Cf. 3.1 Qu'est-ce que le fuseau horaire flottant ? pour la façon de faire.
Cf. 4.1 Comment convertir entre les epochs et les objets DateTime ?
pour la solution de base. Toutefois, il faudra appeler la méthode epoch
de
Time::Piece
pour obtenir la valeur epoch de l'objet.
La création d'un objet
Time::Piece
s'effectue en appelant gmtime
ou localtime
(tous deux surchargés par
Time::Piece)
avec la valeur retournée par la méthode epoch()
de DateTime.
use Time::Piece; my $lt1 = localtime(1057632876); my $dt = DateTime->from_epoch(epoch => $lt1->epoch()); print $dt->datetime(), "\n"; # Imprime 2003-07-08T02:54:36 my $lt2 = gmtime($dt->epoch()); print $lt2, "\n"; # Imprime Tue Jul 8 02:54:36 2003
Utilisez le module DateTime::Format::DateManip. À noter que ce module permet également de convertir des durées (durations) du module Date::Manip.
use DateTime::Format::DateManip; use Date::Manip; my $dt = DateTime::Format::DateManip->parse_datetime ("Jan 1st, 2001 12:30 AM GMT"); my $dm = DateTime::Format::DateManip->format_datetime($dt);
# De même, il faut utiliser l'epoch pour les comparaisons parce que # Date::Manip stocke les heures dans le fuseau horaire local my $ep = UnixDate($dm, "%s"); is($ep, '978309000', "DateTime::Format::Manip->format_datetime()");
C'est un peu délicat parce que
Date::Calc
vous permet d'utiliser des heures GMT et des heures locales, mais vous
devez vous-mêmes savoir quel type d'heure est stocké dans tel ou tel objet.
La meilleure façon de procéder est d'utiliser les fonctions Date_To_Time(...)
et
Mktime(...)
pour convertir une date-heure GMT ou une date-heure
locale en une epoch, puis de continuer comme
indiqué dans la recette
4.1 Comment convertir entre les epochs et les objets DateTime ?.
Pour convertir un objet DateTime
en une valeur
Date::Calc,
utiliser la fonction Gmtime()
ou Localtime()
en lui envoyant le resultat de epoch()
de
DateTime,
en fonction du type d'heure que vous voulez.
S'il s'agit d'utiliser un Date::Calc::Object, la démarche est similaire, seule la syntaxe d'appel change.
Un objet DateTime::Duration représente une durée. Vous obtenez un objet DateTime::Duration lorsque vous effectuez une soustraction entre deux objets DateTime et vous pouvez ajouter un objet DateTime::Duration à un objet DateTime existant pour obtenir un nouvel objet DateTime.
Un objet DateTime::Duration se décompose en durées élémentaires, étant donné que l'ajout de 31 jours n'est pas équivalent à l'ajout d'un mois, ou bien que l'ajout d'une minute n'est pas équivalent à l'ajout de 60 secondes, en raison des minutes comportant une seconde intercalaire (cf. 7.4 Secondes intercalaires, passage à l'heure d'été).
use DateTime::Duration; # TODO Think up a good example, we already do age above
Les trois modes permettent de choisir le traitement à effectuer si l'addition d'un certain nombre de mois ou d'années fait « déborder » le mois. Donc, si vous avez :
use DateTime::Duration(); sub test_duration_mode { my ($dt, $mode) = @_; my $dur = DateTime::Duration->new (years => 1, end_of_month => $mode); my $res = $dt + $dur; print $res->ymd(), "\n"; } my $dt1 = DateTime->new(year => 2000, month => 2, day => 29); my $dt2 = DateTime->new(year => 2003, month => 2, day => 28); # "wrap" reporte l'excédent sur le mois suivant test_duration_mode($dt1, "wrap"); # Imprime "2001-03-01\n" # "limit" empêche le report de l'excédent... test_duration_mode($dt1, "limit"); # Imprime "2001-02-28\n" # ... mais oublie 3 ans plus tard que l'on était en fin de mois test_duration_mode($dt2, "limit"); # Imprime "2004-02-28\n" # "preserve" fait "adhérer" la valeur à la fin du mois... test_duration_mode($dt1, "preserve"); # Imprime "2001-02-28\n" # ...même si l'addition proprement dite ne serait pas allée jusque-là test_duration_mode($dt2, "preserve"); # Imprime "2004-02-29\n"
Si vous voulez calculer quelque chose du genre « deux jours avant la fin du mois », il vaut mieux utiliser une récurrence :
# From Flavio Glock $set = DateTime::Event::Recurrence->monthly( days => -2 ); print "Occurrence suivante ", $set->next( $dt )->datetime;
Un objet DateTime::Set permet de représenter de façon efficace un certain nombre d'objets DateTime. Vous pouvez créer un ensemble à partir d'une liste d'objets DateTime existants :
use DateTime::Set; my $dt1 = DateTime->new(year => 2003, month => 6, day => 1); my $dt2 = DateTime->new(year => 2003, month => 3, day => 1); my $dt3 = DateTime->new(year => 2003, month => 3, day => 2); my $ens1 = DateTime::Set->from_datetimes( dates => [ $dt1, $dt2 ] ); $ens1 = $ens1->union($dt3); # Insère une autre date print "Le minimum de l'ensemble est la date la plus ancienne\n" if $dt2 == $ens1->min(); print "Le maximum de l'ensemble est la date la plus récente\n" if $dt1 == $ens1->max(); my $it = $ens1->iterator(); while ( my $dt = $it->next() ) { print $dt->ymd(), "\n"; } # Imprime "2003-03-01\n2003-03-02\n2003-06-01\n"
DateTime::Set peut également traiter des ensembles qui ne sont pas énumérés entièrement. Par exemple, vous pouvez créer l'ensemble de tous les premiers jours du mois :
my $set = DateTime::Set->from_recurrence( recurrence => sub { $_[0]->truncate( to => 'month' )->add( months => 1 ) }); my $dt1 = DateTime->new(year => 2003, month => 3, day => 1); my $dt2 = DateTime->new(year => 2003, month => 2, day => 11); print "2003-03-01 est le premier jour du mois\n" if $set->contains($dt1); print "2003-02-11 n'est pas le premier jour du mois\n" unless $set->contains($dt2);
Vous pouvez utiliser contains()
pour savoir si une date
donnée appartient à l'ensemble, comme c'est montré dans
5.3 Qu'est-ce qu'un objet DateTime::Set ?
ou bien, vous pouvez utiliser un itérateur pour boucler sur toutes les
valeurs de l'ensemble.
Pour boucler sur les éléments d'un ensemble, assurez-vous d'abord que la
date de début de cet ensemble est définie (et si vous souhaitez que la boucle
s'arrête, assurez-vous également que la date de fin est définie).
Si votre ensemble n'est a pas encore, vous pouvez créer soit un objet
DateTime::Set
soit un objet
DateTime::Span
et prendre l'intersection avec votre ensemble. Dans un but pratique,
la méthode iterator()
admet les mêmes arguments que
DateTime::Span
et les utilise pour limiter la boucle, tout se passant comme si
vous aviez utilisé un objet
DateTime::Span.
Dans l'exemple ci-dessous, nous utilisons un objet DateTime::Event::Recurrence pour définir plus aisément une récurrence mensuelle équivalente à celle que nous avons défini « à la main » en 5.3 Qu'est-ce qu'un objet DateTime::Set ?.
use DateTime::Event::Recurrence; my $set = DateTime::Event::Recurrence->monthly(); my $dt1 = DateTime->new(year => 2003, month => 3, day => 2); my $dt2 = DateTime->new(year => 2003, month => 6, day => 1); # Itérateur illimité sur un ensemble infini my $it1 = $set->iterator(); print $it1->next(), "\n"; # Imprime "-inf\n" # Itérateur borné sur un ensemble infini my $it2 = $set->iterator(start => $dt1, end => $dt2); while ( $dt = $it2->previous() ) { print $dt->ymd(), "\n"; } # Imprime "2003-06-01\n2003-05-01\n2003-04-01\n"
Dans l'exemple ci-dessus, nous avons utilisé la méthode previous()
pour boucler sur l'ensemble par valeurs décroissantes.
Vous pouvez également convertir votre
DateTime::Set
en une simple liste d'objets
DateTime
avec la méthode as_list
. Il vaut mieux ne pas
y avoir recours, car la représentation interne de
DateTime::Set
est nettement plus efficace.
L'une des fonctionnalités les plus importantes de DateTime::Set est que vous pouvez effectuer des opérations ensemblistes. Par exemple, vous pouvez créer l'ensemble de tous les premiers jours du mois et l'ensemble de tous les lundis, effectuer l'intersection des deux ensembles et vous obtenez tous les mois commençant par un lundi :
use DateTime::Event::Recurrence; # Premier jour du mois my $fom = DateTime::Event::Recurrence->monthly(); # Tous les lundis (premier jour de la semaine) my $mon = DateTime::Event::Recurrence->weekly( days => 1 ); # Tous les lundis qui tombent le premier jour du mois my $set = $fom->intersection($mon); my $it = $set->iterator (start => DateTime->new(year => 2003, month => 1, day => 1), before => DateTime->new(year => 2004, month => 1, day => 1)); while ( my $dt = $it->previous() ) { print $dt->ymd(), "\n"; } # Imprime "2003-12-01\n2003-09-01\n"
Voici la liste complète des opérations ensemblistes :
$ens3 = $ens1->union($ens2)
$ens3
contient tous les éléments de $ens1
ainsi
que tous ceux de $ens2
.
$ens3 = $ens1->complement($ens2)
$ens3
contient tous les élements de $ens1
qui ne figurent pas dans $ens2
.
$ens3 = $ens1->intersection($ens2)
$ens3
contient tous les élements qui figurent à la fois dans
$ens1
et dans $ens2
.
Un dernier opérateur, le complément unaire $ens3 = $ens1->complement()
renvoie un objet
DateTime::SpanSet
contenant tous les éléments n'appartenant pas à $ens1
.
Les modules suivants permettent de créer des récurrences de façon pratique.
Permet de créer des DateTime::Set pour chaque unité de temps (jour, heure, etc) tout en permettant une certaine personnalisation.
Permet de créer des objets DateTime::Set avec la syntaxe de la crontab.
Permet de créer des objets DateTime::Set (et autres) avec la syntaxe puissante, mais complexe, de iCal.
Un objet « intervalle »
(DateTime::Span)
représente un événement qui sétale dans le temps, alors qu'un objet
DateTime
se produit à un instant ponctuel (quoique, des objets
DateTime
peuvent représenter des durées s'ils sont tronqués par
truncate
à la même unité de temps, cf.
Pourquoi faut-il tronquer les dates ?).
À l'inverse des objets
DateTime::Duration,
les objets DateTime::Span
ont un début et une fin fixés.
Par exemple, un intervalle peut représenter une réunion planifiée du 2003-03-03 12:00:00 au 2003-03-03 13:00:00. L'intervalle inclut tous les instants compris dans cette heure. Quant au point de début et au point final, vous choisissez s'ils sont inclus dans l'intervalle à sa création.
use DateTime; use DateTime::Span; my $start = DateTime->new( year => 2003, month => 3, day => 3, hour => 12 ); my $before = DateTime->new( year => 2003, month => 3, day => 3, hour => 13 ); # le début est inclus mais pas la fin my $reunion = DateTime::Span->from_datetimes( start => $start, before => $before ); my $dt = DateTime->new( year => 2003, month => 3, day => 3, hour => 12, minute => 15 ); print "Je serai occupé à ce moment-là" if $reunion->contains($dt);
Note du traducteur : je n'ai pas traduit ce paragraphe car je pense qu'il contient des fautes de frappe et je n'ai pas regardé la meilleure façon de les corriger.
Un objet « ensemble d'intervalles » (DateTime::SpanSet) représente, comme on peut s'y attendre, un ensemble d'objets intervalles (DateTime::Span). Par exemple, pour représenter une semaine type de travail de 9 heures à 17 heures, du lundi au vendredi, avec une pause déjeuner de midi à 13 heures, utilisez ceci :
###### A VERIFIER ! use DateTime::Event::Recurrence; use DateTime::SpanSet; # Make the set representing the work start times: M-F 9:00 to 12:00 my $start = DateTime::Event::Recurrence->weekly ( days => [1 .. 5], hours => [8, 12] ); # Make the set representing the work end times: M-F 13:00 to 17:00 my $end = DateTime::Event::Recurrence->weekly ( days => [1 .. 5], hours => [13, 17] ); # Build a spanset from the set of starting points and ending points my $spanset = DateTime::SpanSet->from_sets ( start_set => $start, end_set => $end ); # Iterate from Thursday the 3rd to Monday the 6th my $it = $spanset->iterator (start => DateTime->new(year => 2003, month => 1, day => 3), before => DateTime->new(year => 2003, month => 1, day => 7)); while (my $span = $it->next) { my ($st, $end) = ($span->start(), $span->end()); print $st->day_abbr, " ", $st->hour, " to ", $end->hour, "\n"; } # Imprime "Fri 8 to 12\nFri 13 to 17\nMon 8 to 12\nMon 13 to 17\n" # Now see if a given DateTime falls within working hours my $dt = DateTime->new(year => 2003, month => 2, day => 11, hour => 11); print $dt->datetime, " is a work time\n" if $spanset->contains( $dt );
my $dt1 = DateTime->new(year => 2002, month => 3, day => 1); my $dt2 = DateTime->new(year => 2002, month => 2, day => 11); my $date = DateTime->new(year => 2002, month => 2, day => 23); # S'assurer que $dt1 est antérieure à $dt2 ($dt1, $dt2) = ($dt2, $dt1) if $dt1 > $dt2; # Tronquer les dates au jour (ne pas faire si vous souhaitez comparer # les heures précises) $dt1->truncate( to => 'day' ); $dt1->truncate( to => 'day' ); $date->truncate( to => 'day' ); # Et maintenant, comparer if ($dt1 <= $date and $date <= $dt2) { print '$date est comprise entre les deux autres dates'; }
Ou bien, vous pouvez avoir recours à un intervalle DateTime::Span :
use DateTime::Span; my $dt1 = DateTime->new(year => 2002, month => 3, day => 1); my $dt2 = DateTime->new(year => 2002, month => 2, day => 11); my $date = DateTime->new(year => 2002, month => 2, day => 23); # S'assurer que $dt1 est antérieure à $dt2 ($dt1, $dt2) = ($dt2, $dt1) if $dt1 > $dt2; # Construire l'intervalle (start et end donneront des comparaisons >= et <= # si vous voulez les comparaisons > et <, utiliser before et after à la place) my $intervalle = DateTime::Span->from_datetimes(start => $dt1, end => $dt2); if ($intervalle->contains($date)) { print '$date est comprise entre les deux autres dates'; }
Cf. également 2.3 Pourquoi faut-il tronquer les dates ?
use DateTime::Duration; my $dt1 = DateTime->new(year => 2002, month => 3, day => 1); my $dt2 = DateTime->new(year => 2002, month => 2, day => 11); # Contruire la durée à comparer aux deux dates $duree = DateTime::Duration->new( days => 19, hours => 3, minutes => 12); sub dates_proches { my ($dt1, $dt2, $duree) = @_; # S'assurer que $dt1 est antérieure à $dt2 ($dt1, $dt2) = ($dt2, $dt1) if $dt1 > $dt2; # On soustrait la durée à la date de fin et on compare le # résultat à la date de début. Si le résultat est antérieur, # cela signifie que les dates sont proches. if ($dt2 - $duree < $dt1) { return 1; } else { return 0; } } print 'plus rapprochées que $duree' if dates_proches($dt1, $dt2, $duree);
C'est juste un cas particulier de 6.2 Comment vérifier que l'intervalle entre deux dates-heures est inférieur ou supérieur à une durée donnée ?
Remarquez que soustraire les deux dates et regarder la composante year
ne fonctionne pas. Cf.
??????
# Date (et heure) de naissance my $naissance = DateTime->new(year => 1974, month => 2, day => 11, hour => 6, minute => 14); # Comme l'anniversaire ne se fête pas à la minute près, on arrondit la date au jour $naissance->truncate( to => 'day' ); my $aujourdhui = DateTime->today(); # L'âge demandé my $age_18 = DateTime::Duration->new( years => 18 ); print "Vous avez le droit de vote." unless within_interval($naissance, $aujourdhui, $age_18);
Exemple:
Avril 1998 Lun Mar Mer Jeu Ven Sam Dim 1 2 3 4 5 = semaine 1 6 7 8 9 10 11 12 = semaine 2 13 14 15 16 17 18 19 = semaine 3 20 21 22 23 24 25 26 = semaine 4 27 28 29 30 = semaine 5
# Arguments en entrée : # - La date # - Le jour que nous considérons en début de semaine (1 pour lundi, 7 pour dimanche) # (facultatif) sub get_week_num { my $dt = shift; my $start_of_week = shift || 1; # Par quel jour commence le mois ? (1 = lundi, etc) my $premier = $dt->clone(); $premier->set(day => 1); my $wday = $premier->day_of_week(); # On ramène ce numéro au début de la semaine (0 = début, 6 = dernier jour de la semaine) $wday = ($wday - $start_of_week + 7) % 7; # Puis on fait le calcul pour obtenir le numéro de la semaine my $mday = $dt->day_of_month_0(); return int ( ($mday + $wday) / 7 ) + 1; }
# Argument en entrée : la date # en fait, la formule est indépendant du jour de la semaine, # cela fonctionne aussi bien pour les lundis et les dimanches sub get_day_occurrence { my $dt = shift; return int( $dt->day_of_month_0() / 7 + 1 ); }
# Arguments en entrée # - La date # - Le jour souhaité (1 = lundi, 7 = dimanche) # - Le jour qui, selon vous, est le premier de la semaine (1 = lundi, 7 = dimanche) # (facultatif) # NOTE : le résultat peut très bien se trouver dans un mois différent sub get_day_in_same_week { my $dt = shift; my $cible = shift; my $debut_semaine = shift || 1; # Quel est le rang de la date source dans la semaine (0..6) ? my $wday = ($dt->day_of_week() - $debut_semaine + 7) % 7; # Quel est le rang du jour cible dans la semaine (0..6) ? $cible = ($cible - $debut_semaine + 7) % 7; # Ajuste la date source pour correspondre au jour cible return $dt->clone()->add(days => $cible - $wday); }
# La date et la cible (1 = lundi, 7 = dimanche) my $dt = DateTime->new(year => 1998, month => 4, day => 3); # vendredi my $cible = 6; # samedi # Obtient le jour de la semaine pour la date my $jour = $dt->day_of_week(); # Applique les corrections my ($prec, $suiv) = ($dt->clone(), $dt->clone()); if ($jour == $cible) { $prec->add( days => -7 ); $suiv->add( days => 7 ); } else { my $correction = ( $cible - $jour + 7 ) % 7; $prec->add( days => $correction - 7 ); $suiv->add( days => $correction ); } # $prec est le 1998-03-28, $suiv est le 1998-04-04
On se place à la fin du mois et on recule d'un jour tant que l'on se trouve sur un samedi ou un dimanche.
my $dt = DateTime->last_day_of_month( year => 2003, month => 8 ); # 6 = samedi, 7 = dimanche while ( $dt->day_of_week >= 6 ) { $dt->subtract( days => 1 ) } print "Vous serez payés le ", $dt->ymd, "\n";
Ce n'est peut-être pas la solution la plus efficace, mais elle est simple à comprendre.
Note du traducteur : la version originale parlait du 3e vendredi du mois, mais pour le groupe Paris.PM, c'est le 2e mercredi.
# Définit les caractéristiques de la réunion my $jour_reunion = 3; # (1 = lundi, 7 = dimanche) my $semaine_reunion = 2; my $dt = DateTime->new(year => 2004, month => 6, day => 18); # Repositionne au premier jour du mois demandé my $resultat = $dt->clone()->set( day => 1 ); # Ajuste le résultat au jour demandé puis ajuste la semaine my $dow = $resultat->day_of_week(); $resultat->add( days => ( $jour_reunion - $dow + 7 ) % 7, weeks => $semaine_reunion - 1 ); # Vérifie que l'on n'est pas passé au mois suivant die "Aucune date ne correspond ce mois-ci" if $dt->month() != $result->month(); # $resultat vaut maintenant 2004-06-09
Cette recette considère que vous avez deux dates et que vous voulez effectuer une boucle de l'une à l'autre. Une autre façon de procéder consiste à créer un ensemble (DateTime::Set) et à effectuer une itération sur cet ensemble.
my $debut_dt = DateTime->new(year => 1998, month => 4, day => 7); my $fin_dt = DateTime->new(year => 1998, month => 7, day => 7); my $semaines = 0; for (my $dt = $debut_dt->clone(); $dt <= $fin_dt; $dt->add(weeks => 1) ) { $semaines++; }
Il existe quelques façons de le faire : créer une liste d'objets DateTime, créer un ensemble DateTime::Set qui représente la liste ou simplement effectuer une boucle comme à la question 6.10 Comment itérer sur une plage de dates ?.
De ces trois possibilités, l'itération simple est probablement la plus rapide, mais vous ne pouvez pas facilement passer la liste à d'autres parties de votre traitement. Si vous avez besoin de transmettre une liste de dates, alors il faut utiliser DateTime::Set car les dates ne sont pas générées à la création de l'objet, elles sont générées au fur et à mesure des besoins. D'autre part, il est très facile de compléter ou de filtrer la liste. Cf. 5.3 Qu'est-ce qu'un objet DateTime::Set ?.
# Une liste Perl my $debut_dt = DateTime->new(year => 1998, month => 4, day => 7); my $fin_dt = DateTime->new(year => 1998, month => 7, day => 7); my @liste = (); for (my $dt = $debut_dt->clone(); $dt <= $fin_dt; $dt->add(weeks => 1) ) { push @liste, $dt->clone(); } # Un ensemble DateTime::Set. Nous utilisons DateTime::Event::Recurrence car cela # facilite la création des ensembles (Cf. également DateTime::Event::ICal si vos # ensembles sont plus compliqués) use DateTime::Event::Recurrence; use DateTime::Span; my $set = DateTime::Event::Recurrence->daily(start => $debut_dt, interval => 7); $set = $set->intersection(DateTime::Span->from_datetimes (start => $debut_dt, end => $fin_dt ));
TODO
my $dt = DateTime->now()->subtract( days => 1 ); print $dt->ymd;
TODO
Par exemple, dans trois jours ouvrés...
DateTime.pm est prévu pour intégrer les modules DateTime::Locale et adapter ainsi la sortie aux us et coutumes locaux ou nationaux. De plus, le module DateTime::Format::Strptime utilise lui aussi ces modules pour adapter l'analyse des chaînes aux particularités locales.
# fr_FR = French (France) my $dt = DateTime->new( year => 2000, month => 3, locale => 'fr_FR' ); print $dt->month_name;
Explications fournies par Flavio Glock (fglock at pucrs dot br), au cours d'une discussion entre Peter J. Acklam, Flavio Glock, John Peacock, Eugene Van Der Pijll et d'autres
Avant 1972, la référence du « temps international » était le GMT. En GMT, tous les jours ont le même nombre de secondes. Un jour commence à « minuit » et il dure 86400 secondes. La durée de la seconde était sujette à variation, puisqu'elle dépendait d'observations astronomiques.
Le TAI est un autre système de mesure du temps, dans lequel les secondes dépendent d'un « temps atomique » uniquement, pas de la position relative de la Terre et du Soleil. Un jour TAI dure exactement 86400 secondes TAI et ce système a pour origine le 1er janvier 1958.
En parallèle avec ceux-ci existe le temps UT1, le « temps astronomique ». Le temps UT1 dépend uniquement de la position du Soleil et de la Terre. La différence UT1 - TAI a augmenté au fil du temps, étant donné que la rotation de la Terre ralentit et que la longueur du jour a augmenté en conséquence. La différence entre les deux est un nombre flottant.
En 1972, le temps UTC a été introduit pour avoir une approximation entre le « temps international » et le « temps astronomique ». Maintenant, chaque fois que la différence entre UTC et UT1 dépasse un certain seuil, on introduit une seconde intercalaire. Le temps UTC est synchronisé avec le TAI, c'est-à-dire que la différence UTC - TAI est un nombre entier de secondes. La différence UTC - UT1 est une fraction de seconde.
Le module DateTime se réfère au temps UTC, sauf lorsque le fuseau horaire utilisé est le fuseau flottant.
TODO Explain why some days have 23 or 25 hours, and so on.
TODO Explain how to stringify
TODO Other Modules that are useful
Un grand remerciement à Dave Rolsky pour avoir été suffisamment dingue pour écrire le module DateTime pour commencer et pour avoir dirigé le reste de l'asile de fous et les avoir incités à faire quelque chose de super.
Un grand remerciement également aux autres fous de l'asile (Flavio Glock, Rick Measham, Iain Truskett, Eugene van der Pijll, Claus Färber, Kellan Elliot-McCrea, Daniel Yacob, Jean Forget, Joshua Hoblitt, Matt Sisk, Ron Hill et de nombreux autres), pour avoir travaillé sur ce fabuleux projet et m'avoir supporté avec mes questions saugrenues.
Merci à Steffen Beyer pour avoir écrit le module Date::Calc
et la documentation POD qui l'accompagne, étant donné que j'ai pompé les
premières questions sur cette documentation (et merci à Ron Hill
qui m'a suggéré de le faire).
Copyright (C) 2003, Benjamin Bennett pour le texte original. Tous droits réservés.
Copyright (C) 2004, Jean Forget et les Mongueurs de Perl pour la traduction. Tous droits réservés.
Diffusé sous les mêmes conditions que Perl.