Regex Mexican CURP
A CURP has 18 characters: 4 initials, a YYMMDD date, the sex, one of 33 state codes, 3 internal consonants, a homonymy character and a numeric check. Real attempts settle for letter classes: python-stdnum's regex accepts sex 'Q' and a vowel where a consonant belongs, the npm validate-curp library's accepts month 13 and state 'QQ', and the SAT library satcfdi's, good on states, accepts February 30. Here's a pattern bounding months, days per month and the 33 states, compared against those three attempts.
/^[A-Z][AEIOUX][A-Z]{2}\d{2}(?:(?:0[13578]|1[02])(?:0[1-9]|[12]\d|3[01])|02(?:0[1-9]|[12]\d)|(?:0[469]|11)(?:0[1-9]|[12]\d|30))[HMX](?:AS|B[CS]|C[CHLMS]|D[FG]|G[RT]|HG|JC|M[CNS]|N[ELT]|OC|PL|Q[RT]|S[LPR]|T[CLS]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$/passes 23/23 cases
Comparison of existing patterns
| Source | pattern | Score on the corpus | ReDoS |
|---|---|---|---|
| validate-curp (npm) — classes de lettres seules : accepte le mois 13 (« PEGL891313MJCRMS06 ») et l'État « QQ » ; sa validation de la date et des États se fait ensuite dans le code ; rejette le sexe X | ^[A-Z][AEIOUX][A-Z]{2}[0-9]{6}[HM][A-Z]{2}[B-DF-HJ-NP-TV-Z]{3}[A-Z\d]\d$ | 18 / 23 | safe |
| python-stdnum — la plus lâche, le reste est fait en code : accepte un sexe « Q », une voyelle à la place d'une consonne (« …MJCAMS08 ») et un État inexistant | ^[A-Z]{4}[0-9]{6}[A-Z]{6}[0-9A-Z][0-9]$ | 16 / 23 | safe |
| satcfdi (bibliothèque SAT, utilisée avec fullmatch ; entourée de ^(?:…)$ ici) — mois et 33 États corrects, mais jour 31 pour tous les mois (« 30 février » passe) et sexe X rejeté | ^(?:[A-Z][AEIOUX][A-Z]{2}[0-9]{2}(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])[MH]([ABCMTZ]S|[BCJMOT]C|[CNPST]L|[GNQ]T|[GQS]R|C[MH]|[MY]N|[DH]G|NE|VZ|DF|SP)[BCDFGHJ-NP-TV-Z]{3}[0-9A-Z][0-9])$ | 20 / 23 | safe |
Implementation
const re = new RegExp("^[A-Z][AEIOUX][A-Z]{2}\\d{2}(?:(?:0[13578]|1[02])(?:0[1-9]|[12]\\d|3[01])|02(?:0[1-9]|[12]\\d)|(?:0[469]|11)(?:0[1-9]|[12]\\d|30))[HMX](?:AS|B[CS]|C[CHLMS]|D[FG]|G[RT]|HG|JC|M[CNS]|N[ELT]|OC|PL|Q[RT]|S[LPR]|T[CLS]|VZ|YN|ZS)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\\d$", "");
re.test(input);Limits
- Valide seulement la FORME : ne calcule pas la clé numérique (dernier caractère). « BOXW310820HNERXN08 » et « PEGL890909MJCRMS09 » ont la bonne forme mais une clé fausse, et passent ce motif ; seul un calcul dans le code les rejette. La clé ne distingue pas non plus un « O » d'un « 0 » en position 17.
- Ne gère ni la règle du siècle (position 17 : chiffre jusqu'en 1999, lettre A à J ensuite, impossible à vérifier avec un YY seul), ni les années bissextiles (« 29 février 1989 » passe), ni les mots interdits (« BACA », « PUTA »…) que l'Instructivo remplace par un X.
- Le sexe X, attribué depuis 2022, est accepté mais n'est pas dans le tableau officiel que j'ai consulté (H ou M seulement) : il est attesté par la presse et par un numéro dont la clé est valide. Retirez-le du motif si vous suivez strictement le texte de 2018.
- Majuscules ASCII seulement : « pegl890909mjcrms08 » est rejeté, sans normalisation. Une CURP qui passe forme et clé n'est pas forcément attribuée : seul le RENAPO le confirme. N'utilisez pas de vraies CURP dans vos tests.