
La regex qui sotto valida una data francese GG/MM/AAAA (separatore /, - o .) e rifiuta le date impossibili come il 30 febbraio o il 31 aprile, anni bisestili compresi. Per un formato ISO AAAA-MM-GG basta una regex breve, purché tu confermi poi con l'oggetto Date in JavaScript o con checkdate() in PHP.
Validare una data digitata a mano è più delicato di quanto sembri: «30/02/2026» ha la forma giusta e non esiste. La regex data qui sotto verifica il formato GG/MM/AAAA, con /, - o . come separatore, e rifiuta i giorni impossibili, compreso il 29 febbraio degli anni non bisestili. Seguono poi le versioni JavaScript, PHP e HTML, la variante ISO e il caso in cui l’oggetto Date fa meglio di una regex.
La regex data pronta da copiare
^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$È lunga perché ragiona come un calendario. Tre rami, separati da |:
- Il 31 esiste solo nei mesi di 31 giorni (
01, 03, 05, 07, 08, 10, 12), mentre il 29 e il 30 valgono in tutti i mesi tranne febbraio. - Il 29 febbraio è accettato solo se l’anno è bisestile: divisibile per 4 (
0[48]|[2468][048]|[13579][26]), oppure secolo divisibile per 400 ((?:16|[2468][048]|[3579][26])00). Il 2000 e il 2024 passano, il 1900 e il 2023 vengono rifiutati. - Dall’1 al 28, tutti i mesi accettano la data.
Le retroreferenze \1, \2, \3 e \4 impongono che il secondo separatore sia identico al primo: 07/09/2026 e 07-09-2026 passano, 07/09-2026 no.
Per accettare soltanto la barra obliqua, sostituisci ogni (\/|-|\.) con (\/). Le parentesi restano necessarie: le retroreferenze da \1 a \4 puntano a quei gruppi.
Le date testate
Risultato reale della regex su Node.js 22 e PHP 8.5, identico nei due motori:
| Valore inserito | Risultato | Motivo |
|---|---|---|
31/12/2026 | accettata | il 31 in un mese di 31 giorni |
29/02/2024 | accettata | il 2024 è bisestile |
29/02/2000 | accettata | il 2000 è divisibile per 400 |
01/01/2026 e 1/1/2026 | accettate | lo zero iniziale è facoltativo |
07-09-2026 e 07.09.2026 | accettate | altri separatori ammessi |
29/02/2023 | rifiutata | il 2023 non è bisestile |
29/02/1900 | rifiutata | secolo non divisibile per 400 |
30/02/2026 | rifiutata | febbraio non ha mai 30 giorni |
31/04/2026 | rifiutata | aprile ha 30 giorni |
00/01/2026 | rifiutata | il giorno 0 non esiste |
12/13/2026 | rifiutata | mese 13: la regex è davvero GG/MM e non MM/GG |
07/09-2026 | rifiutata | separatori diversi |
2026-09-07 | rifiutata | formato ISO, vedi più avanti |
07/09/26 e 31/12/1899 | accettate | anno a due cifre o anteriore al 1900: se serve, limitalo nel tuo codice |
Versione JavaScript
La funzione ancora la regex con ^ e $, elimina gli spazi attorno al valore inserito e restituisce un booleano.
/**
* Verifica una data nel formato GG/MM/AAAA, GG-MM-AAAA o GG.MM.AAAA.
* @param {string} date
* @returns {boolean}
*/
function validateDate(date) {
const reg = /^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/;
return reg.test(String(date).trim());
}
console.log(validateDate('29/02/2024')); // true
console.log(validateDate('29/02/2023')); // false
console.log(validateDate('31/04/2026')); // falseIn un modulo lo schema è lo stesso della validazione di un’e-mail: ascoltare submit, chiamare preventDefault() se la data non è valida, mostrare il messaggio accanto al campo.
Versione PHP
Il controllo lato server è obbligatorio: JavaScript si può disattivare. Lo stesso pattern funziona con preg_match(); le retroreferenze si scrivono \\1 in una stringa PHP tra apici singoli.
/**
* Verifica una data nel formato GG/MM/AAAA, GG-MM-AAAA o GG.MM.AAAA.
*/
function isValidDate(string $date): bool
{
$pattern = '/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/';
return preg_match($pattern, trim($date)) === 1;
}
var_dump(isValidDate('29/02/2024')); // bool(true)
var_dump(isValidDate('30/02/2026')); // bool(false)L’alternativa senza regex: checkdate()
PHP sa già dire se una terna giorno, mese, anno esiste. Una piccola regex per spezzare la stringa, poi checkdate() per giudicare: è più corto e più leggibile.
function isValidDateFr(string $date): bool
{
if (!preg_match('#^(\d{1,2})[/.-](\d{1,2})[/.-](\d{4})$#', trim($date), $m)) {
return false;
}
return checkdate((int) $m[2], (int) $m[1], (int) $m[3]); // mese, giorno, anno
}
var_dump(isValidDateFr('29/02/2024')); // bool(true)
var_dump(isValidDateFr('29/02/2023')); // bool(false)
var_dump(isValidDateFr('31/04/2026')); // bool(false)Attenzione a DateTime::createFromFormat('d/m/Y', '29/02/2023'): non restituisce false ma il 1o marzo 2023, e segnala lo scarto in DateTime::getLastErrors(). Per un controllo severo, checkdate() è più sicuro.
Versione HTML
Se l’utente digita la data in testo libero, l’attributo pattern applica la regex prima dell’invio. È ancorato in modo implicito: si tolgono ^ e $.
<label for="naissance">Date de naissance (JJ/MM/AAAA)</label>
<input
id="naissance"
name="naissance"
type="text"
inputmode="numeric"
placeholder="JJ/MM/AAAA"
pattern="(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})|(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})"
title="Une date valide au format JJ/MM/AAAA"
required
>Ma la soluzione HTML migliore non è la regex: è type="date". Il browser mostra un selettore adatto alla lingua dell’utente, impedisce il 30 febbraio e trasmette sempre il valore nel formato ISO AAAA-MM-GG, qualunque sia la visualizzazione.
<input id="naissance" name="naissance" type="date" min="1900-01-01" max="2026-12-31" required>Conseguenza lato server: con type="date" va validato il formato ISO, non il formato francese.
Variante ISO AAAA-MM-GG
Per una data ISO basta una regex breve a controllare la forma, e l’oggetto Date conferma che il giorno esiste. Insieme sostituiscono la regex grande.
function isValidIsoDate(value) {
const m = /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/.exec(value);
if (!m) return false;
const [, y, mo, d] = m.map(Number);
const date = new Date(y, mo - 1, d);
// new Date(2026, 1, 30) restituisce il 2 marzo: verifichiamo che nulla sia slittato
return date.getFullYear() === y && date.getMonth() === mo - 1 && date.getDate() === d;
}
console.log(isValidIsoDate('2026-09-07')); // true
console.log(isValidIsoDate('2026-02-30')); // false
console.log(isValidIsoDate('2026-13-01')); // falseLa stessa tecnica vale per il formato francese: spezzare con /^(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{4})$/, poi verificare con Date. È l’equivalente JavaScript del checkdate() di PHP.
E il formato americano MM/GG/AAAA?
La regex dell’articolo è francese: prima il giorno, poi il mese. Per un pubblico americano, inverti i primi due blocchi oppure, più semplice, normalizza tutto in ISO fin dall’inserimento con type="date". Un’applicazione che accetta i due ordini senza distinguerli sbaglia una volta su due per tutte le date dall’1 al 12.
Confrontare due date dopo la validazione
Una volta validata la data e convertita in oggetto Date, i confronti sono diretti: gli operatori < e > confrontano gli istanti, e la sottrazione dà uno scarto in millisecondi.
const debut = new Date('2026-09-07');
const fin = new Date('2026-12-25');
console.log(debut < fin); // true
console.log(Math.round((fin - debut) / 86_400_000)); // 109 giorniLa formattazione per la visualizzazione (toLocaleDateString, Intl.DateTimeFormat) è un altro argomento, come la data e l’ora in PHP o Carbon in Laravel.
Regex o oggetto Date: che cosa scegliere?
- La regex grande quando vuoi un’unica regola, identica in JavaScript, PHP e HTML, senza dipendere da una libreria né da un oggetto
Date. - Spezzare la stringa +
Dateocheckdate()quando conta la leggibilità, o quando devi poi manipolare la data: ce l’hai già sotto forma di oggetto. type="date"per qualsiasi modulo moderno: l’inserimento è guidato, il valore normalizzato, e resta solo il controllo ISO lato server.
Le altre validazioni della serie: indirizzo e-mail, indirizzo IP, codice postale e password.
Errori frequenti
new Date(2026, 1, 30) non solleva errori: restituisce il 2 marzo 2026. Confronta sempre anno, mese e giorno dopo la costruzione.2026-09-07 viene rifiutato dalla regex GG/MM/AAAA. Usa la variante ISO dell'articolo o, meglio, input type="date" che restituisce già questo formato.type="date" mostra «07/09/2026» in francese ma il valore trasmesso è «2026-09-07». La validazione lato server deve aspettarsi il formato ISO.

