RegEx für übereinstimmendes Datumsmuster in Java

1. Einleitung

Reguläre Ausdrücke sind ein leistungsstarkes Werkzeug zum Abgleichen verschiedener Arten von Mustern, wenn sie ordnungsgemäß verwendet werden.

In diesem Artikel verwenden wir das Paket java.util.regex , um festzustellen, ob eine bestimmte Zeichenfolge ein gültiges Datum enthält oder nicht.

Eine Einführung in reguläre Ausdrücke finden Sie in unserem Handbuch zur Java-API für reguläre Ausdrücke.

2. Datumsformatübersicht

Wir werden ein gültiges Datum in Bezug auf den internationalen Gregorianischen Kalender definieren. Unser Format folgt dem allgemeinen Muster: JJJJ-MM-TT.

Nehmen wir auch das Konzept eines Schaltjahres auf , das einen Tag mit einem Tag vom 29. Februar enthält. Nach dem Gregorianischen Kalender nennen wir einen Jahressprung, wenn die Jahreszahl gleichmäßig durch 4 geteilt werden kann, mit Ausnahme derjenigen, die durch 100 teilbar sind, aber einschließlich derer, die durch 400 teilbar sind .

In allen anderen Fällen , werden wir ein Jahr rufen regelmäßig .

Beispiele für gültige Daten:

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

Beispiele für ungültige Daten:

  • 31.12.2017 : Falscher Token-Begrenzer
  • 2018-1-1 : fehlende führende Nullen
  • 2018-04-31 : Falsche Tage zählen für April
  • 2100-02-29 : Dieses Jahr ist kein Sprung, da der Wert durch 100 geteilt wird , sodass der Februar auf 28 Tage begrenzt ist

3. Implementierung einer Lösung

Da wir ein Datum mit regulären Ausdrücken abgleichen , skizzieren wir zunächst eine Schnittstelle DateMatcher , die eine Methode für einzelne Übereinstimmungen bereitstellt :

public interface DateMatcher { boolean matches(String date); }

Im Folgenden wird die Implementierung Schritt für Schritt vorgestellt, um am Ende eine vollständige Lösung zu finden.

3.1. Anpassung an das breite Format

Wir beginnen mit der Erstellung eines sehr einfachen Prototyps, der die Formatbeschränkungen unseres Matchers behandelt:

class FormattedDateMatcher implements DateMatcher { private static Pattern DATE_PATTERN = Pattern.compile( "^\\d{4}-\\d{2}-\\d{2}$"); @Override public boolean matches(String date) { return DATE_PATTERN.matcher(date).matches(); } }

Hier geben wir an, dass ein gültiges Datum aus drei Gruppen von Ganzzahlen bestehen muss, die durch einen Bindestrich getrennt sind. Die erste Gruppe besteht aus vier Ganzzahlen, wobei die verbleibenden zwei Gruppen jeweils zwei Ganzzahlen haben.

Übereinstimmende Daten: 2017-12-31 , 2018-01-31 , 0000-00-00 , 1029-99-72

Nicht übereinstimmende Daten: 2018-01 , 2018-01-XX , 2020/02/29

3.2. Übereinstimmung mit dem spezifischen Datumsformat

In unserem zweiten Beispiel werden Bereiche von Datumstoken sowie unsere Formatierungsbeschränkungen akzeptiert. Der Einfachheit halber haben wir unser Interesse auf die Jahre 1900 - 2999 beschränkt.

Nachdem wir unser allgemeines Datumsformat erfolgreich angepasst haben, müssen wir dies weiter einschränken - um sicherzustellen, dass die Daten tatsächlich korrekt sind:

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

Hier haben wir drei Gruppen von Ganzzahlbereichen eingeführt, die übereinstimmen müssen:

  • (19|2[0-9])[0-9]{2}deckt einen begrenzten Bereich von Jahren ab, indem eine Zahl abgeglichen wird, die mit 19 oder 2X beginnt, gefolgt von einigen Ziffern.
  • 0[1-9]|1[012]Entspricht einer Monatszahl im Bereich von 01-12
  • 0[1-9]|[12][0-9]|3[01]stimmt mit einer Tageszahl im Bereich von 01-31 überein

Spieltermine : 1900-01-01 , 2205-02-31 , 2999-12-31

Nicht übereinstimmende Daten: 1899-12-31 , 2018-05-35 , 2018-13-05 , 3000-01-01 , 2018-01-XX

3.3. Passend zum 29. Februar

Um die Schaltjahre korrekt abzugleichen, müssen wir zuerst feststellen, wann wir auf ein Schaltjahr gestoßen sind , und dann sicherstellen, dass wir den 29. Februar als gültiges Datum für diese Jahre akzeptieren.

Da die Anzahl der Schaltjahre in unserem eingeschränkten Bereich groß genug ist, sollten wir die entsprechenden Teilbarkeitsregeln verwenden, um sie zu filtern:

  • Wenn die durch die letzten beiden Ziffern einer Zahl gebildete Zahl durch 4 teilbar ist, ist die ursprüngliche Zahl durch 4 teilbar
  • Wenn die letzten beiden Ziffern der Nummer 00 sind, ist die Nummer durch 100 teilbar

Hier ist eine Lösung:

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

Das Muster besteht aus folgenden Teilen:

  • 2000|2400|2800entspricht einer Reihe von Schaltjahren mit einem Teiler von 400 in einem eingeschränkten Bereich von 1900-2999
  • 19|2[0-9](0[48]|[2468][048]|[13579][26]))Entspricht allen White-List- Kombinationen von Jahren, die einen Teiler von 4 und keinen Teiler von 100 haben
  • -02-29Spiele 2. Februar

Spieltermine : 2020-02-29 , 2024-02-29 , 2400-02-29

Nicht übereinstimmende Daten: 2019-02-29 , 2100-02-29 , 3200-02-29 , 2020/02/29

3.4. Passende allgemeine Februar-Tage

Neben dem 29. Februar in Schaltjahren müssen wir auch alle anderen Februar-Tage (1 - 28) in allen Jahren abgleichen :

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

Spieltermine : 2018-02-01 , 2019-02-13 , 2020-02-25

Non-matching dates: 2000-02-30, 2400-02-62, 2018/02/28

3.5. Matching 31-Day Months

The months January, March, May, July, August, October, and December should match for between 1 and 31 days:

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

Matching dates: 2018-01-31, 2021-07-31, 2022-08-31

Non-matching dates: 2018-01-32, 2019-03-64, 2018/01/31

3.6. Matching 30-Day Months

The months April, June, September, and November should match for between 1 and 30 days:

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

Matching dates: 2018-04-30, 2019-06-30, 2020-09-30

Non-matching dates: 2018-04-31, 2019-06-31, 2018/04/30

3.7. Gregorian Date Matcher

Now we can combine all of the patterns above into a single matcher to have a complete GregorianDateMatcher satisfying all of the constraints:

class GregorianDateMatcher implements DateMatcher { private static 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 public boolean matches(String date) { return DATE_PATTERN.matcher(date).matches(); } }

We've used an alternation character “|” to match at least one of the four branches. Thus, the valid date of February either matches the first branch of February 29th of a leap year either the second branch of any day from 1 to 28. The dates of remaining months match third and fourth branches.

Since we haven't optimized this pattern in favor of a better readability, feel free to experiment with a length of it.

At this moment we have satisfied all the constraints, we introduced in the beginning.

3.8. Note on Performance

Das Parsen komplexer regulärer Ausdrücke kann die Leistung des Ausführungsflusses erheblich beeinträchtigen. Der Hauptzweck dieses Artikels bestand nicht darin, eine effiziente Methode zum Testen einer Zeichenfolge auf ihre Mitgliedschaft in einer Reihe aller möglichen Daten zu erlernen.

Erwägen Sie die Verwendung von LocalDate.parse (), das von Java8 bereitgestellt wird, wenn ein zuverlässiger und schneller Ansatz zur Validierung eines Datums erforderlich ist.

4. Fazit

In diesem Artikel haben wir gelernt, wie man reguläre Ausdrücke verwendet, um das streng formatierte Datum des Gregorianischen Kalenders abzugleichen, indem wir auch Regeln für das Format, den Bereich und die Länge der Monate angeben.

Der gesamte in diesem Artikel vorgestellte Code ist auf Github verfügbar. Dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.