W3docs

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): DateTime

Deux choses à retenir :

  • Il mute l'objet en place — le DateTime original 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-10

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

JetonSignificationExempleDurée
YAnnéesP3Y3 ans
MMoisP6M6 mois
DJoursP10D10 jours
HHeuresPT4H4 heures
MMinutesPT30M30 minutes
SSecondesPT45S45 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:00

Les 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:00

Remarque : DateInterval valide strictement le format. Une chaîne invalide (par exemple un P manquant, ou PT sans jetons de temps) lève une Exception. Si la durée provient d'une entrée utilisateur, encapsulez le constructeur dans un bloc try...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-05

Pour 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-03

Ici, 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-15

Cela 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-10

Conclusion

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.

Pratique

Pratique
Que fait DateTime::sub() avec le DateInterval que vous lui passez ?
Que fait DateTime::sub() avec le DateInterval que vous lui passez ?
Was this page helpful?