Regex date : valider une date au format JJ/MM/AAAA ou AAAA-MM-JJ (JavaScript, PHP, HTML)

Une regex date qui refuse le 30 février et le 31 avril, avec les variantes ISO et américaine, les fonctions JavaScript et PHP, l’attribut HTML, et le moment où il vaut mieux laisser la regex de côté.

Expression régulière pour valider une date
Réponse rapide

La regex ci-dessous valide une date française JJ/MM/AAAA (séparateur /, - ou .) et refuse les dates impossibles comme le 30 février ou le 31 avril, années bissextiles comprises. Pour un format ISO AAAA-MM-JJ, une regex courte suffit si vous confirmez ensuite avec l’objet Date en JavaScript ou checkdate() en PHP.

Valider une date saisie à la main est plus délicat qu’il n’y paraît : « 30/02/2026 » a la bonne forme et n’existe pas. La regex date ci-dessous vérifie le format JJ/MM/AAAA, avec /, - ou . comme séparateur, et refuse les jours impossibles, y compris le 29 février des années non bissextiles. Vous trouverez ensuite les versions JavaScript, PHP et HTML, la variante ISO et le cas où l’objet Date fait mieux qu’une regex.

La regex date prête à copier

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})$

Elle est longue parce qu’elle raisonne comme un calendrier. Trois branches, séparées par | :

  1. Le 31 n’existe que dans les mois de 31 jours (01, 03, 05, 07, 08, 10, 12), et le 29 et le 30 dans tous les mois sauf février.
  2. Le 29 février n’est accepté que si l’année est bissextile : divisible par 4 (0[48]|[2468][048]|[13579][26]), ou siècle divisible par 400 ((?:16|[2468][048]|[3579][26])00). 2000 et 2024 passent, 1900 et 2023 sont refusées.
  3. Du 1 au 28, tous les mois acceptent la date.

Les références \1, \2, \3 et \4 imposent que le second séparateur soit identique au premier : 07/09/2026 et 07-09-2026 passent, 07/09-2026 non.

Un seul séparateur ?

Pour n’accepter que la barre oblique, remplacez chaque (\/|-|\.) par (\/). Les parenthèses restent nécessaires : les références arrière \1 à \4 pointent vers ces groupes.

Les dates testées

Résultat réel de la regex sous Node.js 22 et PHP 8.5, identique dans les deux moteurs :

Saisie Résultat Raison
31/12/2026 acceptée 31 dans un mois de 31 jours
29/02/2024 acceptée 2024 est bissextile
29/02/2000 acceptée 2000 est divisible par 400
01/01/2026 et 1/1/2026 acceptées le zéro initial est optionnel
07-09-2026 et 07.09.2026 acceptées autres séparateurs autorisés
29/02/2023 refusée 2023 n’est pas bissextile
29/02/1900 refusée siècle non divisible par 400
30/02/2026 refusée février n’a jamais 30 jours
31/04/2026 refusée avril a 30 jours
00/01/2026 refusée le jour 0 n’existe pas
12/13/2026 refusée mois 13 : la regex est bien JJ/MM et non MM/JJ
07/09-2026 refusée séparateurs différents
2026-09-07 refusée format ISO, voir plus bas
07/09/26 et 31/12/1899 acceptées année sur deux chiffres ou avant 1900 : à borner dans votre code si besoin

Version JavaScript

La fonction ancre la regex avec ^ et $, retire les espaces autour de la saisie et renvoie un booléen.

validateDate.js
/**
 * Vérifie une date au format JJ/MM/AAAA, JJ-MM-AAAA ou JJ.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

Dans un formulaire, le schéma est le même que pour la validation d’un e-mail : écouter submit, appeler preventDefault() si la date est invalide, afficher le message à côté du champ.

Version PHP

Le contrôle côté serveur est obligatoire : le JavaScript se désactive. Le même motif fonctionne avec preg_match(), les références arrière s’écrivent \\1 dans une chaîne PHP entre apostrophes.

isValidDate.php
/**
 * Vérifie une date au format JJ/MM/AAAA, JJ-MM-AAAA ou JJ.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’alternative sans regex : checkdate()

PHP sait déjà dire si un triplet jour, mois, année existe. Une petite regex pour découper, puis checkdate() pour juger : c’est plus court et plus lisible.

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]); // mois, jour, année
}

var_dump(isValidDateFr('29/02/2024')); // bool(true)
var_dump(isValidDateFr('29/02/2023')); // bool(false)
var_dump(isValidDateFr('31/04/2026')); // bool(false)

Attention à DateTime::createFromFormat('d/m/Y', '29/02/2023') : il ne renvoie pas false mais le 1er mars 2023, en signalant l’écart dans DateTime::getLastErrors(). Pour un contrôle strict, checkdate() est plus sûr.

Version HTML

Si l’utilisateur saisit la date en texte libre, l’attribut pattern applique la regex avant l’envoi. Il est implicitement ancré : on retire ^ et $.

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
>

Mais la meilleure solution HTML n’est pas la regex : c’est type="date". Le navigateur affiche un sélecteur adapté à la langue de l’utilisateur, empêche le 30 février, et transmet toujours la valeur au format ISO AAAA-MM-JJ, quel que soit l’affichage.

html
<input id="naissance" name="naissance" type="date" min="1900-01-01" max="2026-12-31" required>

Conséquence côté serveur : avec type="date", c’est le format ISO qu’il faut valider, pas le format français.

Variante ISO AAAA-MM-JJ

Pour une date ISO, une regex courte contrôle la forme, et l’objet Date confirme que le jour existe. Les deux ensemble remplacent la grande regex.

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) renvoie le 2 mars : on vérifie que rien n'a glissé
  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 même technique s’applique au format français : découper avec /^(\d{1,2})[\/.-](\d{1,2})[\/.-](\d{4})$/, puis vérifier avec Date. C’est l’équivalent JavaScript du checkdate() de PHP.

Et le format américain MM/JJ/AAAA ?

La regex de l’article est française : jour puis mois. Pour un public américain, inversez les deux premiers blocs ou, plus simple, normalisez tout en ISO dès la saisie avec type="date". Une application qui accepte les deux ordres sans les distinguer se trompe une fois sur deux pour toutes les dates du 1 au 12.

Comparer deux dates après validation

Une fois la date validée et convertie en objet Date, les comparaisons sont directes : les opérateurs < et > comparent les instants, et la soustraction donne un écart en millisecondes.

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 jours

Le formatage pour l’affichage (toLocaleDateString, Intl.DateTimeFormat) est un autre sujet, comme la date et l’heure en PHP ou Carbon dans Laravel.

Regex ou objet Date : que choisir ?

  • La grande regex quand vous voulez une seule règle, identique en JavaScript, PHP et HTML, sans dépendre d’une bibliothèque ni d’un objet Date.
  • Découpage + Date ou checkdate() quand la lisibilité compte, ou quand vous devez ensuite manipuler la date : vous l’avez déjà sous forme d’objet.
  • type="date" pour tout formulaire moderne : la saisie est guidée, la valeur normalisée, et il ne reste que le contrôle ISO côté serveur.

Les autres validations de la série : adresse e-mail, adresse IP, code postal et mot de passe.

Erreurs fréquentes

Mélanger les séparateurs La regex capture le premier séparateur et exige le même ensuite : « 07/09-2026 » est refusé. C’est voulu, mais surprenant si un formulaire accepte plusieurs saisies.
Oublier que new Date corrige silencieusement new Date(2026, 1, 30) ne lève pas d’erreur : elle renvoie le 2 mars 2026. Comparez toujours année, mois et jour après construction.
Valider une date ISO avec la regex française 2026-09-07 est refusé par la regex JJ/MM/AAAA. Utilisez la variante ISO de l’article, ou mieux, input type="date" qui renvoie déjà ce format.
Se fier au format d’affichage du navigateur Un champ type="date" affiche « 07/09/2026 » en français mais sa valeur transmise est « 2026-09-07 ». La validation côté serveur doit attendre le format ISO.

HTMLJavaScriptPHP

Damien Flandrin Développeur web depuis 2010, créateur de Gekkode et d’Email Impact. Chaque article est testé sur un projet réel avant publication. Contact
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.