Comparer deux dates en PHP paraît simple tant qu’on travaille avec des valeurs propres et un seul fuseau horaire. Dès qu’on mélange format français, heures, timestamps et saisie utilisateur, les erreurs deviennent discrètes mais coûteuses. Je vais montrer ici les méthodes fiables, les cas où strtotime() suffit encore, et la manière de gérer correctement les écarts, les fuseaux et les formats d’entrée.
L’essentiel pour comparer deux dates sans faux résultat
- DateTimeImmutable est mon choix par défaut pour comparer des dates ou des instants.
- Les opérateurs
<,>,<=et>=sont les plus lisibles pour l’ordre chronologique. - diff() sert à mesurer un écart, pas à décider quelle date est la plus ancienne.
- strtotime() reste utile pour des cas simples, mais il devient fragile dès que le format est ambigu.
- Pour une date seule, il faut neutraliser l’heure et le fuseau horaire avant de comparer.
Choisir la bonne approche selon le type de comparaison
Avant d’écrire une ligne de code, je décide ce que je veux comparer exactement : deux instants précis, deux dates sans heure, ou un écart de durée. La bonne méthode change selon la réponse, et c’est là que beaucoup de bugs commencent.
| Méthode | Quand l’utiliser | Forces | Limites | Mon usage |
|---|---|---|---|---|
DateTimeImmutable + opérateurs |
Comparer l’ordre de deux dates ou heures | Lisible, stable, robuste avec les fuseaux | Demande une normalisation de l’entrée | Par défaut dans les projets web |
diff() |
Mesurer un écart en jours, mois, heures ou minutes | Donne un intervalle détaillé | Ne sert pas à classer deux dates entre elles | Délais, SLA, échéances |
strtotime() + timestamp |
Conversion rapide d’une chaîne connue | Simple, rapide, pratique pour du legacy | Parsing ambigu, dépend du fuseau par défaut | Cas simples et contrôlés |
createFromFormat() |
Entrée utilisateur avec format défini | Contrôle strict du format | Plus verbeux | Formulaires, import de données, formats français |
La documentation PHP recommande clairement DateTimeImmutable pour les calculs de date, et ce n’est pas un conseil cosmétique : c’est ce qui évite une partie des effets de bord dès que l’on manipule plusieurs objets. Une fois ce choix posé, je peux comparer les valeurs sans transformer le code en usine à timestamps.
Comparer deux dates avec DateTimeImmutable
Quand je travaille sur une application web moderne, je pars presque toujours de deux objets DateTimeImmutable. L’intérêt est simple : l’objet ne se modifie pas en place, donc le code reste plus prévisible, surtout dans des traitements en chaîne.
$deadline = new DateTimeImmutable('2026-06-15 18:00:00', new DateTimeZone('Europe/Paris'));
$now = new DateTimeImmutable('2026-06-09 09:00:00', new DateTimeZone('Europe/Paris'));
if ($now < $deadline) {
echo 'Encore dans les temps';
} else {
echo 'Échéance dépassée';
}Comparer l’ordre chronologique
Les opérateurs <, >, <= et >= sont suffisants si votre objectif est de savoir quelle date arrive avant l’autre. Je les trouve plus lisibles qu’une cascade de conversions en timestamp, surtout dans un code métier où la logique doit rester immédiate.
Vérifier le même moment
Pour l’égalité, je me méfie de ===. Cet opérateur dit seulement si deux variables pointent vers le même objet, pas si elles représentent le même instant. Si je veux comparer la valeur temporelle, je passe par getTimestamp() pour une précision à la seconde, ou par format('U.u') si les microsecondes comptent réellement.
$sameSecond = $a->getTimestamp() === $b->getTimestamp();Cette distinction paraît technique, mais elle évite des erreurs très bêtes dans les validations, les réservations ou les règles de publication. Quand je dois connaître non pas quel instant vient avant l’autre, mais de combien il s’en éloigne, je passe à diff().
Mesurer l’écart avec diff()
diff() ne répond pas à la question « laquelle est la plus grande ? », mais à la question « quelle est la distance entre les deux ? ». C’est idéal pour afficher un délai, calculer une ancienneté ou contrôler une fenêtre de validité.
$start = new DateTimeImmutable('2026-06-01 08:30:00', new DateTimeZone('Europe/Paris'));
$end = new DateTimeImmutable('2026-06-10 11:15:00', new DateTimeZone('Europe/Paris'));
$interval = $start->diff($end);
echo $interval->days; // 9
echo $interval->h; // 2
echo $interval->i; // 45Le point utile ici, c’est que DateInterval décrit un écart, mais ne remplace pas un vrai test d’ordre. Si je veux savoir si l’intervalle est négatif, j’inspecte aussi $interval->invert plutôt que d’interpréter le résultat à l’aveugle.
Pour un calcul vraiment indépendant du fuseau, je convertis d’abord les deux objets vers UTC avant d’appeler diff(). La documentation PHP insiste sur ce point, et dans les systèmes distribués c’est une précaution que je considère comme non négociable. Une fois ce cadre posé, le vrai sujet devient la conversion des chaînes d’entrée, et c’est souvent là que les problèmes commencent.
Quand strtotime() suffit encore et quand je l’évite
strtotime() reste pratique quand la chaîne est simple, connue et contrôlée. En revanche, dès que le format est ambigu ou que l’entrée vient d’un formulaire utilisateur, je préfère une méthode plus stricte. La documentation PHP précise d’ailleurs que cette fonction parse une description textuelle et qu’elle travaille avec le fuseau horaire par défaut si rien n’est explicite.
$a = strtotime('2026-06-01');
$b = strtotime('2026-06-10');
if ($a < $b) {
echo 'Ordre correct';
}Ce code fonctionne, mais il ne me rassure que dans un contexte très fermé. Le problème apparaît vite avec des formats comme 01/02/2026, qui peuvent prêter à confusion selon la convention attendue, ou avec des chaînes incomplètes qui héritent de composants implicites.
Quand le format est connu, je préfère createFromFormat() parce qu’il me force à expliciter l’attendu. C’est plus verbeux, mais beaucoup plus sain pour un formulaire en français ou un import CSV où la structure des données doit rester sous contrôle.
$input = '09/06/2026 14:30';
$date = DateTimeImmutable::createFromFormat(
'd/m/Y H:i',
$input,
new DateTimeZone('Europe/Paris')
);
$errors = DateTimeImmutable::getLastErrors();
if ($date === false || $errors['warning_count'] > 0 || $errors['error_count'] > 0) {
throw new RuntimeException('Date invalide');
}Je considère cette approche comme plus sérieuse parce qu’elle me donne un vrai contrat d’entrée. À partir de là, le point suivant n’est plus le parsing, mais la manière dont je traite le fuseau horaire et le fait de comparer une date seule ou un instant complet.
Gérer fuseaux horaires, heures et dates seules
Dans un projet web, je vois souvent deux besoins différents mélangés dans le même code : comparer une date de calendrier et comparer un moment précis. Ces deux logiques ne produisent pas les mêmes résultats, surtout dès qu’un horaire franchit minuit ou qu’un changement d’heure intervient.
Comparer une date sans tenir compte de l’heure
Si le métier ne s’intéresse qu’au jour, je normalise les deux objets sur une heure commune, souvent minuit, puis je compare une représentation stable. Une comparaison de type Y-m-d est alors plus claire qu’une comparaison brute des objets complets.
$tz = new DateTimeZone('Europe/Paris');
$day1 = (new DateTimeImmutable('2026-06-09 18:45:00', $tz))->setTime(0, 0, 0);
$day2 = (new DateTimeImmutable('2026-06-09 07:10:00', $tz))->setTime(0, 0, 0);
if ($day1->format('Y-m-d') === $day2->format('Y-m-d')) {
echo 'Même jour';
}Lire aussi : Gérer l'IP en PHP - Évitez les pièges, sécurisez vos apps
Comparer un instant absolu
Si je veux comparer l’instant réel, je fixe d’abord le fuseau horaire des deux valeurs, puis je les ramène dans une base commune, souvent UTC. C’est particulièrement important si l’application manipule des utilisateurs répartis sur plusieurs pays ou si les données viennent d’API différentes.
$paris = new DateTimeZone('Europe/Paris');
$utc = new DateTimeZone('UTC');
$a = (new DateTimeImmutable('2026-10-25 01:30:00', $paris))->setTimezone($utc);
$b = (new DateTimeImmutable('2026-10-25 02:30:00', $paris))->setTimezone($utc);
if ($a < $b) {
echo 'A précède B';
}Ce réglage paraît secondaire, mais c’est exactement le genre de détail qui crée des écarts de 1 heure, 24 heures ou plus lors d’un changement d’heure. Une fois ce cadre stabilisé, il reste à éliminer les erreurs de logique les plus courantes.
Les erreurs qui cassent une comparaison en silence
Les bugs de dates sont rarement spectaculaires. Ils ne font pas toujours planter l’application, mais ils faussent discrètement une validation, un tri ou une échéance. C’est pour cela que je les traite comme des erreurs de qualité, pas comme des détails.
| Erreur fréquente | Ce que cela provoque | Correction utile |
|---|---|---|
| Comparer des chaînes brutes | Ordre lexicographique au lieu d’un ordre chronologique | Convertir en DateTimeImmutable ou en timestamps |
Utiliser === pour tester une valeur temporelle |
On compare l’identité de l’objet, pas son contenu | Comparer les timestamps ou les dates normalisées |
| Oublier le fuseau horaire | Décalages invisibles entre serveur, utilisateur et API | Définir explicitement un fuseau, souvent UTC |
Passer du texte ambigu à strtotime()
|
Interprétation inattendue ou normalisation silencieuse | Privilégier createFromFormat() pour les entrées connues |
| Comparer des dates complètes alors que seul le jour compte | Résultats faux à cause de l’heure cachée | Neutraliser l’heure avant la comparaison |
Quand je relis ce type de code, je cherche toujours la même chose : quel est le contrat réel de la donnée, et est-ce que le code le respecte ? Cette discipline vaut mieux qu’un correctif tardif, parce que les dates touchent souvent la facturation, les accès, les délais ou les publications.
La règle pratique que j’applique dans un projet web
Si je devais résumer ma méthode en une règle simple, ce serait celle-ci : je normalise d’abord la donnée, puis je compare ensuite. En pratique, cela veut dire que je pars de DateTimeImmutable pour la plupart des cas, de diff() pour mesurer un écart, et de createFromFormat() dès que l’entrée doit être validée strictement.
- Pour un tri chronologique, j’utilise les opérateurs
<et>sur des objets déjà construits. - Pour une durée affichable, je passe par
diff()et j’extrais les unités utiles. - Pour un formulaire français, je pars d’un format explicite comme
d/m/Y H:i. - Pour un système distribué, je fixe le fuseau dès l’entrée, souvent en UTC côté stockage.
- Pour du code ancien, je garde
strtotime()seulement si je contrôle très bien les chaînes d’entrée.
Cette approche me paraît la plus saine en 2026 parce qu’elle réduit les ambiguïtés sans alourdir inutilement le code. Si je ne dois retenir qu’une chose, c’est qu’une comparaison de dates fiable commence toujours avant l’opérateur lui-même, au moment où l’on choisit le bon type, le bon format et le bon fuseau.