Espressione regolare per abbinare date valide
Domanda
Sto cercando di scrivere un'espressione regolare che convalidi una data.La regex deve corrispondere a quanto segue
- M/G/AAAA
- MM/GG/AAAA
- 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 PUÒ includere il 30 o il 31 febbraio (ad esempio:31/02/2008)
Finora l'ho fatto
^(([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)$
Questo corrisponde correttamente TRANNE che include ancora il 30/02/2008 e il 31/02/2008.
Qualcuno ha un suggerimento migliore?
Modificare: 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.
Soluzione
Questo non è un uso appropriato delle espressioni regolari.Faresti meglio a usarlo
[0-9]{2}/[0-9]{2}/[0-9]{4}
e quindi controllare gli intervalli in un linguaggio di livello superiore.
Altri suggerimenti
Ecco il Reg ex che corrisponde a tutte le date valide compresi gli anni bisestili.Formati accettati formato mm/gg/aaaa oppure mm-gg-aaaa oppure mm.gg.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
Sono arrivato qui perché il titolo di questa domanda è ampio e stavo cercando un'espressione regolare che potessi utilizzare per abbinare un formato di data specifico (come l'OP).Ma poi ho scoperto, come molte risposte e commenti hanno evidenziato in modo esauriente, ci sono molte insidie che rendono molto complicata la costruzione di un modello efficace quando si estraggono date mescolate con dati di origine di scarsa qualità o non strutturati.
Nella mia esplorazione dei problemi, ho ideato un sistema che ti consente di creare un'espressione regolare organizzando insieme quattro sottoespressioni più semplici che corrispondono al delimitatore e intervalli validi per i campi anno, mese e giorno nell'ordine hai bisogno.
Questi sono :-
Delimitatori
[^\w\d\r\n:]
Ciò corrisponderà a tutto ciò che non è un carattere di parola, un carattere di cifra, un ritorno a capo, una nuova riga o due punti.I due punti devono essere presenti per impedire la corrispondenza in orari che sembrano date (vedi i miei dati di test)
Puoi ottimizzare questa parte del modello per accelerare la corrispondenza, ma questa è una buona base per rilevare i delimitatori più validi.
Nota tuttavia;Corrisponderà a una stringa con delimitatori misti come questa 2/12-73 che potrebbe non essere effettivamente una data valida.
Valori dell'anno
(\d{4}|\d{2})
Corrisponde a un gruppo di due o 4 cifre, nella maggior parte dei casi è accettabile, ma se hai a che fare con dati dagli anni 0-999 o oltre 9999 devi decidere come gestirli perché nella maggior parte dei casi un 1, 3 o un anno >4 cifre è spazzatura.
Valori mensili
(0?[1-9]|1[0-2])
Corrisponde a qualsiasi numero compreso tra 1 e 12 con o senza 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 zero iniziale - nota:0 e 00 non corrispondono.
Questa espressione corrisponde alle date formattate per data, mese e 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 ad alcuni di quelli relativi all'anno e alla data del mese.Dovrebbe inoltre essere bloccato con gli operatori di confine per garantire che l'intera stringa di data sia selezionata e impedire che vengano estratte date secondarie valide da dati che non sono ben formati, ad es.senza etichette di confine il 20/12/194 corrisponde al 20/12/19 e il 101/12/1974 corrisponde al 01/12/1974
Confronta i risultati dell'espressione successiva con quella sopra con i dati del test nella sezione senza senso (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'è convalida in questa espressione regolare, quindi verrebbe abbinata una data ben formata ma non valida come 31/02/2001.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 di cui hai bisogno per eseguire e gestire un'ulteriore convalida nel tuo codice, se provi ad abbinare E convalidare i dati nella RegEx diventa molto complicato e diventa difficile supportarli senza molto documentazione sintetica.
Immondizia dentro, spazzatura fuori.
Detto questo, se disponi di formati misti in cui i valori delle date variano e devi estrarre il più possibile;Puoi combinare un paio di espressioni insieme in questo modo;
Questa (disastrosa) espressione 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.Faccio fatica a pensare a uno scenario in cui ciò non causerà problemi in futuro, è una cattiva pratica e non dovresti affrontarla in quel modo: trova il proprietario dei dati e colpiscilo con il martello della governance .
Infine, se vuoi far corrispondere una stringa AAAAMMGG senza delimitatori puoi eliminare parte dell'incertezza e l'espressione apparirà così
\b(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|30|31)\b
Ma nota ancora, corrisponderà a valori ben formati ma non validi come 20010231 (31 febbraio!) :)
Dati di test
Sperimentando le soluzioni in questo thread mi sono ritrovato con un set di dati di test che include una varietà di date valide e non valide e alcune situazioni difficili in cui potresti o meno voler corrispondere, ad es.Orari che potrebbero corrispondere come date e date su più righe.
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
Versione Perl 5.10 manutenibile
/
(?:
(?<month> (?&mon_29)) [\/] (?<day>(?&day_29))
| (?<month> (?&mon_30)) [\/] (?<day>(?&day_30))
| (?<month> (?&mon_31)) [\/] (?<day>(?&day_31))
)
[\/]
(?<year> [0-9]{4})
(?(DEFINE)
(?<mon_29> 0?2 )
(?<mon_30> 0?[469] | (11) )
(?<mon_31> 0?[13578] | 1[02] )
(?<day_29> 0?[1-9] | [1-2]?[0-9] )
(?<day_30> 0?[1-9] | [1-2]?[0-9] | 30 )
(?<day_31> 0?[1-9] | [1-2]?[0-9] | 3[01] )
)
/x
In questa versione è possibile recuperare gli elementi per nome.
say "Month=$+{month} Day=$+{day} Year=$+{year}";
(Non è stato fatto alcun tentativo di limitare i valori per l'anno.)
Per controllare la validità di una data nel seguente formato:
AAAA/MM/GG o AAAA-MM-GG
Ti consiglierei 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])))
Partite
2016-02-29 | 2012-04-30 | 2019/09/31
Non corrispondenze
2016-02-30 | 2012-04-31 | 2019/09/35
Puoi personalizzarlo se desideri consentire solo i separatori "/" o "-".Questa RegEx controlla rigorosamente la validità della data e verifica i mesi di 28,30 e 31 giorni, anche gli anni bisestili con il mese 29/02.
Provalo, funziona molto bene e previene molti bug nel tuo codice!
PER TUA INFORMAZIONE :Ho creato una variante per il datetime SQL.Lo troverai lì (cerca il mio nome): Espressione regolare per convalidare un timestamp
I feedback sono benvenuti :)
Sembra che tu stia estendendo eccessivamente le espressioni regolari per questo scopo.Quello che farei è utilizzare una regex per abbinare alcuni formati di data e quindi utilizzare una funzione separata per convalidare i valori dei campi data così estratti.
Versione espansa di Perl
Nota l'uso di /x
modificatore.
/^(
(
( # 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 funzionato con i suggerimenti di cui sopra, lo uso, poiché ottiene qualsiasi data, ho eseguito questa espressione attraverso 50 collegamenti e ha ottenuto tutte le date su ogni 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 regex convalida le date comprese tra 01-01-2000 e 12-31-2099 con separatori corrispondenti.
^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$
Regex non è stato pensato per convalidare intervalli di numeri (questo numero deve essere compreso tra 1 e 5 quando il numero che lo precede sembra essere un 2 e il numero che lo precede è inferiore a 6).Cerca semplicemente lo schema di posizionamento dei numeri nella regex.Se è necessario convalidare le qualità di una data, inserirla in un oggetto data js/c#/vb e interrogare i numeri lì.
So che questo non risponde alla tua domanda, ma perché non usi una routine di gestione della data per verificare se è una data valida?Anche se modifichi l'espressione regolare con un'asserzione lookahead negativa come (?!31/0?2) (ovvero, non corrisponde a 31/2 o 31/02) avrai comunque il problema di accettare 29 02 negli anni non bisestili e su un formato di data con separatore singolo.
Il problema non è semplice se vuoi davvero convalidare una data, controlla questo discussione del forum.
Per un esempio o un modo migliore, in C#, controlla questo link
Se utilizzi un'altra piattaforma/lingua, faccelo sapere
Versione Perl6
rx{
^
$<month> = (\d ** 1..2)
{ $<month> <= 12 or fail }
'/'
$<day> = (\d ** 1..2)
{
given( +$<month> ){
when 1|3|5|7|8|10|12 {
$<day> <= 31 or fail
}
when 4|6|9|11 {
$<day> <= 30 or fail
}
when 2 {
$<day> <= 29 or fail
}
default { fail }
}
}
'/'
$<year> = (\d ** 4)
$
}
Dopo averlo utilizzato per verificare l'input in cui sono disponibili i valori $/
o individualmente come $<month>
, $<day>
, $<year>
.(quelle sono solo la sintassi per accedere ai valori in $/
)
Non è stato fatto alcun tentativo per controllare l'anno o che non corrisponda al 29 febbraio negli anni non bisestili.
Se hai intenzione di insistere per farlo con un'espressione regolare, ti consiglio 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 permettono di leggere e comprendere.
Un approccio leggermente diverso che potrebbe essere utile o meno per te.
Sono in php.
Il progetto a cui si riferisce non avrà mai una data anteriore al 1 gennaio 2008.Quindi, prendo la "data" immessa e utilizzo strtotime().Se la risposta è >= 1199167200 allora ho una data che mi è utile.Se viene inserito qualcosa che non assomiglia a una data, viene restituito -1.Se viene inserito null, restituisce il numero della data odierna, quindi è necessario prima controllare una voce non nulla.
Funziona per la mia situazione, forse anche per la tua?