RegEx voor het matchen van datumpatroon in Java

1. Inleiding

Reguliere expressies zijn een krachtig hulpmiddel om verschillende soorten patronen te matchen, mits op de juiste manier gebruikt.

In dit artikel gebruiken we java.util.regex pakket om te bepalen of een gegeven Draad bevat een geldige datum of niet.

Raadpleeg onze Guide to Java Regular Expressions API voor een inleiding tot reguliere expressies.

2. Overzicht van datumnotaties

We gaan een geldige datum definiëren in relatie tot de internationale Gregoriaanse kalender. Ons formaat volgt het algemene patroon: JJJJ-MM-DD.

Laten we ook het concept van een sprong jaar dat een jaar is met een dag van 29 februari. Volgens de Gregoriaanse kalender noemen we een jaar sprong als het jaarnummer gelijkmatig kan worden gedeeld door 4 behalve degene die deelbaar zijn door 100 maar inclusief die welke deelbaar zijn door 400.

In alle andere gevallen, we bellen een jaar regelmatig.

Voorbeelden van geldige datums:

  • 2017-12-31
  • 2020-02-29
  • 2400-02-29

Voorbeelden van ongeldige datums:

  • 2017/12/31: onjuist tokenscheidingsteken
  • 2018-1-1: ontbrekende voorloopnullen
  • 2018-04-31: verkeerde dagen tellen voor april
  • 2100-02-29: dit jaar is geen sprong voorwaarts aangezien de waarde zich door deelt 100, dus februari is beperkt tot 28 dagen

3. Implementeren van een oplossing

Aangezien we een datum gaan matchen met behulp van reguliere expressies, laten we eerst een interface schetsen DateMatcher, die een enkele wedstrijden methode:

openbare interface DateMatcher {booleaanse overeenkomsten (tekenreeksdatum); }

We gaan de implementatie hieronder stap voor stap presenteren en bouwen aan het einde toe naar een complete oplossing.

3.1. Passend bij het brede formaat

We beginnen met het maken van een heel eenvoudig prototype dat de formaatbeperkingen van onze matcher behandelt:

class FormattedDateMatcher implementeert DateMatcher {privé statisch Pattern DATE_PATTERN = Pattern.compile ("^ \ d {4} - \ d {2} - \ d {2} $"); @Override openbare booleaanse overeenkomsten (String date) {return DATE_PATTERN.matcher (date) .matches (); }}

Hier specificeren we dat een geldige datum moet bestaan ​​uit drie groepen gehele getallen gescheiden door een streepje. De eerste groep bestaat uit vier gehele getallen, terwijl de overige twee groepen elk twee gehele getallen hebben.

Overeenkomende data: 2017-12-31, 2018-01-31, 0000-00-00, 1029-99-72

Niet-overeenkomende datums: 2018-01, 2018-01-XX, 2020/02/29

3.2. Overeenkomend met het specifieke datumnotatie

Ons tweede voorbeeld accepteert reeksen datumtokens evenals onze opmaakbeperking. Voor de eenvoud hebben we onze interesse beperkt tot de jaren 1900 - 2999.

Nu we met succes onze algemene datumnotatie hebben aangepast, moeten we dat verder beperken - om er zeker van te zijn dat de datums echt correct zijn:

^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$

Hier hebben we er drie geïntroduceerd groepen van gehele getallen die moeten overeenkomen:

  • (19|2[0-9])[0-9]{2} bestrijkt een beperkt aantal jaren door een nummer te matchen dat begint met 19 of 2x gevolgd door een paar cijfers.
  • 0[1-9]|1[012] komt overeen met een maandnummer in een bereik van 01-12
  • 0[1-9]|[12][0-9]|3[01] komt overeen met een dagnummer in een bereik van 01-31

Overeenkomende data: 1900-01-01, 2205-02-31, 2999-12-31

Niet-overeenkomende datums: 1899-12-31, 2018-05-35, 2018-13-05, 3000-01-01, 2018-01-XX

3.3. Passend bij de 29 februari

Om schrikkeljaren correct af te stemmen, moeten we eerst identificeren wanneer we een schrikkeljaar zijn tegengekomen, en zorg er dan voor dat we 29 februari accepteren als een geldige datum voor die jaren.

Aangezien het aantal schrikkeljaren in ons beperkte bereik groot genoeg is, moeten we de juiste deelbaarheidsregels gebruiken om ze te filteren:

  • Als het nummer gevormd door de laatste twee cijfers van een nummer deelbaar is door 4, is het oorspronkelijke nummer deelbaar door 4
  • Als de laatste twee cijfers van het nummer 00 zijn, is het nummer deelbaar door 100

Hier is een oplossing:

^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$

Het patroon bestaat uit de volgende onderdelen:

  • 2000|2400|2800 komt overeen met een reeks schrikkeljaren met een verdeler van 400 in een beperkt bereik van 1900-2999
  • 19|2[0-9](0[48]|[2468][048]|[13579][26])) komt overeen met alle witte lijst combinaties van jaren met een scheidingsteken van 4 en heb geen verdeler van 100
  • -02-29 wedstrijden 2 februari

Overeenkomende data: 2020-02-29, 2024-02-29, 2400-02-29

Niet-overeenkomende datums: 2019-02-29, 2100-02-29, 3200-02-29, 2020/02/29

3.4. Bijpassende algemene dagen van februari

Naast het matchen van 29 februari in schrikkeljaren, we moeten ook alle andere dagen van februari (1 - 28) in alle jaren matchen:

^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$

Overeenkomende data: 2018-02-01, 2019-02-13, 2020-02-25

Niet-overeenkomende datums: 2000-02-30, 2400-02-62, 2018/02/28

3.5. Bijpassende 31-daagse maanden

De maanden januari, maart, mei, juli, augustus, oktober en december moeten tussen de 1 en 31 dagen overeenkomen:

^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$

Overeenkomende data: 2018-01-31, 2021-07-31, 2022-08-31

Niet-overeenkomende datums: 2018-01-32, 2019-03-64, 2018/01/31

3.6. Bijpassende maanden van 30 dagen

De maanden april, juni, september en november moeten tussen de 1 en 30 dagen overeenkomen:

^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$

Overeenkomende data: 2018-04-30, 2019-06-30, 2020-09-30

Niet-overeenkomende datums: 2018-04-31, 2019-06-31, 2018/04/30

3.7. Gregoriaanse Date Matcher

Nu kunnen we combineer alle bovenstaande patronen tot een enkele matcher om een ​​compleet te hebben Gregoriaanse DateMatcher voldoen aan alle beperkingen:

klasse GregorianDateMatcher implementeert DateMatcher {privé statisch Pattern DATE_PATTERN = Pattern.compile ("^ ((2000 | 2400 | 2800 | (19 | 2 [0-9] (0 [48] | [2468] [048] | [13579] [ 26]))) - 02-29) $ "+" | ^ (((19 | 2 [0-9]) [0-9] {2}) - 02- (0 [1-9] | 1 [ 0-9] | 2 [0-8])) $ "+" | ^ (((19 | 2 [0-9]) [0-9] {2}) - (0 [13578] | 10 | 12 ) - (0 [1-9] | [12] [0-9] | 3 [01])) $ "+" | ^ (((19 | 2 [0-9]) [0-9] {2 }) - (0 [469] | 11) - (0 [1-9] | [12] [0-9] | 30)) $ "); @Override openbare booleaanse overeenkomsten (String date) {return DATE_PATTERN.matcher (datum) .matches (); }}

We hebben een afwisseling teken "|" om er minstens één te matchen van de vier takken. De geldige datum van februari komt dus overeen met de eerste tak van 29 februari van een schrikkeljaar of de tweede tak van een willekeurige dag vanaf 1 naar 28. De datums van de resterende maanden komen overeen met de derde en vierde tak.

Aangezien we dit patroon niet hebben geoptimaliseerd ten gunste van een betere leesbaarheid, kunt u gerust experimenteren met een lengte ervan.

Op dit moment hebben we voldaan aan alle beperkingen die we in het begin hebben geïntroduceerd.

3.8. Opmerking over prestaties

Het parseren van complexe reguliere expressies kan de prestaties van de uitvoeringsstroom aanzienlijk beïnvloeden. Het primaire doel van dit artikel was niet om een ​​efficiënte manier te vinden om een ​​string te testen op zijn lidmaatschap in een reeks van alle mogelijke datums.

Overweeg om te gebruiken LocalDate.parse () geleverd door Java8 als een betrouwbare en snelle benadering voor het valideren van een datum nodig is.

4. Conclusie

In dit artikel hebben we geleerd hoe we reguliere expressies kunnen gebruiken voor het matchen van de strikt opgemaakte datum van de Gregoriaanse kalender door ook regels te geven voor het formaat, het bereik en de lengte van maanden.

Alle code die in dit artikel wordt gepresenteerd, is beschikbaar op Github. Dit is een op Maven gebaseerd project, dus het zou gemakkelijk moeten kunnen worden geïmporteerd en uitgevoerd zoals het is.