Blog ENI : Toute la veille numérique !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

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...