Dates et heures
Introduction
Obtenir la date et l’heure courante et effectuer des calculs sur des temps sont des opérations courantes en C. Il existe pour cela trois types de données correspondant à plusieurs utilisations possible de la date et de l’heure :
-
l’estampille temporelle, de type time_t ;
-
la structure struct tm ;
-
la chaîne de caractères (sous n’importe quel format).
L’estampille correspond au nombre de secondes écoulées depuis le 1er janvier 1970 à 00:00:00 (UTC). C’est un nombre entier de type time_t dont les deux utilisations majeures sont les calculs de différence de dates et l’horodatage d’événements. En l’occurrence, la fonction qui retourne la date et l’heure courante renvoie un résultat sous forme d’estampille. De plus, c’est sous cette forme qu’il vaut mieux stocker date et heure pendant l’exécution d’un programme : cela ne prend que quatre octets (c’est un long int dans glibc) et les calculs sont faciles à effectuer.
La structure struct tm est la plus simple à manipuler pour nous, humains, car ses champs correspondent à des notions humaines : jour, mois, année, heure, minutes, secondes... Elle sert donc en général à générer à partir de paramètres fournis par l’utilisateur, avant d’être...
Récupérer la date et l’heure courante
Problème
Vous avez besoin de connaître l’heure et la date courante.
Solution
La fonction time() renvoie en permanence le nombre de secondes écoulées depuis le 1er janvier 1970 à 0h00. Il s’agit donc d’utiliser cette fonction puis de convertir l’estampille temporelle résultante par exemple en une chaîne de caractères pour l’afficher.
Discussion
Pour obtenir la date ou l’heure sous une forme plus lisible, on transforme généralement cette estampille en une structure distinguant les heures, minutes, secondes et les jours, mois, année, ou directement en une chaîne de caractères. Les recettes "Convertir une estampille en struct tm et réciproquement" et "Convertir une estampille ou struct tm en chaîne de caractères" vous présentent cela plus en détail.
Lorsque vous travaillez avec la date et l’heure à des fins d’horodatage d’événements, comme l’heure à laquelle vous voulez que votre application se manifeste si vous êtes en train de programmer un réveil, il est toujours préférable de stocker cette heure en tant qu’estampille, sans la convertir en struct tm : cela économise la mémoire (44 octets contre 4 avec une estampille) et facilite les calculs....
Connaître le jour de la semaine
Problème
Vous avez besoin de connaître le jour de la semaine d’une date, pour savoir par exemple si l’on est samedi.
Solution
Récupérez la date sous la forme struct tm. Le champ tm_wday vous donne la réponse sous la forme d’un entier.
Discussion
La récupération de la date d’aujourd’hui sous forme de struct tm s’effectue en deux étapes, décrites successivement dans les recettes "Récupérer la date et l’heure courante" et "Convertir une estampille ou struct tm en chaîne de caractères" : la récupération de la date sous forme d’estampille puis sa transformation sous forme de struct tm. Le champ tm_wday retourne un nombre entre 0 (signifiant dimanche) et 6 (samedi). Au résultat, cela donne :
time_t t;
struct tm *tm;
char *semaine[] =
{ "dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi",
"samedi" };
if(time (&t) == (time_t) -1)
printf("Échec de time()\n");
else {
tm = localtime (&t);
if(tm)
printf ("Aujourdhui, nous sommes %s.\n", semaine[tm->tm_wday]);
else
printf("Échec de localtime\n");
}
Cet exemple doit être modifié...
Effectuer des calculs sur les dates
Problème
Vous souhaitez savoir combien de jours ou de secondes se sont écoulées entre deux dates.
Solution
Convertissez vos dates en estampilles temporelles, puis effectuez vos calculs. Reconvertissez le résultat si besoin dans un type plus approprié.
Discussion
Les calculs sur les dates sont en général de deux sortes : le calcul de la différence entre deux dates et la génération d’une date en fonction de divers paramètres, et des fois les deux comme le calcul de la différence entre maintenant et une date générée.
Pour générer une date, remplissez les divers champs d’une structure de type struct tm. Nous allons générer la date d’aujourd’hui, à midi, pour l’exemple :
struct tm tm;
timt_t t;
if(time(&t) != (time_t) -1) {
tm = localtime(&t);
if(tm) {
tm->tm_sec = 0;
tm->tm_min = 0;
tm->tm_hour = 12;
}
}
La structure est initialisée avec la date d’aujourd’hui avec l’heure actuelle, mais nous écrasons les valeurs de l’heure pour y placer 12h00 et 0 seconde, tout en gardant les autres valeurs, entre autres la date. Les paramètres à initialiser pour générer une date sont tm_sec, tm_min, tm_hour, tm_mday, tm_mon...
Convertir une estampille en struct tm et réciproquement
Problème
Vous disposez d’une date sous forme d’estampille et voulez la convertir en struct tm ou l’inverse.
Solution
Pour transformer une estampille en struct tm, recourez à localtime() ou gmtime(). Pour transformer un struct tm en estampille, utilisez mktime().
Discussion
Une estampille est une valeur simple : le nombre de secondes écoulées depuis le 1er janvier 1970 00h00’00 en temps universel coordonné (UTC). Par contre, les données stockées dans un struct tm sont complexes. Elles décomposent une date en ses différents éléments, jours, mois, année, heure, minutes, secondes, jours de la semaine, jours de l’année et décalage horaire, sans tenir compte du fuseau horaire. Par conséquent, la conversion vers une estampille est simple car il n’y a qu’un résultat possible pour une date, et de plus, mktime() corrige les dates dont les champs de la structure struct tm seraient mal renseignés (cela peut être volontaire). Par contre, la conversion vers un struct tm dépend du fuseau horaire. En conséquence, si vous voulez obtenir un struct tm en temps universel coordonné (UTC), utilisez gmtime(). Si vous voulez le même résultat, mais en tenant compte de votre fuseau horaire, utilisez localtime().
Si votre programme utilise...
Convertir une estampille ou un struct tm en chaîne de caractères
Problème
Vous disposez d’une date sous forme d’estampille ou de struct tm et vous voulez la transformer en chaîne de caractères.
Solution
Utilisez asctime(), ctime() ou strftime().
Discussion
Les fonctions asctime() et ctime() renvoient la date sous forme de chaîne de caractères de la forme : "Sat May 1 15:00:00 2004\n". Si vous programmez avec des threads, utilisez les versions dites réentrantes de ces fonctions : asctime_r() et ctime_r(). La fonction asctime() renvoie une chaîne de caractères correspondant à la date passée en paramètre, alors que ctime(), équivalente à asctime(localtime()), tient compte du fuseau horaire.
Pour afficher une date avec un format que vous maîtrisez, vous utilisez strftime() dont la syntaxe est proche de snprintf(). Pour le formatage des dates, notez l’existence de la norme ISO 8601 : les dates doivent être écrites sous forme AAAA-MM-JJ et les heures sous la forme HH:MM:SS. Pour cela, vous pouvez utiliser le format "%FT%T". D’autres formats sont aussi possibles comme celui utilisé par asctime() et par ctime() : les deux exemples ci-dessous sont équivalents (sauf pour la valeur renvoyée).
char d[] = "ddd mmm dd hh:mm:ss yyyy\n ";
strftime (d, strlen (d), "%c"...
Convertir une chaîne de caractères en estampille ou en struct tm
Problème
Vous disposez d’une chaîne de caractères contenant une date et, ou, une heure et souhaitez en obtenir une estampille ou un struct tm.
Solution
Utilisez strptime() ou getdate() si elles existent sur votre système.
Pour des systèmes qui ne proposent pas ces fonctions, ou si vous cherchez la compatibilité avec des systèmes qui ne les proposent pas, il n’y a pas de solution toute faite. Il faut analyser la chaîne de caractères, soit caractère par caractère, soit à l’aide d’une expression régulière (voyez la recette "Déterminer si une chaîne de caractères correspond au motif précisé dans une expression régulière" du chapitre "Chaînes de caractères" à ce sujet). De plus, la solution que vous allez construire sera une solution propre à un format de date et ne pourra pas être utilisée si le format change un peu trop. Néanmoins, le principe de l’analyse consiste à trouver dans la chaîne de caractères les données nécessaires pour remplir une variable de type struct tm.
Discussion
La fonction strptime() est le pendant de strftime(). Il faut fournir la chaîne à analyser suivie d’une chaîne qui indique le format de la date. Pour une date au format "Sat May 1 15:00:00 2004", le format est "%a %b %d %T %Y" :
time_t t = 1083416400;
char *d[20];
snprintf (d, 20, "%s", asctime (gmtime (&t)));
strptime (d, "%a %b %d %T %Y", &tm);
printf ("%s%s\n", d, asctime (&tm));
La fonction getdate(), ou getdate_r() si vous utilisez des threads, ne prend qu’une chaîne de caractères en entrée et renvoie un pointeur vers un tampon contenant le résultat sous forme de struct tm. Par contre, le format de la chaîne de caractères à utiliser est fourni dans un fichier, sur sa première ligne, au même format que pour...
Faire une pause
Problème
Vous voulez temporiser le programme, le faire attendre un certain temps ou jusqu’à une certaine heure.
Solution
La solution qui convient généralement pour des durées longues est d’utiliser sleep() dont la précision est de l’ordre de la seconde, ou select() pour une meilleure précision.
Discussion
La fonction sleep() dispose d’un gros avantage sur toutes les autres méthodes : sa simplicité. Les inconvénients liés à l’utilisation de cette fonction sont que sa précision est du niveau de la seconde, et qu’il ne faut pas utiliser des signaux qui pourraient interrompre la pause. Cependant, dans de nombreux cas, cela suffit amplement. La fonction select() est généralement utilisée pour déterminer si des entrées ont eu lieu ou si des sorties sont possibles sur les descripteurs de fichiers. Mais cette fonction dispose aussi d’un paramètre permettant d’indiquer une valeur de dépassement de temps autorisé (timeout) dont nous tirons profit ici :
struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;
select (0, NULL, NULL, NULL, &tv);
Ce code effectue une pause de dix secondes. Nous pouvons jouer sur le champ tv_usec pour aller au niveau de la microseconde. Pour atteindre le niveau de la nanoseconde, pselect() utilise une variable...
Calculer le temps mis par un extrait de programme à s’exécuter
Problème
Afin d’optimiser votre programme ou de faire des statistiques, vous souhaitez connaître le temps qu’il met à exécuter certaines parties de code.
Solution
Pour connaître le temps CPU, utilisez la fonction getrusage(). Notez le temps processeur avant l’exécution du code à chronométrer, puis appelez une seconde fois getrusage(). Le temps écoulé est la différence entre les valeurs renvoyées par cette fonction.
Pour connaître le temps réel, utilisez time() ou gettimeofday(). De la même façon qu’avec getrusage(), faites une différence entre la mesure avant et celle après l’opération.
Discussion
Sur un système mono-tâche, il suffirait de connaître l’heure avant et après l’exécution du code à chronométrer. Mais sur un système multitâches, comme le sont aujourd’hui les systèmes grand public, le déroulement d’une tâche est souvent interrompu pour permettre aux autres tâches de s’exécuter parallèlement, donnant à l’utilisateur cette impression que tout fonctionne en même temps. Par conséquent, il faut utiliser une autre fonction que time(), qui travaille sur l’heure courante. Vous recourrez à la fonction getrusage() qui prend pour référence le temps processeur écoulé lorsque celui-ci se consacrait à notre tâche.
La fonction getrusage() prend en argument RUSAGE_SELF (ou RUSAGE_CHILDREN si vous voulez mesurer le temps des processus fils), suivi d’un pointeur...