Newsletter

Regex data: validare GG/MM/AAAA o AAAA-MM-GG in JavaScript e PHP

Una regex data che rifiuta il 30 febbraio e il 31 aprile, con le varianti ISO e americana, le funzioni JavaScript e PHP, l'attributo HTML e il momento in cui conviene lasciar perdere la regex.

Regex per validare una data: JavaScript, PHP e HTML
Risposta rapida

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

javascript
^(?:(?: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 |:

  1. 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.
  2. 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.
  3. 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.

Un solo separatore?

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.

validateDate.js
/**
 * 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')); // false

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

isValidDate.php
/**
 * 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.

isValidDateFr.php
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 $.

formulaire.html
<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.

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

isValidIsoDate.js
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')); // false

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

javascript
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 giorni

La 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 + Date o checkdate() 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

Mescolare i separatori La regex cattura il primo separatore e poi pretende lo stesso: «07/09-2026» viene rifiutato. È voluto, ma sorprende se un modulo accetta più formati di inserimento.
Dimenticare che new Date corregge in silenzio new Date(2026, 1, 30) non solleva errori: restituisce il 2 marzo 2026. Confronta sempre anno, mese e giorno dopo la costruzione.
Validare una data ISO con la regex francese 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.
Fidarsi del formato di visualizzazione del browser Un campo type="date" mostra «07/09/2026» in francese ma il valore trasmesso è «2026-09-07». La validazione lato server deve aspettarsi il formato ISO.

HTMLJavaScriptPHP

Damien Flandrin Sviluppatore web dal 2010, creatore di Gekkode e di Email Impact. Ogni articolo è testato su un progetto reale prima della pubblicazione. Contatti
Newsletter

I nuovi test, tutorial e progetti, via e-mail.

Test riproducibili, codice versionato, risultati datati. Mai spam.