
date() formatta un timestamp, DateTimeImmutable serve appena c'è un calcolo, IntlDateFormatter dà i nomi dei giorni e dei mesi in francese. Imposta il fuso in modo esplicito: senza date.timezone, PHP usa UTC. strftime() è deprecata da PHP 8.1 e il suo sostituto non è date(), che non traduce nulla.
Mostrare una data in PHP è semplice. Mostrare la data giusta, nel fuso giusto, in francese, e calcolare un intervallo senza sbagliare al cambio dell’ora è tutt’altra cosa. Questo articolo copre le funzioni di base, poi le classi DateTimeImmutable e IntlDateFormatter che risolvono i casi che date() non sa trattare. Tutti gli output sono stati prodotti su PHP 8.5.10.
Il fuso orario, prima di ogni altra cosa
È la causa di bug numero uno, e per giunta silenziosa. Se date.timezone non è impostato in php.ini, PHP usa UTC. Un sito francese mostra allora un orario sfasato di una o due ore a seconda della stagione, senza il minimo avviso.
echo ini_get('date.timezone'), "\n"; // (vuoto) su molte installazioni
echo date_default_timezone_get(), "\n"; // UTCL’impostazione si fa in php.ini, oppure all’avvio dell’applicazione:
date_default_timezone_set('Europe/Paris');La regola di fondo: memorizza in UTC, mostra nel fuso dell’utente. Un database che contiene orari locali diventa inutilizzabile appena un utente cambia fuso o scatta l’ora legale.
date() e time()
time() restituisce il numero di secondi trascorsi dal 1º gennaio 1970 a mezzanotte UTC. date() formatta un timestamp di questo tipo, o l’istante corrente se non gliene passi uno.
date_default_timezone_set('Europe/Paris');
echo date('d/m/Y'), "\n"; // 02/09/2026
echo date('d/m/Y H:i:s'), "\n"; // 02/09/2026 21:47:03
echo time(), "\n"; // 1788378423
echo date('d/m/Y', 1645303037), "\n"; // 19/02/2022I caratteri di formato più usati:
| Data | Ora | ||
|---|---|---|---|
d | giorno a 2 cifre (01 a 31) | H | ora su 24 h (00 a 23) |
j | giorno senza zero iniziale (1 a 31) | h | ora su 12 h (01 a 12) |
m | mese a 2 cifre (01 a 12) | i | minuti (00 a 59) |
n | mese senza zero iniziale (1 a 12) | s | secondi (00 a 59) |
Y | anno a 4 cifre | A | AM o PM |
N | giorno della settimana (1 = lunedì) | P | scarto, per esempio +02:00 |
t | numero di giorni del mese | U | timestamp Unix |
L | 1 se l’anno è bisestile | c | formato ISO 8601 completo |
Per inserire un carattere che è anche un codice di formato bisogna fargli l’escape con una barra rovesciata:
echo date('\L\e d/m/Y'), "\n"; // Le 02/09/2026
// senza barra rovesciata, «L» darebbe 0 o 1 ed «e» il nome del fusodate() non parla francese
È la seconda trappola classica. I nomi dei giorni e dei mesi restituiti da date() sono sempre in inglese, e setlocale() non cambia nulla.
$ts = mktime(0, 0, 0, 4, 1, 2022);
echo date('l', $ts), "\n"; // Friday
setlocale(LC_TIME, 'fr_FR.UTF-8', 'fr_FR');
echo date('l', $ts), "\n"; // Friday — invariatoLa soluzione è IntlDateFormatter, dell’estensione intl:
$formateur = new IntlDateFormatter(
'fr_FR',
IntlDateFormatter::FULL,
IntlDateFormatter::NONE,
'Europe/Paris',
IntlDateFormatter::GREGORIAN,
);
echo $formateur->format($ts), "\n"; // vendredi 1 avril 2022Per un formato personalizzato la classe accetta un pattern ICU, i cui codici non sono quelli di date():
$formateur->setPattern("EEEE d MMMM y '\u{e0}' HH'h'mm");
echo $formateur->format(new DateTimeImmutable('now', new DateTimeZone('Europe/Paris')));
// mercredi 2 septembre 2026 à 21h47Se intl non è disponibile, una tabella di corrispondenze toglie d’impaccio per il francese, ma non regge su più lingue.
DateTimeImmutable, la classe da usare
Per tutto ciò che va oltre la semplice visualizzazione, le classi valgono più delle funzioni. DateTimeImmutable è preferibile a DateTime: i suoi metodi restituiscono un nuovo oggetto invece di modificare quello corrente, il che elimina le modifiche a distanza.
$paris = new DateTimeZone('Europe/Paris');
$date = new DateTimeImmutable('2026-09-02 14:30:00', $paris);
echo $date->format('d/m/Y H:i P'), "\n"; // 02/09/2026 14:30 +02:00
echo $date->modify('+3 days')->format('d/m/Y'), "\n"; // 05/09/2026
echo $date->format('d/m/Y'), "\n"; // 02/09/2026 — invariato
// Con DateTime (mutabile), la terza riga mostrerebbe 05/09/2026// Aggiungere e togliere durate
$dans30Mois = $date->add(new DateInterval('P30M'));
$ilYa2Sem = $date->sub(new DateInterval('P2W'));
// Differenza tra due date
$ecart = $date->diff(new DateTimeImmutable('2026-12-25', $paris));
echo $ecart->days, " jours\n";
echo $ecart->format('%m mois et %d jours'), "\n";
// Una serie di date
$periode = new DatePeriod(
$date,
new DateInterval('P1D'),
new DateTimeImmutable('2026-09-07', $paris),
);
foreach ($periode as $jour) {
echo $jour->format('D d/m'), "\n";
}La trappola dell’ultimo giorno del mese
Aggiungere un mese a una fine mese non dà quello che ti aspetti, e il comportamento è lo stesso con DateInterval e con l’aritmetica su mktime().
$fin = new DateTimeImmutable('2026-01-31');
echo $fin->add(new DateInterval('P1M'))->format('d/m/Y'), "\n"; // 03/03/2026Il 31 febbraio non esiste: PHP lo fa traboccare su marzo. Per ottenere l’ultimo giorno del mese successivo devi chiederlo esplicitamente:
echo $fin->modify('last day of next month')->format('d/m/Y'), "\n"; // 28/02/2026Un giorno non dura sempre 24 ore
Al passaggio all’ora legale un giorno ne dura 23, al ritorno all’ora solare, 25. Aggiungere 86400 secondi a un timestamp dà quindi un orario sbagliato.
$paris = new DateTimeZone('Europe/Paris');
$avant = new DateTimeImmutable('2026-03-28 12:00:00', $paris);
$apres = $avant->add(new DateInterval('P1D'));
echo $apres->format('d/m/Y H:i P'), "\n"; // 29/03/2026 12:00 +02:00
echo ($apres->getTimestamp() - $avant->getTimestamp()) / 3600, " heures\n"; // 23DateInterval ragiona in giorni di calendario e restituisce correttamente mezzogiorno del giorno dopo, anche se sono passate solo 23 ore. È il comportamento giusto per un promemoria «domani a mezzogiorno», e quello sbagliato per una durata fatturata. Sono due nozioni distinte: P1D per un giorno di calendario, PT24H per ventiquattro ore.
Leggere una data inserita dall’utente
strtotime() capisce molte cose, ma interpreta i formati ambigui all’americana: 03/04/2026 diventa il 4 marzo, non il 3 aprile. Quando il formato è noto, createFromFormat() non lascia spazio a dubbi.
$date = DateTimeImmutable::createFromFormat(
'!d/m/Y', // il «!» azzera l'ora a 00:00:00
'03/04/2026',
new DateTimeZone('Europe/Paris'),
);
echo $date->format('d F Y'), "\n"; // 03 April 2026Senza il ! iniziale, i campi che non fornisci prendono il valore dell’istante corrente: due esecuzioni dello stesso codice danno due orari diversi.
Il punto più pericoloso: una data non valida non fallisce, trabocca in silenzio.
$saisie = '31/02/2026';
$date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $paris);
echo $date->format('d/m/Y'), "\n"; // 03/03/2026
print_r(DateTimeImmutable::getLastErrors()); // ['warnings' => [10 => 'The parsed date was invalid'], …]
var_dump(checkdate(2, 31, 2026)); // bool(false)Devi quindi controllare getLastErrors() dopo ogni analisi, oppure confrontare la data riformattata con quella inserita:
function lireDate(string $saisie, DateTimeZone $fuseau): ?DateTimeImmutable
{
$date = DateTimeImmutable::createFromFormat('!d/m/Y', $saisie, $fuseau);
if ($date === false) {
return null;
}
$erreurs = DateTimeImmutable::getLastErrors();
if ($erreurs !== false && ($erreurs['warning_count'] || $erreurs['error_count'])) {
return null;
}
return $date->format('d/m/Y') === $saisie ? $date : null;
}Da PHP 8.2, getLastErrors() restituisce false quando non c’è nulla da segnalare, invece di un array con i contatori a zero. Il controllo deve tenerne conto.
Novità recenti
PHP 8.4 ha aggiunto createFromTimestamp(), che accetta i timestamp frazionari:
$d = DateTimeImmutable::createFromTimestamp(1645303037.5);
echo $d->format('d/m/Y H:i:s.u P'), "\n"; // 19/02/2022 20:37:17.500000 +00:00Prima bisognava passare da '@' . $ts o setTimestamp(), e i microsecondi andavano persi.
Le funzioni deprecate da non usare più
Diverse funzioni di data ancora presenti emettono un avviso di deprecazione. Su PHP 8.5.10 funzionano, ma la loro rimozione è già programmata.
| Funzione | Deprecata da | Sostituto |
|---|---|---|
strftime() | PHP 8.1 | IntlDateFormatter::format() |
gmstrftime() | PHP 8.1 | IntlDateFormatter::format() |
strptime() | PHP 8.2 | date_parse_from_format() o IntlDateFormatter::parse() |
date_sunrise() / date_sunset() | PHP 8.1 | date_sun_info() |
utf8_encode() | PHP 8.2 | mb_convert_encoding() |
I messaggi esatti, rilevati sia su PHP 8.5.10 sia su PHP 8.4.25:
Deprecated: Function strftime() is deprecated since 8.1,
use IntlDateFormatter::format() instead
Deprecated: Function strptime() is deprecated since 8.2,
use date_parse_from_format() (for locale-independent parsing),
or IntlDateFormatter::parse() (for locale-dependent parsing) instead
Deprecated: Constant SUNFUNCS_RET_STRING is deprecated since 8.4,
as date_sunrise() and date_sunset() were deprecated in 8.1strftime() era il modo abituale di ottenere una data in francese. Il suo sostituto non è date(), che non traduce nulla, ma IntlDateFormatter.
Al contrario, mktime(), checkdate(), getdate() e idate() non sono deprecate e funzionano normalmente. Restano utili per i casi semplici, anche se le classi coprono tutto quello che fanno.
Da ricordare
- Imposta il fuso in modo esplicito, memorizza in UTC, mostra in ora locale.
date()per formattare un istante,DateTimeImmutableper qualsiasi calcolo.IntlDateFormatterper i nomi dei giorni e dei mesi in francese.- Dopo
createFromFormat(), controllagetLastErrors(): una data non valida trabocca in silenzio. - Un giorno di calendario non è 24 ore.
P1DePT24Hnon sono intercambiabili.
Per analizzare date a milioni in un file di importazione, vedi leggere file di grandi dimensioni con PHP, e la connessione a un database per memorizzarle in UTC.
Vedi anche validare una data con un’espressione regolare, gestire le date con Carbon in Laravel e l’hub Sviluppo web.
Errori frequenti
date.timezone in php.ini, PHP ricade su UTC e mostra un orario sfasato senza il minimo avviso.setlocale() non ha alcun effetto su date(). Il sostituto di strftime() è IntlDateFormatter, non date().31/02/2026 diventa 03/03/2026 con un semplice avviso in getLastErrors(). Va controllato dopo ogni analisi.03/04/2026 diventa il 4 marzo, non il 3 aprile.31/01/2026 + P1M dà 03/03/2026. Per l'ultimo giorno del mese, usa modify('last day of next month').P1D avanza di un giorno di calendario anche se sono passate solo 23 ore. P1D e PT24H non sono intercambiabili.false quando non c'è nulla da segnalare, non più un array di contatori a zero.

