Regex US date (MM/DD/YYYY)
The American format puts the month before the day: '02/29/2024'. A regex can check days per month (30 or 31 days, 28 in February) and the Gregorian leap rule (years divisible by 4, except centuries not divisible by 400). The attempts I read are less strict on form or on days: regular-expressions.info only writes the 'every month has 31 days' form with a free choice of separators ('02/31/2024' passes), the OWASP pattern handles leap years but accepts 2-digit years, a choice of three separators and a comma inside a class, and jQuery Validation Engine's handles days and leap years well but mixes yyyy-mm-dd and mm/dd/yyyy and drops the leading-zero requirement. Here's a strict 4-digit pattern, compared against those attempts.
/^(?:(?:0[13578]|1[02])/31/[1-9][0-9]{3}|(?:0[13-9]|1[0-2])/(?:29|30)/[1-9][0-9]{3}|(?:0[1-9]|1[0-2])/(?:0[1-9]|1[0-9]|2[0-8])/[1-9][0-9]{3}|02/29/(?:[1-9][0-9](?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00))$/passes 30/30 cases
Comparison of existing patterns
| Source | pattern | Score on the corpus | ReDoS |
|---|---|---|---|
| OWASP Validation Regex Repository (« date ») — bissextiles gérés, mais années à 2 chiffres, jours/mois sans zéro initial, séparateurs « / - . » au choix et virgule dans « [1,3-9] » : « 4/5/25 » et « 02-29-2024 » passent | ^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$ | 26 / 30 | safe |
| regular-expressions.info (variante mm/dd/yyyy) — 31 jours partout et aucun contrôle des bissextiles : « 02/31/2024 » et « 04/31/2025 » passent ; séparateurs « - », espace, « / » ou « . » au choix ; années 1900-2099 seulement | ^(0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$ | 21 / 30 | safe |
| jQuery-Validation-Engine (dateFormat) — gère bien jours par mois et bissextiles, mais accepte aussi yyyy-mm-dd, « / » ou « - » au choix et mois/jour sans zéro initial (« 1/5/2025 », « 01-01-2025 ») | ^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:0?[1-9]|1[0-2])(\/|-)(?:0?[1-9]|1\d|2[0-8]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(0?2(\/|-)29)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$ | 27 / 30 | safe |
Implementation
const re = new RegExp("^(?:(?:0[13578]|1[02])/31/[1-9][0-9]{3}|(?:0[13-9]|1[0-2])/(?:29|30)/[1-9][0-9]{3}|(?:0[1-9]|1[0-2])/(?:0[1-9]|1[0-9]|2[0-8])/[1-9][0-9]{3}|02/29/(?:[1-9][0-9](?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00))$", "");
re.test(input);Limits
- Valide la FORME et la cohérence calendaire (jours par mois, bissextiles), mais pas la plausibilité : « 01/01/9999 » passe. Le zéro initial est obligatoire (« 1/5/2025 » refusé) et seul le séparateur « / » est accepté : normalisez avant.
- Ambiguïté : « 03/04/2025 » est le 4 mars en format américain et le 3 avril en format européen ; la regex ne peut pas le deviner. Voir date-fr pour le format JJ/MM/AAAA.
- Années 1000 à 9999 seulement ; calendrier grégorien appliqué à toutes les années (« 02/29/1500 » est refusé alors qu'il existait dans le calendrier julien).