Java Datumsformatierung
Datum und Uhrzeit in Java mit DateTimeFormatter und Standard- oder benutzerdefinierten Mustern in Strings umwandeln.
Datumsformatierung ist die Umwandlung eines Datums-/Uhrzeitwerts in einen für Menschen lesbaren String. Dieses Kapitel behandelt DateTimeFormatter — wie man einen erstellt (eingebaut, lokalisiert oder musterbasiert), das vollständige Muster-Alphabet, die Handhabung von Locale und Zeitzonen sowie die häufigsten Fehlerquellen, die zu falscher Ausgabe führen. Es funktioniert mit jedem java.time-Typ: LocalDate, LocalTime, LocalDateTime, ZonedDateTime und Instant.
Jeder java.time-Typ hat eine toString()-Methode, die die ISO-8601-Darstellung erzeugt: 2025-11-04, 14:30:00, 2025-11-04T14:30:00Z. Das ist für Logs und maschinelle Kommunikation ausreichend. Für die Anzeige für Menschen ("4. November 2025" oder "4. Nov, 14:30") benötigt man einen Formatter.
Die Klasse heißt java.time.format.DateTimeFormatter. Sie ist der moderne, thread-sichere und unveränderliche Ersatz für das veraltete java.text.SimpleDateFormat (das nicht thread-sicher war und bei gemeinsamer Verwendung zu schwer nachvollziehbaren Produktionsfehlern führte). Verwende eine Instanz als static final und nutze sie dauerhaft in allen Threads — keine Synchronisation, kein defensives Kopieren nötig.
Drei Wege, einen Formatter zu erstellen
// 1. Built-in ISO formatters
DateTimeFormatter.ISO_LOCAL_DATE; // 2025-11-04
DateTimeFormatter.ISO_LOCAL_DATE_TIME; // 2025-11-04T14:30:00
DateTimeFormatter.ISO_OFFSET_DATE_TIME; // 2025-11-04T14:30:00-05:00
DateTimeFormatter.ISO_ZONED_DATE_TIME; // 2025-11-04T14:30:00-05:00[America/New_York]
DateTimeFormatter.ISO_INSTANT; // 2025-11-04T19:30:00Z
DateTimeFormatter.BASIC_ISO_DATE; // 20251104
// 2. Localised formatters
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG); // November 4, 2025 (en-US)
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM); // Nov 4, 2025, 2:30:00 PM
// 3. Pattern-based formatters
DateTimeFormatter.ofPattern("dd MMM yyyy"); // 04 Nov 2025
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm zzz"); // 2025-11-04 14:30 ESTDie Muster-API wird am häufigsten verwendet. Die lokalisierte Variante ist die richtige Wahl, wenn ein kulturell angemessenes Format gewünscht wird und das JDK das Layout selbst wählen soll.
Formatierung
Der Aufruf ist auf beiden Seiten symmetrisch:
String s = formatter.format(temporal);
String s2 = temporal.format(formatter); // same thing, fluent styleBeide Varianten funktionieren. Die meisten Codebasen verwenden die Fluent-Form.
LocalDate today = LocalDate.now();
String us = today.format(DateTimeFormatter.ofPattern("MM/dd/yyyy")); // 11/04/2025
String iso = today.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2025-11-04
String eu = today.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")); // 04.11.2025Das Muster-Alphabet
Die große Übersichtstabelle — die man immer wieder nachschlagen wird. Buchstaben sind case-sensitiv, und die Anzahl der Zeichen ist entscheidend.
| Buchstabe | Bedeutung | Beispiel |
|---|---|---|
y | Jahr | y → 2025, yy → 25, yyyy → 2025 |
M | Monat | M → 11, MM → 11, MMM → Nov, MMMM → November |
d | Tag des Monats | d → 4, dd → 04 |
E | Wochentag | E → Tue, EEEE → Tuesday |
H | Stunde 0-23 | H → 14, HH → 14 |
h | Stunde 1-12 | h → 2, hh → 02 (zusammen mit a verwenden) |
a | AM/PM | a → PM |
m | Minute | m → 5, mm → 05 |
s | Sekunde | s → 9, ss → 09 |
S | Sekundenbruchteile | SSS → 123 (Millisekunden) |
n | Nanosekunde | nnnnnnnnn → 123456789 |
z | Zonenname | z → EST, zzzz → Eastern Standard Time |
Z | Zonenversatz | Z → -0500, ZZ → -0500, ZZZZ → GMT-05:00 |
X | ISO-Versatz | X → -05, XX → -0500, XXX → -05:00 |
V | Zonen-ID | VV → America/New_York |
Literaltext wird in einfache Anführungszeichen eingeschlossen:
DateTimeFormatter.ofPattern("EEEE, MMMM d 'at' h:mm a"); // Tuesday, November 4 at 2:30 PMFür ein wörtliches einfaches Anführungszeichen verwendet man zwei: ''.
Das am häufigsten verwechselte Paar ist m vs. M (Kleinbuchstabe = Minute, Großbuchstabe = Monat) und H vs. h (Großbuchstabe = 0-23, Kleinbuchstabe = 1-12). Die meisten "die Uhrzeit ist irgendwie falsch"-Fehler haben ihren Ursprung in einem dieser Tippfehler.
Lokalisierung: Locale und withLocale
Ein Formatter verwendet die Standard-Locale der JVM, sofern nicht anders angegeben. Für eine Ausgabe "immer auf Englisch" oder "immer auf Deutsch" sollte die Locale explizit festgelegt werden:
DateTimeFormatter english = DateTimeFormatter.ofPattern("EEEE, MMMM d", Locale.US);
DateTimeFormatter german = DateTimeFormatter.ofPattern("EEEE, d. MMMM", Locale.GERMAN);
DateTimeFormatter french = DateTimeFormatter.ofPattern("EEEE d MMMM", Locale.FRENCH);
today.format(english); // Tuesday, November 4
today.format(german); // Dienstag, 4. November
today.format(french); // mardi 4 novembreFür serverseitig gerendertem Inhalt sollte immer eine Locale übergeben werden. Die "Standard-Locale der JVM" ist auf Produktionsservern nicht vorhersehbar und die Ursache für "funktioniert auf meinem Laptop, nicht aber auf dem Server"-Fehler.
Zeitzonendarstellung
ZonedDateTime und Instant sind die einzigen Typen, die Zeitzoneninformationen enthalten. Das Formatieren eines LocalDateTime mit einem Muster, das z oder Z enthält, führt zu einer Ausnahme — es gibt keine Zeitzone zum Ausgeben. Zuerst konvertieren:
ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z")); // 2025-11-04 14:30 ESTAuch für Instant benötigt der Formatter eine Zeitzone — Instant hat keine eigene, daher brauchen Formatter, die Zeitzonen-abhängige Felder enthalten, ein withZone:
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
.withZone(ZoneId.of("America/New_York"));
f.format(Instant.now()); // formatter supplies the zone for displayOhne withZone wirft das Formatieren eines Instant mit einem kalenderförmigen Muster eine Ausnahme.
Formatierte Formatter mit FormatStyle
Die lokalisierten Factories bieten vier kanonische Größen:
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT); // 11/4/25 (en-US), 04.11.25 (de-DE)
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); // Nov 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG); // November 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL); // Tuesday, November 4, 2025Dieselben vier Größen gibt es für ofLocalizedTime und ofLocalizedDateTime. Verwende diese, wenn das Layout der Locale des Benutzers folgen soll, anstatt ein festes Format zu erzwingen. Kombiniere sie mit .withLocale(...), um die Locale festzulegen.
Ein praktisches Beispiel: ein Datum, sechs Anzeigevarianten
Das folgende Programm formatiert ein ZonedDateTime auf sechs gängige Arten: ISO für maschinelle Logs, US-12-Stunden-Format für englischsprachige Nutzer, europäisches 24-Stunden-Format für deutsche Nutzer, eine lange lokalisierte Form, ein benutzerdefiniertes Muster mit eingebettetem Literaltext sowie ein Instant-via-withZone-Formatter für rohe Zeitstempel.
Was man der Ausgabe entnehmen kann:
- Die gecachten
static final DateTimeFormatter-Felder sind die richtige Vorgehensweise.DateTimeFormatterist unveränderlich und thread-sicher; das Erstellen einer Instanz ist günstig, aber nicht kostenlos, und die Wiederverwendung derselben Instanz überall ist das vom JDK empfohlene Muster. Innerhalb einer häufig aufgerufenen Schleife sollte keine neue Instanz erstellt werden. - Dasselbe
ZonedDateTimeerzeugte sechs verschiedene Strings — abhängig vom Formatter. Das Wertobjekt änderte sich nie; der Formatter ist das einzige, was das Layout steuert. Das ist die Trennung, für dieDateTimeFormatterexistiert — den Werttyp sauber halten und die Darstellung dem Formatter überlassen. - Der "häufige Tippfehler"-Block gab
14:11fürHH:MMaus, weilMMonat bedeutet, nicht Minute. Diese beiden sind das am häufigsten verwechselte Paar im Muster-Alphabet. Wenn eine angezeigte Uhrzeit verdächtig nach einem Datumskomponenten aussieht, sollte man die Groß-/Kleinschreibung im Muster prüfen. - Die
FormatStyle-Leiter erzeugte vier zunehmend längere Strings.FormatStyle.MEDIUMist ein sinnvoller Standard für "Datum anzeigen, ohne zu viel nachzudenken";LONGundFULLfür Kontexte, in denen Jahr und Wochentag eindeutig sein müssen;SHORTfür enge UI-Bereiche. LocalDateTimemit einem zonenabhängigen Muster warf eine Ausnahme — der Formatter benötigt Zonendaten, undLocalDateTimehat keine. Die Lösung ist eine Konvertierung (ldt.atZone(zone)) oder das Entfernen des zonenabhängigen Felds aus dem Muster. In jedem Fall ist das Fehlverhalten zur Laufzeit klar erkennbar.
Wie geht es weiter
Die Formatierung ist die Richtung Wert → String. Das nächste Kapitel, Java Date Parsing, ist das Gegenteil — String → Wert — unter Verwendung derselben DateTimeFormatter-Muster und derselben Einschränkungen. Beide zusammen bilden die I/O-Grenze für jeden Code, der Datumsangaben mit Benutzern, Konfigurationen, Logs oder externen APIs austauscht.