W3docs

Java Datum-Parsing

Strings in Java-Datumsobjekte umwandeln mit DateTimeFormatter und Parsing-Ausnahmen korrekt behandeln.

Parsing ist das Spiegelbild von Formatierung. Gleicher DateTimeFormatter, gleiche Muster-Alphabetik, gleiche Besonderheiten — aber ein String wird gelesen statt geschrieben. Jeder java.time-Typ hat eine parse(...)-Factory-Methode; mit einem Standardmuster (ISO-8601) nimmt sie ein Argument, mit einem benutzerdefinierten Muster nimmt sie einen Formatter.

LocalDate    d  = LocalDate.parse("2025-11-04");                                       // ISO default
LocalTime    t  = LocalTime.parse("14:30:00");
LocalDateTime dt = LocalDateTime.parse("2025-11-04T14:30:00");                          // note the T
ZonedDateTime zdt = ZonedDateTime.parse("2025-11-04T14:30:00-05:00[America/New_York]");
Instant       i = Instant.parse("2025-11-04T19:30:00Z");                                // trailing Z mandatory

Jeder dieser Aufrufe verwendet den Standard-Formatter des jeweiligen Typs — striktes ISO-8601. Für alles, was keine ISO-Form hat, erstellt man einen Formatter und übergibt ihn.

Parsing mit benutzerdefiniertem Muster

DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate d = LocalDate.parse("04/11/2025", f);                                         // British DMY

Die Muster-Alphabetik ist dieselbe wie bei der Formatierung — dd MMM yyyy, HH:mm:ss, MM/dd/yyyy h:mm a. Die Übereinstimmungsregel ist standardmäßig strikt: Jedes Literalzeichen im Muster muss im Input exakt vorhanden sein, und jedes Feld muss die richtige Stellenanzahl haben.

DateTimeFormatter dmy = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate.parse("4/11/2025", dmy);                                                       // FAILS: "dd" requires 2 digits
LocalDate.parse("04/11/2025", dmy);                                                      // OK

Wenn der Input manchmal einstellig ist, verwende d/M/yyyy (das 1-2 Stellen akzeptiert) oder erstelle den Formatter mit DateTimeFormatterBuilder und parseStrict(false). Die einbuchstabige Form ist die einfachere Lösung.

Locale spielt beim Parsing eine Rolle

Dieselbe Locale-Problematik wie bei der Formatierung: Monatsnamen (MMMM) und Wochentagsnamen (EEEE) sind sprachspezifisch, daher muss der Formatter wissen, in welcher Sprache der Input verfasst ist.

DateTimeFormatter englishDay = DateTimeFormatter.ofPattern("EEEE, MMMM d yyyy", Locale.US);
DateTimeFormatter germanDay  = DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy",  Locale.GERMAN);

LocalDate.parse("Tuesday, November 4 2025", englishDay);   // 2025-11-04
LocalDate.parse("Dienstag, 4. November 2025", germanDay);  // 2025-11-04 (German month name)

Beachte yyyy in beiden Mustern. Um ein LocalDate zu erzeugen, muss der Input eine Jahreszahl liefern — ein Muster wie "EEEE, MMMM d" parst zwar, aber nur zu einem TemporalAccessor ohne Jahresfeld, sodass LocalDate.parse darauf wirft. Wenn die Strings tatsächlich kein Jahr enthalten, parse zu einem TemporalAccessor und kombiniere das Ergebnis selbst mit einer Jahreszahl.

Ohne explizite Locale wird Locale.getDefault() verwendet — und die Standard-Locale einer Server-JVM ist unvorhersehbar. Übergib immer eine Locale, wenn Monats- oder Tagesnamen geparst werden, die ein Benutzer eintippen könnte. Das Spiegelprinzip „immer mit Locale formatieren" gilt auch hier.

DateTimeParseException

Ein fehlgeschlagenes Parsing wirft eine DateTimeParseException (eine Unterklasse von RuntimeException, also nicht auf parse deklariert). Die Meldung nennt sowohl die Position als auch das Erwartete:

try {
  LocalDate.parse("2025-13-45");                              // month 13, day 45
} catch (DateTimeParseException e) {
  e.getParsedString();                                         // "2025-13-45"
  e.getErrorIndex();                                           // index where parsing gave up
  e.getMessage();                                              // human description
}

Zwei verschiedene Fehlerarten landen hier:

  • Format-Nichtübereinstimmung. Der String passt überhaupt nicht zum Muster — "04 nov 2025" gegen "dd-MM-yyyy".
  • Wert außerhalb des gültigen Bereichs. Der String passt zum Muster, aber ein Wert ist unmöglich — Monat 13, Tag 32.

Beide werfen dieselbe Klasse. Abfangen und melden; niemals stillschweigend schlucken.

Zweistellige Jahresangaben und die MAX_VALUE-Falle

Das yy-Muster (zweistellige Jahreszahl) hat ein dokumentiertes, aber überraschendes Standardverhalten: Es parst zu dem Jahr, das am nächsten zu heute liegt, innerhalb eines 100-Jahres-Fensters. LocalDate.parse("11/04/25", DateTimeFormatter.ofPattern("MM/dd/yy")) ist 2025-11-04 im Jahr 2025 und 2125-11-04 im Jahr 2076. Das ist eine nützliche Eigenschaft für „nahe bei heute"-Fälle und eine Fehlerquelle für Archivdaten.

Die Lösung ist, yyyy zu verwenden, wenn der Input vier Stellen hat, und das Jahrhundert-Fenster explizit anzugeben, wenn er das nicht tut:

DateTimeFormatter f = new DateTimeFormatterBuilder()
    .appendPattern("MM/dd/")
    .appendValueReduced(ChronoField.YEAR, 2, 2, 1950)         // window starts at 1950
    .toFormatter();

Wenn du Legacy-Daten mit yy verarbeitest, dokumentiere das Fenster im Code. Der Standard ist „rollierender Pivot um das aktuelle Jahr", was nicht das Richtige ist für „alle meine Daten stammen aus den 1980ern".

Parsing ohne Festlegung auf einen Typ

DateTimeFormatter.parse(String) gibt einen TemporalAccessor zurück — die unterste Ebene der Typhierarchie. Nützlich, wenn der Input entweder ein LocalDate oder ein LocalDateTime sein könnte:

TemporalAccessor ta = DateTimeFormatter.ISO_DATE_TIME.parseBest(
    "2025-11-04T14:30:00",
    LocalDateTime::from,                                       // preferred
    LocalDate::from);                                          // fallback

parseBest(text, ...queries) versucht die from-Methode jedes Typs der Reihe nach und gibt den ersten erfolgreichen zurück. Das Ergebnis benötigt instanceof, um es für etwas Konkretes zu verwenden:

if (ta instanceof LocalDateTime ldt) ...
else if (ta instanceof LocalDate ld) ...

Für die meisten Fälle ist der direkte Aufruf von parse(...) auf dem spezifischen Typ einfacher. parseBest ist für den Fall gedacht, dass mehrere Formen akzeptiert werden (eine CSV-Spalte, die entweder ein Datum oder ein Datum-Uhrzeit-Wert sein könnte).

Tolerantes Parsing mit dem Builder

DateTimeFormatterBuilder ermöglicht das Zusammensetzen eines Formatters, der toleranter ist:

DateTimeFormatter lenient = new DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd[ HH:mm[:ss]]")                  // optional sections in []
    .parseLenient()                                            // accept missing leading zeros etc.
    .parseCaseInsensitive()                                    // ignore case on month/day names
    .toFormatter(Locale.US);

Die Klammersyntax [...] markiert einen optionalen Abschnitt — dieses Muster parst sowohl "2025-11-04" (ohne Uhrzeit) als auch "2025-11-04 14:30" (mit Uhrzeit). Kombiniert mit parseLenient und parseCaseInsensitive lässt sich ein Formatter erstellen, der eine größere Bandbreite von Eingaben akzeptiert, ohne einen eigenen Parser zu schreiben.

Das ist übertrieben für Code, der beide Enden kontrolliert. Verwende den strikten Standard, es sei denn, du liest Benutzereingaben oder Legacy-Daten.

Instant.parse ist strikt

Instant.parse("2025-11-04T19:30:00Z") funktioniert. Das abschließende Z (UTC) ist Pflicht; jeder andere Offset (-05:00, +09:00) erfordert zunächst OffsetDateTime.parse oder ZonedDateTime.parse, dann .toInstant():

Instant inst = OffsetDateTime.parse("2025-11-04T14:30:00-05:00").toInstant();

Das ist die kanonische Konvertierung, wenn eine externe API ISO-8601-Strings mit Zeitzonen-Offsets, aber ohne IANA-Zone liefert.

Ein ausgearbeitetes Beispiel: Konfiguration lesen, parsen, auf fehlerhafte Eingabe reagieren

Das folgende Programm parst drei datumsartige Strings aus einer synthetischen Konfiguration: ein ISO-Datum, ein benutzerdefiniertes Musterdatum und einen ISO-Instant. Es demonstriert dann den toleranten Builder mit einem optionalen Zeitabschnitt, die parseBest-API für „entweder ein Datum oder ein Datum-Uhrzeit-Wert" und den Fehlerfall, wenn der Input nicht passt.

java— editable, runs on the server

Was aus der Ausführung zu entnehmen ist:

  • Die drei ISO-Standard-Parser akzeptierten jeweils genau die Standardform: yyyy-MM-dd für LocalDate, die T-getrennte Form für LocalDateTime und die Z-terminierte Form für Instant. Keine Flexibilität, kein Raten — und das ist der Sinn. Wenn der Input passt, wird kein Formatter benötigt; wenn nicht, ist der Aufbau eines Formatters eine Zeile.
  • Der tolerante Formatter akzeptierte drei verschiedene Input-Formen — nur Datum, Datum mit Uhrzeit, Datum mit Uhrzeit und Sekunden — weil der eingeklammerte [...]-Abschnitt optional ist. parseBest(text, LocalDateTime::from, LocalDate::from) wählte den reichhaltigsten Typ, den jeder Input unterstützte. Das ist das richtige Muster, wenn Benutzer-eingegebene oder Konfigurations-Daten mit variabler Präzision akzeptiert werden.
  • OffsetDateTime.parse(wire).toInstant() war die kanonische Brücke von „einem ISO-8601-Timestamp mit einem Offset" zu Instant. Instant.parse selbst akzeptiert nur Z-suffixiertes UTC; alles andere muss zuerst über OffsetDateTime (oder ZonedDateTime) gehen. Die Konvertierung ist in jede Richtung eine Zeile.
  • Das deutsche Locale-Parsing funktionierte nur, weil der Formatter mit Locale.GERMAN gebaut wurde. Die Standard-Locale hätte "November" abgelehnt, wenn die JVM auf Deutsch läuft (was erwartet, dass Locale.GERMANs "November" gegen die deutschen Namen abgeglichen wird). Locale immer am Parse-Rand festsetzen — der Formatter allein reicht nicht; die Locale steuert die Auflösung von Monats- und Tagesbezeichnungen.
  • Die beiden Fehlerblöcke warfen beide DateTimeParseException mit nützlicher Positionsinformation. getErrorIndex ist der Ort, an dem der Parser aufgegeben hat; getParsedString ist der Input, wie der Parser ihn gesehen hat. Diese Informationen in benutzer-sichtbaren Fehlern ausgeben — „Datum konnte nicht bei Zeichen 5 geparst werden" ist wesentlich hilfreicher als „ungültiges Datum".

Wie geht es weiter

Formatierung und Parsing schließen die String-Grenze ab. Das nächste Kapitel, Java Temporal Adjusters, kehrt zur Wertseite zurück und behandelt die eingebauten Adjusters (firstDayOfMonth, nextOrSame(MONDAY) usw.) sowie das Schreiben eigener Adjusters — nützlich immer dann, wenn das gewünschte Datum vom vorhandenen Datum abhängt („erster Dienstag nach dem 15.").

Übungsaufgaben

Übung
Ein Webformular lässt Benutzer ein Datum im Format '04/11/2025' (Tag/Monat/Jahr) eingeben. Dein Code verwendet `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` und fragt dann 'liegt dieses Datum in der Zukunft?'. Ein Benutzer tippt '4/11/2025' (ohne führende Null beim Tag) und das Parsing wirft. Was ist die kleinste Korrektur?
Ein Webformular lässt Benutzer ein Datum im Format '04/11/2025' (Tag/Monat/Jahr) eingeben. Dein Code verwendet `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` und fragt dann 'liegt dieses Datum in der Zukunft?'. Ein Benutzer tippt '4/11/2025' (ohne führende Null beim Tag) und das Parsing wirft. Was ist die kleinste Korrektur?
Was this page helpful?