« Les dates et les heures, c'est ce qu'on enseigne à de jeunes enfants. Qu'est-ce que ça peut bien avoir de difficile ? »
Beaucoup de développeurs ont fait cette erreur, et l'expérience qui s'ensuit a tendance à faire du mal à leur santé et à leur bonheur.
Quiconque fait de la programmation un tant soit peu sérieuse avec des dates et des heures devrait au moins être prêt à comprendre et à atténuer les problèmes potentiels.
Ce document tente de donner un aperçu de la façon dont R gère les dates et les heures, mais il faudra consulter la documentation complète pour davantage de détails.
En tant que langage de science des données, R accorde une importance particulière aux dates et aux heures. Une grande partie des données se présente sous forme de séries temporelles : le nombre quotidien de patients pendant une épidémie, les chiffres mensuels de l'emploi publiés par les gouvernements, les populations d'oiseaux dans une colonie de nidification, le PIB annuel des grandes économies, et d'innombrables autres exemples.
Est-ce à dire que R simplifie la gestion des dates et des heures ?
Hélas, non !
Les fonctionnalités de dates et d'heures de Base R ont mauvaise réputation, et la plupart sont aujourd'hui rarement utilisées.
Le paquet lubridate représente une nette amélioration, mais prépare-toi à une bibliothèque vaste et complexe, conçue pour couvrir un éventail tout aussi vaste et complexe de problèmes.
À l'origine indépendant, lubridate est désormais intégré à la collection tidyverse.
Charge-le avec library(lubridate) ou library(tidyverse).
Parmi les ressources qui t'aideront à travailler avec lubridate :
Une date n'est qu'un jour, sans information d'heure.
En interne, les dates sont stockées sous forme du nombre (signé) de jours écoulés depuis le 1970-01-01.
today()
#> [1] "2026-04-22"
Un datetime combine les informations de date, d'heure et de fuseau horaire.
En interne, il est stocké sous forme du nombre de secondes écoulées depuis le début de 1970 dans le fuseau horaire UTC.
now()
#> [1] "2026-04-22 15:37:11 MST" # mid-afternoon in Arizona
Aussi surprenant que cela paraisse, R n'a pas de représentation interne pour les valeurs qui ne contiennent qu'une heure.
date et datetime
Les besoins les plus fréquents en matière de dates et d'heures sont sans doute les suivants :
today()) et/ou le datetime actuel (avec now()).Il existe de nombreuses façons d'écrire les dates et les heures, qui ont tendance à dépendre de la culture. Les dates tout en chiffres comme « 7/6/23 » sont ambiguës, sources de confusion, et ont provoqué de nombreuses erreurs coûteuses dans des organisations multinationales (l'auteur en parle d'amère expérience personnelle).
La norme internationale (a-m-j) est définie dans ISO 8601, avec deux avantages principaux :
Cependant, la science des données consiste à travailler avec les données qu'on trouve, et il est souvent inutile de se plaindre de la façon dont les autres les formatent.
Base R dispose de fonctions de conversion qui prennent une chaîne de format (par défaut ISO 8601).
as.Date("2026-04-22")
#> [1] "2026-04-22"
as.Date("4/22/26", format = "%m/%d/%y")
#> [1] "2026-04-22"
as.POSIXct("2026-04-22 15:30:09 MST")
#> [1] "2026-04-22 15:30:09 MST"
Les chaînes de format ressemblent à la fonction strptime(), vieille de plusieurs décennies, présente dans beaucoup de langages.
Les développeurs qui connaissent cette syntaxe peuvent éventuellement utiliser la fonction fast_strptime() de lubridate.
Plus pratique encore, lubridate fournit un large éventail de fonctions auxiliaires, dont les noms indiquent l'ordre des champs.
Une fois que les fonctions savent quelles valeurs chercher, et dans quel ordre, l'analyse est d'une souplesse impressionnante.
mdy("4/22/26")
#> [1] "2026-04-22"
mdy("April 22nd 26")
#> [1] "2026-04-22"
mdy("Wednesday, April 22nd 2026")
#> [1] "2026-04-22"
mdy_hm("Wednesday, April 22nd 2026, 4:14 pm")
#> [1] "2026-04-22 16:14:00 UTC" # defaults to your OS timezone
Lorsque ton programme dispose des composantes d'une date ou d'un datetime sous forme de nombres séparés, il peut les assembler avec make_date() ou make_datetime().
Voici les composantes que l'on peut fournir, avec leur ordre et leurs valeurs par défaut :
make_datetime(
year = 1970L,
month = 1L,
day = 1L,
hour = 0L,
min = 0L,
sec = 0,
tz = "UTC"
)
#> [1] "1970-01-01 UTC"
make_datetime(2026, 4, 22, 17)
#> [1] "2026-04-22 17:00:00 UTC"
Naturellement, make_date() ne prend que les arguments année, mois et jour.
La console R affiche automatiquement les dates et les datetimes sous forme de string. C'est généralement pratique, mais parfois trompeur.
En interne, les dates et les datetimes sont des nombres (accompagnés de quelques attributs), pas des strings. Renvoyer une date (ou un datetime) là où une string est attendue posera des problèmes, il faut donc savoir comment effectuer la conversion.
Une approche simple consiste à utiliser as.character() de Base R, qui produit une string au format ISO 8601.
Il existe aussi une fonction format_ISO8601() pour les datetimes, qui propose des options d'arrondi à une précision choisie.
today() |> as.character() # a date
#> [1] "2026-04-23"
now() |> as.character() # a datetime
#> [1] "2026-04-23 09:07:09.779319"
now() |> format_ISO8601() # defaults to nearest second
#> [1] "2026-04-23T09:07:38"
Peu de non-programmeurs ont envie de voir une sortie ISO 8601, il est donc essentiel de gérer les formats de dates et d'heures propres à chaque culture.
Base R fournit la fonction format(), qui utilise des chaînes de format similaires aux vénérables fonctions strftime() présentes dans beaucoup d'autres langages.
format(today(), "%A, %d %B %Y")
#> [1] "Thursday, 23 April 2026"
Cette approche de Base R est laide mais fiable. Voici quelques-uns des codes les plus courants :
| Code | Résultat |
|---|---|
| %d | Numéro du jour du mois (5, 17, 28, etc.) |
| %a | Jour de la semaine abrégé (Mon, Tue, Wed, etc.) |
| %A | Jour de la semaine complet (Monday, Tuesday, etc.) |
| %w | Numéro du jour de la semaine (0-6, dimanche vaut 0) |
| %u | Numéro du jour de la semaine (1-7, lundi vaut 1) |
| %b | Mois abrégé (Jan, Feb, etc.) |
| %B | Mois complet (January, February, etc.) |
| %y | Année sur 2 chiffres (par ex. 89) |
| %Y | Année sur 4 chiffres (par ex. 1989) |
| %h | heures (horloge sur 24 h) |
| %m | minutes |
| %s | secondes |
| %z | décalage par rapport à UTC |
| %Z | Fuseau horaire (chaîne de caractères) |
Lubridate vise à offrir une alternative plus conviviale avec la fonction stamp(), ainsi qu'avec les fonctions plus spécifiques stamp_date() et stamp_time().
Celles-ci prennent en argument une chaîne modèle qui est un exemple de la sortie souhaitée, et créent une fonction personnalisée pour générer ce format.
Lubridate tente de deviner le format, mais se heurte souvent à des ambiguïtés et produit un résultat erroné.
Prépare-toi à fournir des chaînes de format (comme "dmy") pour lever l'ambiguïté.
Pour Exercism, il peut aussi être utile de préciser locale = "C", afin de contourner la locale de ton système et de travailler en anglais (comme les tests s'y attendent).
Les locales sont compliquées !
sf <- stamp("Saturday, June 27, 2026 at 14:45", locale = "C")
#> Multiple formats matched: "%A, %B %d, %Y at %H:%M"(1), "Saturday, %Om %d, %Y at %H:%M"(1), "Saturday, %B %d, %Y at %H:%M"(1), "%A, %Om %d, %Y at %H:%M"(0)
#> Using: "%A, %B %d, %Y at %H:%M"
sf(make_datetime(2023, 3, 29, 15, 0, 0))
#> [1] "Wednesday, March 29, 2023 at 15:00"
Lubridate fournit de nombreuses fonctions pour lire ou modifier les éléments d'une date ou d'un datetime : des fonctions évidentes comme year() et second(), mais aussi des fonctions plus spécialisées comme quarter() et epiweek().
Toutes ces fonctions permettent d'obtenir une valeur, et la plupart permettent aussi d'en définir une.
dt <- now()
dt
#> [1] "2026-04-23 15:47:11 MST"
# get
month(dt)
#> [1] 4
# set
month(dt) <- 11
dt
#> [1] "2026-11-23 15:47:11 MST"
Pour wday() (jour de la semaine) et month(), la sortie par défaut est un entier, mais :
labels = TRUE renvoie une chaîne de caractères abrégée correspondant à la locale courante.abbr = FALSE renvoie le nom complet.d <- today()
d
#> [1] "2026-04-23"
wday(d)
#> [1] 5
wday(d, label = TRUE)
#> [1] Thu
#> Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
wday(d, label = TRUE, abbr = FALSE)
#> [1] Thursday
#> Levels: Sunday < Monday < Tuesday < Wednesday < Thursday < Friday < Saturday
Ces étiquettes sont des categorical variables, qui feront l'objet d'un autre concept.
Les valeurs sont limitées aux Levels listés, qui indiquent aussi l'ordre de tri.
Par défaut, la semaine commence le dimanche, mais on peut changer cela avec l'argument week_start (utilise 1 pour lundi).
dt <- make_datetime(2026, 4, 23)
# default behavior
wday(dt)
#> [1] 5
wday(dt, label = T)
#> [1] Thu
# set week_start to Monday
wday(dt, week_start = 1)
#> [1] 4
Quelques fonctions utilitaires renvoient des valeurs TRUE/FALSE, comme leap_year() et dst() (l'heure d'été est-elle active ?).
La durée des mois varie, donc days_in_month() en donne la valeur entière.
Les valeurs date comme datetime représentent un instant sur la ligne du temps.
Comment gérer des notions comme « hier à la même heure » ou « dans une semaine » ?
Les apprenants qui connaissent d'autres langages orientés science ont peut-être entendu parler de datetime.timedelta en Python ou du type Period en Julia.
En revanche, R propose pas moins de trois façons de représenter les étendues de temps, chacune avec ses forces et ses limites. Comme toujours, choisis celle qui convient le mieux à ton contexte.
Ces différentes étendues se convertissent facilement entre elles, avec as.period(), as_duration() et as.interval().
On peut tester le type d'une étendue avec is.period(), etc.
Une period, comme hours(3), permet d'avancer une heure d'horloge de 3 heures.
Les irrégularités comme l'heure d'été (DST) sont largement ignorées.
Toutes les périodes portent le nom d'une unité au pluriel : years, weeks, etc.
d <- today()
d
#> [1] "2026-04-25"
d + weeks(2)
#> [1] "2026-05-09"
# dates are coerced to datetimes at midnight if necessary
d - hours(20)
#> [1] "2026-04-24 04:00:00 UTC"
hours(3)
#> [1] "3H 0M 0S"
Une duration, comme dhours(3), avance le temps physique de 3 heures, en tenant compte des irrégularités comme DST qui font que l'heure d'horloge s'écarte du temps physique.
Toutes les durées ajoutent un préfixe d à la période correspondante : dyears, dweeks, etc.
dhours(3)
#> [1] "10800s (~3 hours)"
Un interval est la combinaison d'une heure de début et d'une heure de fin, créé soit avec la fonction interval(), soit avec l'opérateur infixe %--%.
# the next 2 lines were run about 33s apart, so now() is different
dt1 <- now()
dt2 <- now() + hours(1)
# interval(0 function)
ivl <- interval(dt1, dt2)
ivl
#> [1] 2026-04-24 16:24:54 MST--2026-04-24 17:25:27 MST
# infix operator
iv2 <- dt1 %--% dt2
iv2 == iv1
#> [1] TRUE
Gérer les fuseaux horaires peut s'avérer délicat. La citation ci-dessous vient de la documentation de Python, mais elle s'applique tout autant à R :
« Les règles d'ajustement du temps dans le monde sont plus politiques que rationnelles, changent fréquemment, et il n'existe aucune norme adaptée à toutes les applications en dehors d'UTC. »
Quelques points généraux sur les fuseaux horaires :
datetime inclut toujours un fuseau horaire (tz) ; une date n'est pas sensible au fuseau horaire.now() fait exception : elle récupère l'heure locale à partir de l'horloge du système, tz compris.
Le tz local est utilisé par défaut, mais on peut en préciser d'autres, comme now("UTC").# timezone defaults to UTC
local <- ymd_hm("2026-04-25 10:32")
local
#> [1] "2026-04-25 10:32:00 UTC"
# timezone can be specified
AZ <- ymd_hm("2026-04-25 10:32", tz = "America/Phoenix")
AZ
#> [1] "2026-04-25 10:32:00 MST"
# same clocktime, different tz
local - AZ
#> Time difference of -7 hours
Les opérations arithmétiques sur les fuseaux horaires sont plus fiables avec les durées qu'avec les périodes.
Travailler avec des dates et des datetimes est généralement bien plus simple avec lubridate qu'avec Base R.
Ce concept a tenté de donner un aperçu, mais il passe inévitablement sous silence un très grand nombre de détails.
Au risque de nous répéter : la documentation est ton amie, alors n'hésite pas à t'en servir.
Dans cet exercice, tu vas travailler sur un planificateur de rendez-vous pour un salon de beauté à New York qui a ouvert le 15 septembre 2012.
Tu as cinq tâches à accomplir, et toutes portent sur des dates de rendez-vous. Les horaires utilisent le format 24 heures (pas d'AM/PM). Pour que les questions de fuseau horaire restent aussi simples que possible, considère que tout est en UTC.
Implémente la fonction schedule_appointment() pour convertir une représentation textuelle d'une date de rendez-vous au format DateTime correspondant.
Note que l'entrée est au format américain mois/jour/année.
julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00
Implémente la fonction has_passed() qui prend une date de rendez-vous et vérifie si le rendez-vous appartient au passé :
julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true
Implémente la fonction is_afternoon_appointment() qui prend une date de rendez-vous et vérifie si le rendez-vous a lieu l'après-midi (>= 12:00 et < 18:00) :
julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true
Implémente la fonction day_of_week() qui renvoie le jour de la semaine sous forme numérique.
Le salon est fermé le dimanche, donc lundi est le jour 1 et samedi le jour 6.
# a Friday
day_of_week(make_datetime(2026, 06, 05, 12, 30, 00))
#> [1] 5
Lorsqu'un client a besoin de reporter un rendez-vous, celui-ci est automatiquement replanifié au vendredi suivant à la même heure (c'est le jour le plus calme de la semaine).
Implémente la fonction reschedule() qui prend le rendez-vous d'origine (sous la forme d'une string au format mois/jour/année) et renvoie un rendez-vous reprogrammé.
# a Saturday
reschedule("06/13/2026 12:30:00")
#> [1] "2026-06-19 12:30:00 UTC"
Si le rendez-vous d'origine tombait du lundi au jeudi, replanifie-le au vendredi de cette semaine. Pour les rendez-vous du vendredi ou du samedi, replanifie-les au vendredi de la semaine suivante.
Inscris-toi sur Exercism pour apprendre et maîtriser R avec 21 concepts111 exercices, et un vrai mentorat humain, le tout gratuitement.