W3docs

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

Due cose da ricordare:

  • Modifica l'oggetto in modo diretto — il DateTime originale 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-10

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

TokenSignificatoEsempioDurata
YAnniP3Y3 anni
MMesiP6M6 mesi
DGiorniP10D10 giorni
HOrePT4H4 ore
MMinutiPT30M30 minuti
SSecondiPT45S45 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:00

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

Nota: DateInterval valida il formato in modo rigoroso. Una stringa non valida (ad esempio la mancanza della P, o PT senza token temporali) genera un'Exception. Se la durata proviene dall'input dell'utente, racchiudi il costruttore in un blocco try...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-05

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

Qui 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-15

Questo è 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-10

Conclusione

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.

Esercitazione

Pratica
Cosa fa DateTime::sub() con il DateInterval che gli viene passato?
Cosa fa DateTime::sub() con il DateInterval che gli viene passato?
Was this page helpful?