Regex French date (dd/mm/yyyy)
The reference Stack Overflow question (267 votes) starts from a concrete problem: a regex with no leap-year logic validated "31/02/4500" as a correct date. The top-voted answer (450 votes) fixes that specific point. But actually testing two other answers from the same thread turns up — in each, independently — a forgotten comma inside a character class, reopening a flaw similar to one the top answer's own author had fixed in their edit history. Here's a reliable pattern, compared against these 3 real attempts.
/^(?:31\/(?:0?[13578]|1[02])\/|(?:29|30)\/(?:0?[13-9]|1[0-2])\/)(?:1[6-9]|[2-9]\d)\d{2}$|^29\/0?2\/(?:(?: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])\/(?:1[6-9]|[2-9]\d)\d{2}$/passes 28/28 cases
Comparison of existing patterns
| Source | pattern | Score on the corpus | ReDoS |
|---|---|---|---|
| Stack Overflow — tentative de l'auteur de la question, sans logique d'année bissextile (accepte « 31/02/4500 ») | ^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}$ | 20 / 28 | safe |
| Stack Overflow — extension à 307 votes de la réponse la plus votée, virgule oubliée dans une classe de caractères | ^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-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})$ | 25 / 28 | safe |
| Stack Overflow — réponse à 86 votes, ne gère pas correctement les siècles (bug signalé en commentaire, valide seulement 1901-2099), même virgule oubliée | (^(((0[1-9]|1[0-9]|2[0-8])[\/](0[1-9]|1[012]))|((29|30|31)[\/](0[13578]|1[02]))|((29|30)[\/](0[4,6,9]|11)))[\/](19|[2-9][0-9])\d\d$)|(^29[\/]02[\/](19|[2-9][0-9])(00|04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96)$) | 24 / 28 | safe |
Implementation
const re = new RegExp("^(?:31\\/(?:0?[13578]|1[02])\\/|(?:29|30)\\/(?:0?[13-9]|1[0-2])\\/)(?:1[6-9]|[2-9]\\d)\\d{2}$|^29\\/0?2\\/(?:(?: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])\\/(?:1[6-9]|[2-9]\\d)\\d{2}$", "");
re.test(input);Limits
- Ne couvre que la forme numérique jj/mm/aaaa avec « / » comme séparateur : les formes « jj-mm-aaaa », « jj.mm.aaaa » ou en toutes lettres (« 3 septembre 2026 »), également utilisées en France, ne sont pas reconnues par ce motif.
- Accepte un jour ou un mois à un seul chiffre sans zéro de tête (« 1/1/2020 ») aussi bien que la forme complète : pour exiger strictement deux chiffres, il faut retirer les « 0? » optionnels du motif.
- L'année doit compter exactement 4 chiffres et rester dans la plage 1600-9999 : une année à 2 chiffres (« 20 » pour 2020) ou antérieure à 1600 est rejetée, une limite arbitraire héritée des plages de siècle du motif source, sans lien avec une règle du calendrier grégorien lui-même.
- Valide uniquement la FORME d'une date calendaire (nombre de jours par mois + règle bissextile) : ne connaît ni les 10 jours sautés lors de la réforme grégorienne de 1582, ni aucune règle métier (date future interdite, jour férié, etc.).