Introduzione
Quando un’applicazione riceve una data da un modulo HTML, da un’API o da un file CSV, non è sufficiente convertirla automaticamente in un oggetto DateTime. Una stringa come 31/02/2026 è formalmente simile a una data, ma non rappresenta un giorno valido. Inoltre, PHP può correggere automaticamente alcune date non valide, producendo risultati inattesi.
Un caso pratico è un modulo per registrare una data di nascita, una data di consegna o una scadenza. L’utente potrebbe inserire il valore nel formato italiano dd/mm/YYYY, mentre il database potrebbe richiedere il formato standard YYYY-mm-dd. È quindi necessario:
- controllare che il formato sia esattamente quello atteso;
- verificare che la data esista davvero;
- gestire eventuali errori senza interrompere l’applicazione;
- convertire la data in un formato coerente per il salvataggio.
In questo tutorial utilizzeremo DateTimeImmutable, preferibile quando vogliamo evitare modifiche accidentali allo stesso oggetto data durante l’elaborazione.
Codice completo
<?php
/**
* Analizza una data nel formato italiano gg/mm/aaaa.
*
* @return array{
* valida: bool,
* data: ?DateTimeImmutable,
* errori: string[]
* }
*/
function analizzaDataItaliana(string $valore): array
{
$formato = ´!d/m/Y´;
// Crea una data usando esclusivamente il formato indicato.
$data = DateTimeImmutable::createFromFormat($formato, $valore);
// Recupera gli errori e gli avvisi generati dal parser.
$problemi = DateTimeImmutable::getLastErrors();
// Nelle versioni recenti di PHP, se non ci sono problemi,
// getLastErrors() può restituire false.
if ($problemi === false) {
$problemi = [
´warning_count´ => 0,
´warnings´ => [],
´error_count´ => 0,
´errors´ => [],
];
}
$errori = [];
foreach ($problemi[´warnings´] as $messaggio) {
$errori[] = $messaggio;
}
foreach ($problemi[´errors´] as $messaggio) {
$errori[] = $messaggio;
}
// Il confronto impedisce a PHP di accettare date "corrette"
// automaticamente, come 31/02/2026.
$formatoNormalizzato = $data !== false
? $data->format(´d/m/Y´)
: null;
if ($data === false || $formatoNormalizzato !== $valore) {
$errori[] = ´La data non rispetta il formato o non è valida.´;
}
return [
´valida´ => $errori === [],
´data´ => $errori === [] ? $data : null,
´errori´ => $errori,
];
}
$input = $_POST[´data´] ?? ´29/02/2028´;
$risultato = analizzaDataItaliana($input);
if (!$risultato[´valida´]) {
echo ´<p>Data non valida:</p>´;
echo ´<ul>´;
foreach ($risultato[´errori´] as $errore) {
echo ´<li>´ . htmlspecialchars($errore, ENT_QUOTES, ´UTF-8´) . ´</li>´;
}
echo ´</ul>´;
exit;
}
$data = $risultato[´data´];
echo ´<p>Data accettata: <strong>´
. htmlspecialchars($data->format(´d/m/Y´), ENT_QUOTES, ´UTF-8´)
. ´</strong></p>´;
echo ´<p>Formato per il database: <strong>´
. htmlspecialchars($data->format(´Y-m-d´), ENT_QUOTES, ´UTF-8´)
. ´</strong></p>´;
echo ´<p>Formato ISO 8601: <strong>´
. htmlspecialchars($data->format(DateTimeInterface::ATOM), ENT_QUOTES, ´UTF-8´)
. ´</strong></p>´;
?> Spiegazione
Usare createFromFormat()
Il metodo DateTimeImmutable::createFromFormat() permette di indicare esplicitamente il formato atteso. Nel nostro caso:
<?php
$data = DateTimeImmutable::createFromFormat(´!d/m/Y´, ´15/08/2026´);
echo $data->format(´Y-m-d´);
// 2026-08-15
?> La ! iniziale è importante: azzera i campi non presenti nella stringa, come ora, minuti e secondi. Senza di essa, alcuni valori potrebbero essere ereditati dalla data corrente o dal contesto del parser.
Perché controllare anche getLastErrors()
La creazione dell’oggetto non è l’unico controllo necessario. Il parser può generare avvisi per date come 31/02/2026. Inoltre, può tentare di correggere il valore trasformandolo in una data successiva. Per questo leggiamo il risultato di getLastErrors() e rifiutiamo ogni input che produca errori o avvisi.
Il confronto finale tra la stringa originale e quella riformattata è una seconda protezione:
<?php
$originale = ´31/02/2026´;
$data = DateTimeImmutable::createFromFormat(´!d/m/Y´, $originale);
if ($data !== false && $data->format(´d/m/Y´) === $originale) {
echo ´Data valida´;
} else {
echo ´Data non valida´;
}
?> Una data come 29/02/2028, invece, viene accettata perché il 2028 è un anno bisestile. La stessa data con l’anno 2027 deve essere rifiutata.
Separare visualizzazione e salvataggio
È consigliabile mostrare all’utente il formato locale dd/mm/YYYY, ma salvare nel database il formato YYYY-mm-dd. In questo modo i dati sono più semplici da ordinare, confrontare e filtrare tramite SQL.
Per esempio, con PDO si può usare una query parametrizzata:
<?php
$sql = ´INSERT INTO ordini (data_consegna) VALUES (:data_consegna)´;
$stmt = $pdo->prepare($sql);
$stmt->execute([
´data_consegna´ => $data->format(´Y-m-d´),
]);
?> Best practice
- Non affidarti a strtotime() per input ambigui: stringhe come
03/04/2026possono essere interpretate in modi diversi. Usa un formato esplicito. - Valida sempre lato server: i controlli JavaScript migliorano l’esperienza utente, ma non sostituiscono la validazione PHP.
- Usa DateTimeImmutable: ogni operazione come
modify()restituisce un nuovo oggetto, riducendo gli effetti collaterali. - Salva date e orari in formato standard: per le sole date usa
Y-m-d; per data e ora considera ISO 8601 o un formato coerente con il database. - Gestisci l’output con htmlspecialchars(): i messaggi derivati dall’input dell’utente non devono essere stampati direttamente in HTML.
- Non confondere data e orario: una data di nascita non necessita necessariamente di un fuso orario, mentre un appuntamento online sì.
- Conserva il valore originale solo per la visualizzazione degli errori: per l’elaborazione usa esclusivamente dati già validati.
Riepilogo
La gestione affidabile delle date in PHP richiede più di una semplice conversione. Con createFromFormat() possiamo definire il formato ammesso; con getLastErrors() possiamo intercettare errori e avvisi; confrontando la stringa originale con quella riformattata impediamo al parser di correggere silenziosamente date impossibili.
Il flusso consigliato è quindi: ricevere la stringa, validarla, creare un DateTimeImmutable, convertire il valore nel formato necessario e solo dopo utilizzarlo o salvarlo nel database.
