Espressione regolare per abbinare le date valide

Sto cercando di scrivere un’espressione regolare che convalida una data. Il regex deve corrispondere al seguente

  • M / D / AAAA
  • MM / DD / YYYY
  • I mesi a una cifra possono iniziare con uno zero iniziale (ad esempio: 03/12/2008)
  • I giorni a una cifra possono iniziare con uno zero iniziale (ad esempio: 3/02/2008)
  • NON PUO includere il 30 febbraio o il 31 febbraio (es: 31/3/2008)

Finora ho

^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d)|((1[012]|0[1-9])(3[01]|2\d|1\d|0[1-9])(19|20)\d\d)|((1[012]|0[1-9])[-/.](3[01]|2\d|1\d|0[1-9])[-/.](19|20)\d\d)$ 

Ciò corrisponde correttamente TRANNE che include ancora il 30 marzo 2008 e il 31/3/2008.

Qualcuno ha un suggerimento migliore?

Modifica: ho trovato la risposta su RegExLib

 ^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$ 

Corrisponde a tutti i mesi validi che seguono il formato MM / GG / AAAA.

Grazie a tutti per l’aiuto.

Questo non è un uso appropriato delle espressioni regolari. Staresti meglio usando

 [0-9]{2}/[0-9]{2}/[0-9]{4} 

e quindi controllare gli intervalli in un linguaggio di livello superiore.

Ecco il Regex che corrisponde a tutte le date valide compresi gli anni bisestili. Formati accettati mm / gg / aaaa o mm-gg-aaaa o mm.dd.aaaa

^(?:(?:(?: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})$

cortesia Asiq Ahamed

Versione Perl 5.10 mantenibile

 / (?: (? (?&mon_29)) [\/] (?(?&day_29)) | (? (?&mon_30)) [\/] (?(?&day_30)) | (? (?&mon_31)) [\/] (?(?&day_31)) ) [\/] (? [0-9]{4}) (?(DEFINE) (? 0?2 ) (? 0?[469] | (11) ) (? 0?[13578] | 1[02] ) (? 0?[1-9] | [1-2]?[0-9] ) (? 0?[1-9] | [1-2]?[0-9] | 30 ) (? 0?[1-9] | [1-2]?[0-9] | 3[01] ) ) /x 

Puoi recuperare gli elementi per nome in questa versione.

 say "Month=$+{month} Day=$+{day} Year=$+{year}"; 

(Non è stato effettuato alcun tentativo per limitare i valori per l’anno.)

Sono atterrato qui perché il titolo di questa domanda è ampio e stavo cercando un’espressione regolare che potrei usare per abbinare su un formato data specifico (come l’OP). Ma poi ho scoperto, come molte delle risposte e dei commenti hanno evidenziato in modo completo, ci sono molte insidie ​​che rendono molto complicata la costruzione di un pattern efficace quando si estraggono date che sono mescolate con dati di origine di scarsa qualità o non strutturati.

Nella mia esplorazione dei problemi, ho creato un sistema che consente di creare un’espressione regolare combinando quattro sub-espressioni più semplici che corrispondono al delimitatore e intervalli validi per i campi dell’anno, del mese e del giorno nell’ordine tu richiedi

Questi sono :-

delimitatori

 [^\w\d\r\n:] 

Ciò corrisponderà a tutto ciò che non è un carattere di parola, carattere di cifre, ritorno a capo, nuova riga o due punti. Il colon deve essere presente per evitare la corrispondenza in momentjs che sembrano date (vedere i miei dati di test)

È ansible ottimizzare questa parte del pattern per accelerare la corrispondenza, ma questa è una buona base che rileva i delimitatori più validi.

Nota comunque; Corrisponde a una stringa con delimitatori misti come questo 2 / 12-73 che potrebbe non essere effettivamente una data valida.

Valori dell’anno

 (\d{4}|\d{2}) 

Questo corrisponde a un gruppo di due o quattro cifre, nella maggior parte dei casi questo è accettabile, ma se hai a che fare con dati degli anni 0-999 o oltre 9999 devi decidere come gestirli perché nella maggior parte dei casi un 1, 3 o> 4 cifre all’anno è spazzatura.

Valori mensili

 (0?[1-9]|1[0-2]) 

Corrisponde a qualsiasi numero compreso tra 1 e 12 con o senza uno zero iniziale: nota: 0 e 00 non corrispondono.

Valori della data

 (0?[1-9]|[12]\d|30|31) 

Corrisponde a qualsiasi numero compreso tra 1 e 31 con o senza uno zero iniziale: nota: 0 e 00 non corrispondono.

Questa espressione corrisponde alle date formattate per data, mese, anno

 (0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2}) 

Ma corrisponderà anche a quelli dell’anno, quelli del mese. Dovrebbe anche essere fermato con gli operatori di confine per garantire che l’intera stringa di data sia selezionata e impedire che vengano eseguite sottosezioni valide da dati che non sono ben formati, cioè senza etichette di contorno 20/12/194 partite come 20/12/19 e Partite 101/12/1974 come 01/12/1974

Confronta i risultati dell’espressione successiva con quella sopra con i dati del test nella sezione nonsense (sotto)

 \b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b 

Non c’è alcuna convalida in questa regex, quindi una data ben formata ma non valida come il 31/02/2001 sarebbe stata abbinata. Questo è un problema di qualità dei dati e, come altri hanno già detto, la tua espressione regolare non dovrebbe aver bisogno di convalidare i dati.

Poiché tu (come sviluppatore) non puoi garantire la qualità dei dati di origine che devi eseguire e gestire la convalida aggiuntiva nel tuo codice, se cerchi di associare e convalidare i dati nel RegEx diventa molto complicato e diventa difficile supporto senza documentazione molto concisa.

Immondizia, spazzatura.

Detto questo, se hai formati misti in cui i valori delle date variano, e devi estrarre più che puoi; Puoi combinare un paio di espressioni insieme in questo modo;

Questa espressione (disastrosa) corrisponde alle date DMY e YMD

 (\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](\d{4}|\d{2})\b) 

MA non sarai in grado di dire se date come 6/9/1973 sono il 6 settembre o il 9 giugno. Sto facendo fatica a pensare a uno scenario in cui questo non causerà un problema da qualche parte lungo la linea, è una ctriggers pratica e non dovresti avere a che fare con esso in quel modo – trova il proprietario dei dati e colpirli con il martello della governance .

Infine, se vuoi abbinare una stringa YYYYMMDD senza delimitatori, puoi togliere parte dell’incertezza e l’espressione appare come questa

 \b(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|30|31)\b 

Ma nota ancora, corrisponderà su valori ben formati ma non validi come 20010231 (31 febbraio!) 🙂

Dati di test

Sperimentando con le soluzioni in questo thread ho finito con un set di dati di test che include una varietà di date valide e non valide e alcune situazioni complicate in cui potresti o non vuoi corrispondere, ad esempio Times che potrebbe corrispondere come date e date su più linee.

Spero che questo sia utile a qualcuno.

 Valid Dates in various formats Day, month, year 2/11/73 02/11/1973 2/1/73 02/01/73 31/1/1973 02/1/1973 31.1.2011 31-1-2001 29/2/1973 29/02/1976 03/06/2010 12/6/90 month, day, year 02/24/1975 06/19/66 03.31.1991 2.29.2003 02-29-55 03-13-55 03-13-1955 12\24\1974 12\30\1974 1\31\1974 03/31/2001 01/21/2001 12/13/2001 Match both DMY and MDY 12/12/1978 6/6/78 06/6/1978 6/06/1978 using whitespace as a delimiter 13 11 2001 11 13 2001 11 13 01 13 11 01 1 1 01 1 1 2001 Year Month Day order 76/02/02 1976/02/29 1976/2/13 76/09/31 YYYYMMDD sortable format 19741213 19750101 Valid dates before Epoch 12/1/10 12/01/660 12/01/00 12/01/0000 Valid date after 2038 01/01/2039 01/01/39 Valid date beyond the year 9999 01/01/10000 Dates with leading or trailing characters 12/31/21/ 31/12/1921AD 31/12/1921.10:55 12/10/2016 8:26:00.39 wfuwdf12/11/74iuhwf fwefew13/11/1974 01/12/1974vdwdfwe 01/01/99werwer 12321301/01/99 Times that look like dates 12:13:56 13:12:01 1:12:01PM 1:12:01 AM Dates that runs across two lines 1/12/19 74 01/12/19 74/13/1946 31/12/20 08:13 Invalid, corrupted or nonsense dates 0/1/2001 1/0/2001 00/01/2100 01/0/2001 0101/2001 01/131/2001 31/31/2001 101/12/1974 56/56/56 00/00/0000 0/0/1999 12/01/0 12/10/-100 74/2/29 12/32/45 20/12/194 2/12-73 

Per controllare una validità della data nel seguente formato:

YYYY / MM / DD o YYYY-MM-DD

Ti consiglio di utilizzare la seguente espressione regolare:

 (((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8]))) 

fiammiferi

29-02-2016 | 2012-04-30 | 2019/09/31

Non-match

30-02-2016 | 2012-04-31 | 2019/09/35

Puoi personalizzarlo se vuoi consentire solo i separatori ‘/’ o ‘-‘. Questo RegEx controlla rigorosamente la validità della data e verifica 28,30 e 31 giorni mesi, anche gli anni bisestili con il 29/02 mese.

Provalo, funziona molto bene e previene il tuo codice da molti bug!

A proposito: ho fatto una variante per il datetime di SQL. Lo troverai lì (cerca il mio nome): Espressione regolare per convalidare un timestamp

I feedback sono i benvenuti 🙂

Sembra che tu stia estasiando la regex per questo scopo. Quello che vorrei fare è usare un’espressione regolare per abbinare alcuni formati di data e quindi utilizzare una funzione separata per convalidare i valori dei campi data così estratti.

Versione estesa Perl

Si noti l’uso del modificatore /x .

 /^( ( ( # 31 day months (0[13578]) | ([13578]) | (1[02]) ) [\/] ( ([1-9]) | ([0-2][0-9]) | (3[01]) ) ) | ( ( # 30 day months (0[469]) | ([469]) | (11) ) [\/] ( ([1-9]) | ([0-2][0-9]) | (30) ) ) | ( # 29 day month (Feb) (2|02) [\/] ( ([1-9]) | ([0-2][0-9]) ) ) ) [\/] # year \d{4}$ | ^\d{4}$ # year only /x 

Originale

 ^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$ 

se non hai ottenuto il funzionamento dei suddetti suggerimenti, lo uso, poiché ottiene qualsiasi data in cui ho eseguito questa espressione tramite 50 collegamenti e ha ottenuto tutte le date su ciascuna pagina.

 ^20\d\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$ 
  var dtRegex = new RegExp(/[1-9\-]{4}[0-9\-]{2}[0-9\-]{2}/); if(dtRegex.test(date) == true){ var evalDate = date.split('-'); if(evalDate[0] != '0000' && evalDate[1] != '00' && evalDate[2] != '00'){ return true; } } 

Questa espressione regolare convalida le date tra 01-01-2000 e 12-31-2099 con i separatori corrispondenti.

 ^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$ 

Perl 6 versione

 rx{ ^ $ = (\d ** 1..2) { $ <= 12 or fail } '/' $ = (\d ** 1..2) { given( +$ ){ when 1|3|5|7|8|10|12 { $ <= 31 or fail } when 4|6|9|11 { $ <= 30 or fail } when 2 { $ <= 29 or fail } default { fail } } } '/' $ = (\d ** 4) $ } 

Dopo averlo utilizzato per verificare l’input, i valori sono disponibili in $/ o singolarmente come $ , $ , $ . (quelli sono solo syntax per accedere ai valori in $/ )

Non è stato fatto alcun tentativo per verificare l’anno, o che non corrisponda al 29 di febbraio negli anni non bisestili.

Regex non è stato concepito per convalidare intervalli numerici (questo numero deve essere compreso tra 1 e 5 quando il numero che precede è uguale a 2 e il numero precedente che è inferiore a 6). Basta cercare lo schema di posizionamento dei numeri nella regex. Se è necessario convalidare è la qualità di una data, inserirla in un object data js / c # / vb e interogare i numeri lì.

So che questo non risponde alla tua domanda, ma perché non usi una routine di gestione della data per verificare se si tratta di una data valida? Anche se modifichi regexp con un’asserzione lookahead negativa come (?! 31/0? 2) (cioè, non corrisponde a 31/2 o 31/02) avrai ancora il problema di accettare 29 02 negli anni non bisestili e su un singolo formato di data separatore.

Il problema non è facile se vuoi veramente validare una data, controlla questa discussione sul forum .

Per un esempio o un modo migliore, in C #, controlla questo link

Se stai utilizzando un’altra piattaforma / lingua, faccelo sapere

Se hai intenzione di insistere per farlo con un’espressione regolare, ti consiglierei qualcosa come:

 ( (0?1|0?3| <...> |10|11|12) / (0?1| <...> |30|31) | 0?2 / (0?1| <...> |28|29) ) / (19|20)[0-9]{2} 

Questo potrebbe rendere ansible la lettura e la comprensione.

Un approccio leggermente diverso che può o non può essere utile per te.

Sono in php

Il progetto a cui si riferisce non avrà mai una data prima del 1 ° gennaio 2008. Quindi, prendo la ‘data’ ed usiamo strtotime (). Se la risposta è> = 1199167200 allora ho una data che mi è utile. Se viene inserito qualcosa che non sembra una data, viene restituito -1. Se viene inserito un valore nullo, viene restituito il numero della data odierna, pertanto è necessario prima verificare la presenza di una voce non nulla.

Funziona per la mia situazione, forse anche la tua?