date_sub()
Apprenez à soustraire des durées d'une date en PHP avec DateTime::sub() et DateInterval, y compris les pièges courants à éviter.
Soustraire du temps à une date en PHP
Soustraire une durée d'une date — « il y a 30 jours », « le début du mois dernier », « deux heures avant maintenant » — est l'une des tâches les plus courantes de manipulation de dates en PHP. L'ancienne fonction procédurale date_sub() (alias de DateTime::sub()) a été supprimée en PHP 8.0, donc le code moderne doit utiliser la méthode orientée objet DateTime::sub() (ou DateTimeImmutable::sub()) conjointement avec un DateInterval.
Cette page explique comment DateTime::sub() fonctionne, comment construire la chaîne d'intervalle, la distinction mutable/immuable, et les pièges calendaires (dépassement de mois, intervalles inversés, heure d'été) qui piègent les développeurs.
Comment fonctionne DateTime::sub()
sub() prend un unique argument DateInterval qui décrit combien de temps retrancher. Sa signature est :
public DateTime::sub(DateInterval $interval): DateTimeDeux choses à retenir :
- Il mute l'objet en place — le
DateTimeoriginal est modifié. - Il retourne également le même objet, vous pouvez donc enchaîner les appels.
$date = new DateTime('2023-10-15', new DateTimeZone('UTC'));
$interval = new DateInterval('P5D'); // 5 days
$date->sub($interval);
echo $date->format('Y-m-d'); // Outputs: 2023-10-10Passer un DateTimeZone explicite rend le résultat prévisible ; sans cela, PHP utilise le fuseau horaire défini par date_default_timezone_set() ou php.ini.
Construire la chaîne DateInterval
DateInterval utilise le format de durée ISO 8601 : P[n]Y[n]M[n]DT[n]H[n]M[n]S. Le préfixe P (période) est obligatoire, et un T sépare la partie date de la partie heure.
| Jeton | Signification | Exemple | Durée |
|---|---|---|---|
Y | Années | P3Y | 3 ans |
M | Mois | P6M | 6 mois |
D | Jours | P10D | 10 jours |
H | Heures | PT4H | 4 heures |
M | Minutes | PT30M | 30 minutes |
S | Secondes | PT45S | 45 secondes |
Notez que M signifie mois avant le T et minutes après — une source fréquente de bugs. Combinez les jetons pour soustraire plusieurs unités à la fois :
$date = new DateTime('2023-10-15 12:00:00');
$interval = new DateInterval('P2M1DT3H'); // 2 months, 1 day, 3 hours
$date->sub($interval);
echo $date->format('Y-m-d H:i:s'); // Outputs: 2023-08-14 09:00:00Les intervalles de temps uniquement fonctionnent de la même manière — ici, 90 minutes :
$date = new DateTime('2023-10-15 08:30:00');
$date->sub(new DateInterval('PT90M')); // 90 minutes
echo $date->format('Y-m-d H:i:s'); // Outputs: 2023-10-15 07:00:00Remarque :
DateIntervalvalide strictement le format. Une chaîne invalide (par exemple unPmanquant, ouPTsans jetons de temps) lève uneException. Si la durée provient d'une entrée utilisateur, encapsulez le constructeur dans un bloctry...catch.
Mutable vs. immuable : privilégiez DateTimeImmutable
Parce que DateTime::sub() modifie l'objet en place, une date que vous faites circuler peut être modifiée de façon inattendue par du code qui appelle sub() dessus. DateTimeImmutable résout ce problème : son sub() laisse l'original intact et retourne une nouvelle instance.
$date = new DateTimeImmutable('2023-10-15');
$earlier = $date->sub(new DateInterval('P10D'));
echo $date->format('Y-m-d'); // Outputs: 2023-10-15 (unchanged)
echo "\n";
echo $earlier->format('Y-m-d'); // Outputs: 2023-10-05Pour la plupart du code applicatif, privilégiez DateTimeImmutable par défaut et n'utilisez le DateTime mutable que lorsque vous souhaitez spécifiquement des mises à jour en place.
Pièges à connaître
Dépassement de mois
Soustraire des mois entiers ne fixe pas la date au dernier jour du mois cible — PHP normalise le dépassement dans le mois suivant. Soustraire un mois au 31 mars atterrit en mars, pas en février :
$date = new DateTime('2023-03-31');
$date->sub(new DateInterval('P1M'));
echo $date->format('Y-m-d'); // Outputs: 2023-03-03Ici, P1M passe d'abord par « 31 février », que PHP repousse au 3 mars (février a 28 jours en 2023). Si vous avez besoin du dernier jour du mois précédent, utilisez plutôt une chaîne relative : new DateTime('last day of previous month').
Les intervalles inversés ajoutent au lieu de soustraire
Un DateInterval possède une propriété invert. Quand elle vaut 1, l'intervalle est négatif, donc sub() ajoute effectivement la durée :
$interval = new DateInterval('P1M');
$interval->invert = 1; // negative interval
$date = new DateTime('2023-10-15');
$date->sub($interval);
echo $date->format('Y-m-d'); // Outputs: 2023-11-15Cela importe lorsque vous réutilisez l'intervalle retourné par DateTime::diff(), qui définit invert automatiquement selon le sens de la différence.
Heure d'été
Lorsque vous soustrayez des jours en traversant une frontière d'heure d'été, PHP ajuste l'heure affichée pour que le résultat calendaire reste correct. Si à la place vous soustrayez des heures (PT24H), vous obtenez exactement 24 heures de temps écoulé, ce qui peut tomber sur une heure d'horloge différente. Choisissez délibérément les intervalles en jours ou en heures selon que vous voulez dire « même heure, jour précédent » ou « exactement 24 heures plus tôt ».
Utiliser Carbon pour une syntaxe fluide
Pour les grandes bases de code, la bibliothèque Carbon enveloppe DateTimeImmutable avec des helpers lisibles et chaînables. Elle est optionnelle — les classes natives couvrent les cas standards — mais elle peut rendre une logique complexe plus claire :
use Carbon\Carbon;
$date = Carbon::parse('2023-10-15');
$newDate = $date->subMonths(2)->subDays(5);
echo $newDate->toDateString(); // Outputs: 2023-08-10Conclusion
Utilisez DateTime::sub() (ou, de préférence, DateTimeImmutable::sub()) avec un DateInterval pour soustraire des durées d'une date. Construisez l'intervalle avec le format ISO 8601 P…T…, gardez à l'esprit la distinction M pour mois vs. minutes, et faites attention au dépassement de mois et aux intervalles inversés.
Pour aller plus loin, voir date_add() pour l'opération inverse, date_diff() pour mesurer l'écart entre deux dates, et date_format() pour formater le résultat.