Comment obtenir ou formater la date et l’heure en PHP

Dans cet article, je vais vous aider à obtenir ou à formater la date et l’heure en PHP. La fonction PHP date() La fonction PHP date() convertit un timestamp en une date et heure plus lisibles pour l’utilisateur. L’ordinateur stocke les dates et les heures dans un format appelé UNIX Timestamp, qui mesure le temps

Comment obtenir ou formater la date et l’heure en PHP
Réponse rapide

date() met en forme un horodatage, DateTimeImmutable sert dès qu'il y a un calcul, IntlDateFormatter donne les noms de jours et de mois en français. Réglez le fuseau explicitement : sans date.timezone, PHP utilise UTC. strftime() est déprécié depuis PHP 8.1 et n'est pas remplacé par date(), qui ne traduit pas.

Afficher une date en PHP est simple. Afficher la bonne date, dans le bon fuseau, en français, et calculer un intervalle sans se tromper au passage à l’heure d’été, l’est nettement moins. Cet article couvre les fonctions de base, puis les classes DateTimeImmutable et IntlDateFormatter qui règlent les cas que date() ne sait pas traiter. Toutes les sorties ont été produites sur PHP 8.5.10.

Le fuseau horaire, avant tout le reste

C’est la source de bug numéro un, et elle est silencieuse. Si date.timezone n’est pas réglé dans php.ini, PHP utilise UTC. Un site français affiche alors une heure décalée d’une ou deux heures selon la saison, sans le moindre avertissement.

php
echo ini_get('date.timezone'), "\n";        // (vide) sur beaucoup d'installations
echo date_default_timezone_get(), "\n";     // UTC

Le réglage se fait dans php.ini, ou en tête d’application :

php
date_default_timezone_set('Europe/Paris');

La règle de fond : stocker en UTC, afficher dans le fuseau de l’utilisateur. Une base de données qui contient des heures locales devient inexploitable dès qu’un utilisateur change de fuseau ou que l’heure d’été passe.

date() et time()

time() renvoie le nombre de secondes écoulées depuis le 1er janvier 1970 à minuit UTC. date() met en forme un tel horodatage, ou l’instant présent si on ne lui en donne pas.

php
date_default_timezone_set('Europe/Paris');

echo date('d/m/Y'), "\n";           // 02/09/2026
echo date('d/m/Y H:i:s'), "\n";     // 02/09/2026 21:47:03
echo time(), "\n";                  // 1788378423

echo date('d/m/Y', 1645303037), "\n";  // 19/02/2022

Les caractères de format les plus utilisés :

Date Heure
d jour sur 2 chiffres (01 à 31) H heure sur 24 h (00 à 23)
j jour sans zéro (1 à 31) h heure sur 12 h (01 à 12)
m mois sur 2 chiffres (01 à 12) i minutes (00 à 59)
n mois sans zéro (1 à 12) s secondes (00 à 59)
Y année sur 4 chiffres A AM ou PM
N jour de la semaine (1 = lundi) P décalage, par exemple +02:00
t nombre de jours dans le mois U horodatage Unix
L 1 si année bissextile c format ISO 8601 complet

Pour insérer un caractère qui est aussi un code de format, il faut l’échapper avec un antislash :

php
echo date('\L\e d/m/Y'), "\n";      // Le 02/09/2026
// sans antislash, « L » donnerait 0 ou 1 et « e » le nom du fuseau

date() ne parle pas français

C’est le deuxième piège classique. Les noms de jours et de mois renvoyés par date() sont toujours en anglais, et setlocale() n’y change rien.

php
$ts = mktime(0, 0, 0, 4, 1, 2022);

echo date('l', $ts), "\n";                    // Friday
setlocale(LC_TIME, 'fr_FR.UTF-8', 'fr_FR');
echo date('l', $ts), "\n";                    // Friday — inchangé

La solution est IntlDateFormatter, de l’extension intl :

php
$formateur = new IntlDateFormatter(
    'fr_FR',
    IntlDateFormatter::FULL,
    IntlDateFormatter::NONE,
    'Europe/Paris',
    IntlDateFormatter::GREGORIAN,
);

echo $formateur->format($ts), "\n";   // vendredi 1 avril 2022

Pour un format personnalisé, la classe accepte un motif ICU, dont les codes diffèrent de ceux de date() :

php
$formateur->setPattern("EEEE d MMMM y '\u{e0}' HH'h'mm");
echo $formateur->format(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')));
// mercredi 2 septembre 2026 à 21h47

Si intl n’est pas disponible, un tableau de correspondance dépanne pour le français, mais ne passera pas à l’échelle sur plusieurs langues.

DateTimeImmutable, la classe à utiliser

Pour tout ce qui dépasse l’affichage, les classes de date valent mieux que les fonctions. DateTimeImmutable est préférable à DateTime : ses méthodes renvoient un nouvel objet au lieu de modifier l’objet courant, ce qui évite les modifications à distance.

php
$paris = new DateTimeZone('Europe/Paris');
$date  = new DateTimeImmutable('2026-09-02 14:30:00', $paris);

echo $date->format('d/m/Y H:i P'), "\n";                     // 02/09/2026 14:30 +02:00
echo $date->modify('+3 days')->format('d/m/Y'), "\n";        // 05/09/2026
echo $date->format('d/m/Y'), "\n";                           // 02/09/2026 — inchangé

// Avec DateTime (muable), la troisième ligne afficherait 05/09/2026
php
// Ajouter et retirer des durées
$dans30Mois = $date->add(new DateInterval('P30M'));
$ilYa2Sem   = $date->sub(new DateInterval('P2W'));

// Écart entre deux dates
$ecart = $date->diff(new DateTimeImmutable('2026-12-25', $paris));
echo $ecart->days, " jours\n";
echo $ecart->format('%m mois et %d jours'), "\n";

// Une série de dates
$periode = new DatePeriod(
    $date,
    new DateInterval('P1D'),
    new DateTimeImmutable('2026-09-07', $paris),
);
foreach ($periode as $jour) {
    echo $jour->format('D d/m'), "\n";
}

Le piège du dernier jour du mois

Ajouter un mois à une fin de mois ne donne pas ce qu’on attend, et ce comportement est le même avec DateInterval qu’avec l’arithmétique sur mktime().

php
$fin = new DateTimeImmutable('2026-01-31');
echo $fin->add(new DateInterval('P1M'))->format('d/m/Y'), "\n";  // 03/03/2026

Le 31 février n’existe pas : PHP le fait déborder sur mars. Pour obtenir le dernier jour du mois suivant, il faut le demander explicitement :

php
echo $fin->modify('last day of next month')->format('d/m/Y'), "\n";  // 28/02/2026

Une journée ne fait pas toujours 24 heures

Au passage à l’heure d’été, une journée en fait 23, à l’heure d’hiver, 25. Ajouter 86400 secondes à un horodatage donne alors une heure fausse.

php
$paris = new DateTimeZone('Europe/Paris');
$avant = new DateTimeImmutable('2026-03-28 12:00:00', $paris);
$apres = $avant->add(new DateInterval('P1D'));

echo $apres->format('d/m/Y H:i P'), "\n";   // 29/03/2026 12:00 +02:00
echo ($apres->getTimestamp() - $avant->getTimestamp()) / 3600, " heures\n";  // 23

DateInterval raisonne en jours calendaires et donne bien midi le lendemain, alors que seules 23 heures se sont écoulées. C’est le comportement correct pour un rappel « demain à midi », et le mauvais pour une durée facturée. Les deux notions sont distinctes : P1D pour un jour calendaire, PT24H pour vingt-quatre heures.

Lire une date saisie par l’utilisateur

strtotime() comprend beaucoup de choses, mais interprète les formats ambigus à l’américaine : 03/04/2026 devient le 4 mars, pas le 3 avril. Pour un format connu, createFromFormat() est sans ambiguïté.

php
$date = DateTimeImmutable::createFromFormat(
    '!d/m/Y',                        // le « ! » remet l'heure à 00:00:00
    '03/04/2026',
    new DateTimeZone('Europe/Paris'),
);
echo $date->format('d F Y'), "\n";   // 03 April 2026

Sans le ! initial, les champs non fournis prennent la valeur de l’instant courant : deux exécutions du même code donnent deux heures différentes.

Le point le plus dangereux : une date invalide n’échoue pas, elle déborde silencieusement.

php
$saisie = '31/02/2026';
$date   = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $paris);

echo $date->format('d/m/Y'), "\n";                    // 03/03/2026
print_r(DateTimeImmutable::getLastErrors());          // ['warnings' => [10 => 'The parsed date was invalid'], …]
var_dump(checkdate(2, 31, 2026));                     // bool(false)

Il faut donc vérifier getLastErrors() après chaque analyse, ou comparer la date reformatée à la saisie d’origine :

php
function lireDate(string $saisie, DateTimeZone $fuseau): ?DateTimeImmutable
{
    $date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $fuseau);

    if ($date === false) {
        return null;
    }

    $erreurs = DateTimeImmutable::getLastErrors();
    if ($erreurs !== false && ($erreurs['warning_count'] || $erreurs['error_count'])) {
        return null;
    }

    return $date->format('d/m/Y') === $saisie ? $date : null;
}

Depuis PHP 8.2, getLastErrors() renvoie false quand il n’y a rien à signaler, au lieu d’un tableau avec des compteurs à zéro. Le test doit en tenir compte.

Nouveautés récentes

PHP 8.4 a ajouté createFromTimestamp(), qui accepte les horodatages fractionnaires :

php
$d = DateTimeImmutable::createFromTimestamp(1645303037.5);
echo $d->format('d/m/Y H:i:s.u P'), "\n";   // 19/02/2022 20:37:17.500000 +00:00

Auparavant il fallait passer par '@' . $ts ou setTimestamp(), et les microsecondes se perdaient.

Les fonctions dépréciées à ne plus utiliser

Plusieurs fonctions de date encore présentes émettent un avis de dépréciation. Elles fonctionnent sur PHP 8.5.10, mais leur retrait est programmé.

Fonction Dépréciée depuis Remplacement
strftime() PHP 8.1 IntlDateFormatter::format()
gmstrftime() PHP 8.1 IntlDateFormatter::format()
strptime() PHP 8.2 date_parse_from_format() ou IntlDateFormatter::parse()
date_sunrise() / date_sunset() PHP 8.1 date_sun_info()
utf8_encode() PHP 8.2 mb_convert_encoding()

Les messages exacts, relevés sur PHP 8.5.10 comme sur PHP 8.4.25 :

code
Deprecated: Function strftime() is deprecated since 8.1,
use IntlDateFormatter::format() instead

Deprecated: Function strptime() is deprecated since 8.2,
use date_parse_from_format() (for locale-independent parsing),
or IntlDateFormatter::parse() (for locale-dependent parsing) instead

Deprecated: Constant SUNFUNCS_RET_STRING is deprecated since 8.4,
as date_sunrise() and date_sunset() were deprecated in 8.1

strftime() était la façon habituelle d’obtenir une date en français. Son remplacement n’est pas date(), qui ne traduit pas, mais bien IntlDateFormatter.

À l’inverse, mktime(), checkdate(), getdate() et idate() ne sont pas dépréciés et fonctionnent normalement. Ils restent utiles pour des cas simples, même si les classes couvrent tout ce qu’ils font.

À retenir

  • Régler le fuseau explicitement, stocker en UTC, afficher en local.
  • date() pour formater un instant, DateTimeImmutable pour tout calcul.
  • IntlDateFormatter pour les noms de jours et de mois en français.
  • Après createFromFormat(), vérifier getLastErrors() : une date invalide déborde en silence.
  • Un jour calendaire n’est pas 24 heures. P1D et PT24H ne sont pas interchangeables.

Pour analyser des dates par millions dans un fichier d’import, voir lire les gros fichiers avec PHP, et la connexion à une base de données pour les stocker en UTC.

Voir aussi valider une date avec une expression régulière, gérer les dates avec Carbon dans Laravel et le hub Développement web.

Erreurs fréquentes

Fuseau horaire non réglé Sans date.timezone dans php.ini, PHP retombe sur UTC et affiche une heure décalée sans le moindre avertissement.
date('l') reste en anglais setlocale() n'a aucun effet sur date(). Le remplacement de strftime() est IntlDateFormatter, pas date().
createFromFormat accepte une date invalide 31/02/2026 devient 03/03/2026 avec un simple avertissement dans getLastErrors(). Il faut le vérifier après chaque analyse.
createFromFormat sans le « ! » Les champs non fournis prennent l'heure courante : deux exécutions du même code donnent deux résultats.
strtotime lit les dates à l'américaine 03/04/2026 devient le 4 mars, pas le 3 avril.
Ajouter un mois à une fin de mois 31/01/2026 + P1M donne 03/03/2026. Pour le dernier jour du mois, utiliser modify('last day of next month').
Une journée ne fait pas 24 heures Au changement d'heure, P1D avance bien d'un jour calendaire alors que 23 heures seulement se sont écoulées. P1D et PT24H ne sont pas interchangeables.
getLastErrors renvoie false Depuis PHP 8.2, la méthode renvoie false quand il n'y a rien à signaler, plus un tableau de compteurs à zéro.

DateDateTimeIntlPHP

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.