date_sub()
Scopri come sottrarre durate dalle date in PHP con DateTime::sub() e DateInterval, incluse le insidie comuni come l'overflow dei mesi.
Sottrarre tempo da una data in PHP
Sottrarre una durata da una data — "30 giorni fa", "l'inizio del mese scorso", "due ore prima" — è una delle operazioni sulle date più comuni in PHP. La vecchia funzione procedurale date_sub() (alias di DateTime::sub()) è stata rimossa in PHP 8.0, quindi il codice moderno dovrebbe usare il metodo orientato agli oggetti DateTime::sub() (o DateTimeImmutable::sub()) insieme a un DateInterval.
Questa pagina illustra il funzionamento di DateTime::sub(), come costruire la stringa dell'intervallo, la distinzione tra oggetti mutabili e immutabili, e le insidie del calendario (overflow dei mesi, intervalli invertiti, ora legale) che creano spesso problemi.
Come funziona DateTime::sub()
sub() accetta un singolo argomento DateInterval che descrive quanto tempo rimuovere. La sua firma è:
public DateTime::sub(DateInterval $interval): DateTimeDue cose da ricordare:
- Modifica l'oggetto in modo diretto — il
DateTimeoriginale viene cambiato. - Restituisce lo stesso oggetto, quindi è possibile concatenare le chiamate.
$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-10Passare un DateTimeZone esplicito rende il risultato prevedibile; senza di esso, PHP utilizza il fuso orario impostato con date_default_timezone_set() o da php.ini.
Costruire la stringa DateInterval
DateInterval utilizza il formato di durata ISO 8601: P[n]Y[n]M[n]DT[n]H[n]M[n]S. Il prefisso P (period) è obbligatorio, e una T separa la parte relativa alla data da quella relativa all'ora.
| Token | Significato | Esempio | Durata |
|---|---|---|---|
Y | Anni | P3Y | 3 anni |
M | Mesi | P6M | 6 mesi |
D | Giorni | P10D | 10 giorni |
H | Ore | PT4H | 4 ore |
M | Minuti | PT30M | 30 minuti |
S | Secondi | PT45S | 45 secondi |
Nota che M indica mesi prima della T e minuti dopo — una fonte frequente di bug. Combina i token per sottrarre più unità contemporaneamente:
$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:00Gli intervalli solo temporali funzionano allo stesso modo — qui, 90 minuti:
$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:00Nota:
DateIntervalvalida il formato in modo rigoroso. Una stringa non valida (ad esempio la mancanza dellaP, oPTsenza token temporali) genera un'Exception. Se la durata proviene dall'input dell'utente, racchiudi il costruttore in un bloccotry...catch.
Mutabile vs. immutabile: preferire DateTimeImmutable
Poiché DateTime::sub() modifica l'oggetto direttamente, una data passata in giro può essere modificata in modo imprevisto da codice che chiama sub() su di essa. DateTimeImmutable risolve questo problema: il suo sub() lascia intatto l'originale e restituisce una nuova istanza.
$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-05Per la maggior parte del codice applicativo, usa DateTimeImmutable come impostazione predefinita e ricorri al DateTime mutabile solo quando vuoi specificamente aggiornamenti in loco.
Insidie da conoscere
Overflow dei mesi
La sottrazione di mesi interi non viene limitata all'ultimo giorno del mese di destinazione — PHP normalizza l'overflow nel mese successivo. Sottrarre un mese dal 31 marzo porta a marzo, non febbraio:
$date = new DateTime('2023-03-31');
$date->sub(new DateInterval('P1M'));
echo $date->format('Y-m-d'); // Outputs: 2023-03-03Qui P1M porta prima a "31 febbraio", che PHP converte al 3 marzo (febbraio ha 28 giorni nel 2023). Se hai bisogno dell'ultimo giorno del mese precedente, usa una stringa relativa: new DateTime('last day of previous month').
Gli intervalli invertiti aggiungono invece di sottrarre
Un DateInterval ha una proprietà invert. Quando vale 1, l'intervallo è negativo, quindi sub() in realtà aggiunge la durata:
$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-15Questo è rilevante quando riutilizzi l'intervallo restituito da DateTime::diff(), che imposta invert automaticamente in base alla direzione della differenza.
Ora legale
Quando si sottraggono giorni attraverso un confine di ora legale, PHP regola l'ora dell'orologio affinché il risultato calendario rimanga corretto. Se invece si sottraggono ore (PT24H), si ottengono esattamente 24 ore di tempo trascorso, che potrebbero corrispondere a un'ora dell'orologio diversa. Scegli deliberatamente intervalli basati su giorni o ore a seconda che tu voglia "la stessa ora, il giorno precedente" oppure "esattamente 24 ore prima".
Utilizzare Carbon per una sintassi fluente
Per codebase di grandi dimensioni, la libreria Carbon racchiude DateTimeImmutable con helper leggibili e concatenabili. È facoltativa — le classi native coprono i casi standard — ma può rendere più chiara la logica complessa:
use Carbon\Carbon;
$date = Carbon::parse('2023-10-15');
$newDate = $date->subMonths(2)->subDays(5);
echo $newDate->toDateString(); // Outputs: 2023-08-10Conclusione
Usa DateTime::sub() (o, preferibilmente, DateTimeImmutable::sub()) con un DateInterval per sottrarre durate da una data. Costruisci l'intervallo con il formato ISO 8601 P…T…, tieni a mente la distinzione tra mesi e minuti con M, e fai attenzione all'overflow dei mesi e agli intervalli invertiti.
Per approfondire, consulta date_add() per l'operazione inversa, date_diff() per misurare il divario tra due date, e date_format() per formattare il risultato.