Wie lässt sich eine Datumsangabe auf Korrektheit prüfen?

Mit Hilfe eines DateTimeFormatter kann ein Datum auf Gültigkeit und syntaktische Korrektheit geprüft werden.

Die Überprüfung einer Datumsangabe muss prinzipiell auf zweierlei Weisen erfolgen:

Wird eine vorgegebene Syntax in der Form JJJJ-MM-TT eingehalten, so beschränkt sich die Überprüfung auf die zweite Bedingung.
Die statische Methode LocalDate.parse() nimmt den Datum-String in der angegebenen Form entgegen und übersetzt ihn bei kalendarischer Korrektheit intern mittels DateTimeFormatter.ISO_LOCAL_DATE in ein LocalDate-Objekt. Wenn das Datum kalendarisch ungültig ist oder der String in einem anderen Format vorliegt, wird eine DateTimeParseException geworfen.

import java.time.LocalDate;
import java.time.format.DateTimeParseException;

public class DatumPruefen {

    public static void main(String[] args) {

        String[] datum = { "2023-03-16", "2023-4-13", "2023-02-29", "2003-13-17", "2003.12.31" };
        for (int i = 0; i < datum.length; i++) {
            if (!isDateValid(datum[i])) {
                System.out.println("Datum nicht valide");
            } else {
                System.out.println("Datum valide");
            }
            System.out.println();
        }
    }

    public static boolean isDateValid(String date) {
        try {
            System.out.println(date);
            System.out.println(LocalDate.parse(date));
        } catch (DateTimeParseException e) {
            return false;
        }
        return true;
    }
}

// Ausgabe
// 2023-03-16
// 2023-03-16
// Datum valide

// 2023-4-13
// Datum nicht valide

// 2023-02-29
// Datum nicht valide

// 2003-13-17
// Datum nicht valide

// 2003.12.31
// Datum nicht valide

Das erste Beispiel zeigt dies anhand eines Arrays mit einem gültigen und vier, aus unterschiedlichen Gründen ungültigen Datum-Strings.
Die Methode isDateValid(String date) wird innerhalb einer Schleife wiederholt jeweils mit einem im Array gespeicherten String als Parameter aufgerufen [10]. Der Übersichtlichkeit halber wird er innerhalb der Methode zunächst ausgegeben. Anschließend wird versucht, den String durch die o.a. Methode LocalDate.parse() in ein LocalDate-Objekt zu wandeln. Wird das Parsen erfolgreich durchlaufen, gibt die Methode true zurück, im anderen Fall false. Man erkennt, dass die Übersetzung des Datum-Strings bei unzulässiger Syntax [35, 44] und falschen kalendarischen Angaben [38, 41] nicht erfolgreich verläuft.

Abweichend formatierte Datum-Strings

Liegt ein Datum-String nicht in der Standardsyntax vor, muss zu dessen Übersetzung ein DateTimeFormatter hinzugezogen werden. Die Klasse ermöglicht die weitgehend freie Formatierung eines Datum-Strings nebst dessen Validierung. Ein Muster (Pattern) definiert hierbei durch Art, Anzahl und Anordnung alphanumerischer Zeichen die syntaktische Form der Datumsangabe1.

Mit diesem als Argument wird durch die statische Methode DateTimeFormatter.ofPattern() ein DateTimeFormatter-Objekt erzeugt. Die Methode ist zweifach überladen und kann neben einem ersten Parameter in Form des Pattern-Strings, als zweiten noch eine Gebietsvariable, eine statische Locale-Konstante, erhalten. Die Methode ofPattern() übersetzt das angegebenen Pattern in die zugehörigen Datums-Bestandteile, die Felder und gibt bei Fehlerfreiheit ein neues DateTimeFormatter-Objekt zurück.

Dieser Formatter kann durch withResolverStyle() weiter spezifiziert werden. Die Methode wertet dabei die gebildeten Datum-Felder aus und interpretiert diese in Abhängigkeit von einer Konstanten des Enum ResolverStyle. Sie steuert die Art des Umgangs mit den Datumswerten in Abhängigkeit vom jeweiligen Datumsfeld.

ResolverStyle.STRICT
Strenge Auslegung der Datumsangabe nach ISO-Maßgabe. Akzeptiert z.B. einen 29. Februar nur in Schaltjahren [47].
ResolverStyle.SMART
'Intelligente' Auslegung der Datumsangabe, die in ihren Auswirkungen zwischen den verschiedenen Feldern unterscheidet. Begrenzt z.B. einen Überlauf des Tages im Monat auf den letzten zulässigen Monatstag [46].
ResolverStyle.LENIENT
Unterscheidet ebenfalls zwischen den einzelnen Feldern und verrechnet überlaufende Werte mit den übergeordneten Nachbarfeldern [45, 50].
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;

public class DatumPruefen {

    public static void main(String[] args) {

        String[] datum = { "29.02.2020", "29.02.2023", "34.13.2000", "29.2.1933", "07.01.0193" };
        ResolverStyle[] styles = { ResolverStyle.LENIENT, ResolverStyle.SMART, ResolverStyle.STRICT };
        for (int i = 0; i < datum.length; i++) {
            System.out.println(datum[i]);
            for (int j = 0; j < styles.length; j++) {
                if (!isDateValid(datum[i], styles[j])) {
                    System.out.println("\tDatum nicht valide");
                } else {
                    System.out.println("\tDatum valide");
                }
            }
            System.out.println("-----------------");
        }
    }

    public static boolean isDateValid(String date, ResolverStyle style) {
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd.MM.uuuu");
        DateTimeFormatter formatter = dateFormatter.withResolverStyle(style);
        System.out.print(style.toString() + ": ");
        try {
            System.out.print(LocalDate.parse(date, formatter));
        } catch (DateTimeParseException e) {
            return false;
        }
        return true;
    }
}

// Ausgabe
// 29.02.2020
// LENIENT: 2020-02-29 Datum valide
// SMART: 2020-02-29   Datum valide
// STRICT: 2020-02-29  Datum valide
// -----------------
// 29.02.2023
// LENIENT: 2023-03-01 Datum valide
// SMART: 2023-02-28   Datum valide
// STRICT:     Datum nicht valide
// -----------------
// 34.13.2000
// LENIENT: 2001-02-03 Datum valide
// SMART:  Datum nicht valide
// STRICT:     Datum nicht valide
// -----------------
// 29.2.1933
// LENIENT:    Datum nicht valide
// SMART:  Datum nicht valide
// STRICT:     Datum nicht valide
// -----------------
// 07.01.0193
// LENIENT: 0193-01-07 Datum valide
// SMART: 0193-01-07   Datum valide
// STRICT: 0193-01-07  Datum valide
// -----------------

Im Beispiel werden zwei Arrays gebildet. Das erste enthält einige Datum-Strings, das zweite die erwähnten drei Konstanten des Enum ResolverStyle. Beide Arrays werden in geschachtelten Schleifen durchlaufen und die Datumswerte und Konstanten als Parameter an die Methode isDateValid() übergeben.

In dieser werden dann für jedes Datum-Konstanten-Paar die o.a. Prozesse ausgeführt: Der Datum-String wird gemäß des angegebenen Patterns übersetzt [26], das resultierende Formatter-Objekt mit der übergebenen Konstante weiterverarbeitet [27] und auf dieser Basis das Datum letztendlich geparsed und ausgegeben [30]. Je nach Erfolg des abschließenden Parsens gibt die Methode true oder false zurück.

1) Die Variationsmöglichkeiten der Pattern-Angaben sind sehr vielfältig und können in den Java-Docs der Klasse DateTimeFormatter nachgeschlagen werden.

Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.