Java DateTimeFormatter Playground
What is DateTimeFormatter?
java.time.format.DateTimeFormatter is the modern Java class for formatting and parsing date/time values. Introduced in Java 8 as part of JSR-310 (the java.time package), it replaces the old SimpleDateFormat from java.text.
Three things make it superior to the legacy API: it is immutable and therefore thread-safe, it integrates with the full java.time type hierarchy (LocalDate, LocalDateTime, ZonedDateTime, Instant, OffsetDateTime), and it supports a much richer set of pattern letters.
Pattern letters reference
A pattern is a string of letters where each letter (and its repetition count) maps to a date-time component. Non-letter characters appear verbatim; letters wrapped in single quotes are treated as literals.
| Letter | Meaning | Example |
|---|---|---|
y | Year of era | yyyy β 2026 |
u | Proleptic year (can be negative) | uuuu β 2026 |
M / L | Month (M = formatting, L = standalone) | M β 4, MM β 04, MMM β Apr, MMMM β April |
d | Day of month | dd β 07 |
D | Day of year | DDD β 097 |
E | Day of week (text) | E β Tue, EEEE β Tuesday |
e | Day of week (number, locale-sensitive) | e β 2 |
a | AM/PM marker | a β PM |
H | Hour of day (0β23) | HH β 14 |
k | Hour of day (1β24) | kk β 24 |
h | Clock hour (1β12) | hh β 02 |
K | Clock hour (0β11) | KK β 02 |
m | Minute of hour | mm β 30 |
s | Second of minute | ss β 45 |
S | Fraction of second | SSS β 123 (milliseconds) |
n | Nano of second | n β 123000000 |
V | Time zone ID | VV β Europe/Paris |
z | Time zone name | z β CEST, zzzz β Central European Summer Time |
Z | Zone offset, no colon | Z β +0200 |
X | ISO zone offset, Z for UTC | X β +02, XX β +0200, XXX β +02:00 |
x | ISO zone offset, +0000 for UTC | xxx β +02:00 |
'text' | Literal text | 'T' β T |
'' | Escaped single quote | '' β ' |
Letters A-Z and a-z are reserved for future use. Avoid using letters not listed above as literals unless quoted.
Predefined formatters
DateTimeFormatter ships with constants for the most common ISO-8601 formats:
| Constant | Pattern | Example |
|---|---|---|
ISO_LOCAL_DATE | yyyy-MM-dd | 2026-04-20 |
ISO_LOCAL_TIME | HH:mm:ss | 14:30:45 |
ISO_LOCAL_DATE_TIME | yyyy-MM-dd'T'HH:mm:ss | 2026-04-20T14:30:45 |
ISO_OFFSET_DATE_TIME | yyyy-MM-dd'T'HH:mm:ssXXX | 2026-04-20T14:30:45+02:00 |
ISO_ZONED_DATE_TIME | offset + [zone] | 2026-04-20T14:30:45+02:00[Europe/Paris] |
ISO_INSTANT | UTC Z form | 2026-04-20T12:30:45Z |
RFC_1123_DATE_TIME | HTTP / email | Mon, 20 Apr 2026 14:30:45 +0200 |
BASIC_ISO_DATE | yyyyMMdd | 20260420 |
Prefer a predefined formatter over a hand-written pattern whenever one fits β they're battle-tested and make intent obvious to readers.
Using DateTimeFormatter in code
Formatting
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
// Simple case
LocalDateTime now = LocalDateTime.now();
String s = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// Custom pattern
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm");
String s2 = now.format(fmt); // "20 Apr 2026 14:30"
// Locale + zone
ZonedDateTime paris = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
DateTimeFormatter french = DateTimeFormatter
.ofPattern("EEEE d MMMM yyyy 'Γ ' HH:mm", Locale.FRENCH);
String s3 = paris.format(french); // "lundi 20 avril 2026 Γ 14:30"
Parsing
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate d = LocalDate.parse("20/04/2026", fmt);
// Parse an ISO offset string
OffsetDateTime odt = OffsetDateTime.parse("2026-04-20T14:30:45+02:00");
// Parse with a specific zone
ZonedDateTime zdt = ZonedDateTime.parse("2026-04-20T14:30:45+02:00[Europe/Paris]");
Formatting an Instant
Instant has no zone, so you must provide one before formatting with fields that need a calendar:
Instant instant = Instant.now();
// This works β ISO_INSTANT is zone-agnostic
String iso = DateTimeFormatter.ISO_INSTANT.format(instant);
// This fails: ofPattern needs a zone for an Instant
DateTimeFormatter.ofPattern("yyyy-MM-dd").format(instant); // throws
// Right way:
DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withZone(ZoneId.of("UTC"))
.format(instant);
Locale-aware formatting
Text output β month names, weekday names, AM/PM markers β depends on the formatter's locale. If you don't set one, the formatter uses Locale.getDefault(), which is usually not what you want in a server application.
DateTimeFormatter us = DateTimeFormatter
.ofPattern("MMMM d, yyyy", Locale.US);
DateTimeFormatter fr = DateTimeFormatter
.ofPattern("d MMMM yyyy", Locale.FRANCE);
DateTimeFormatter de = DateTimeFormatter
.ofPattern("d. MMMM yyyy", Locale.GERMANY);
LocalDate d = LocalDate.of(2026, 4, 20);
us.format(d); // "April 20, 2026"
fr.format(d); // "20 avril 2026"
de.format(d); // "20. April 2026"
Rule of thumb for server code: always pass a Locale explicitly. Never rely on the JVM default β it can differ across Docker images, CI runners, and developer laptops.
Time zones and offsets
Three letters deal with zones: V (zone ID), z (zone name), and X / x / Z (offsets). They are not interchangeable:
VVβEurope/Parisβ the IANA zone ID, full canonical formzβCESTβ the zone's short name at that moment (varies with DST)zzzzβCentral European Summer Timeβ long nameZβ+0200β offset, no colonXXXβ+02:00β ISO offset with colon,Zfor UTCxxxβ+02:00β likeXXXbut+00:00for UTC (noZ)
For machine-readable output, prefer XXX (strictly ISO-8601). For logs read by humans, VV or z is friendlier.
DateTimeFormatter vs SimpleDateFormat
| SimpleDateFormat | DateTimeFormatter | |
|---|---|---|
| Package | java.text | java.time.format |
| Thread-safe | No | Yes (immutable) |
| Works with | Date, Calendar | All java.time types |
| Nano-seconds | No | Yes (S up to 9 digits) |
| Zone handling | Ambiguous (z, Z) | Clear distinction (V, z, X, x, Z) |
| Lenient by default | Yes (silent corruption) | No (strict, throws) |
| Introduced in | JDK 1.1 (1997) | JDK 8 (2014) |
If you see SimpleDateFormat in a code review, it's almost always worth migrating: a single shared SimpleDateFormat instance under load will produce garbage output eventually.
Common mistakes
- Using
YYYYinstead ofyyyy. UppercaseYYYYis the week-based year. On Dec 29-31 it can be off by one. Always use lowercaseyyyy. - Using
DDinstead ofdd. UppercaseDDis day-of-year (1β366). Lowercaseddis day-of-month. - Using
mmfor month. Minutes are lowercasem, months are uppercaseM. - Sharing a
SimpleDateFormatbetween threads. It's mutable and will corrupt output under concurrent access.DateTimeFormatteris immutable and safe to share. - Forgetting to quote a literal letter.
yyyy-MM-dd T HH:mmwill fail; useyyyy-MM-dd'T'HH:mm. - Formatting an
Instantwith fields that need a calendar.Instanthas no zone β use.withZone(ZoneId.of("UTC"))or convert toZonedDateTimefirst. - Assuming
zis parseable round-trip.CESTis ambiguous (it's used in multiple zones in some datasets). PreferVVfor serialization. - Not setting a locale. "Apr" vs "avr." depends on the JVM default locale, which can vary across deployment targets.
Related tools and guides
- Epoch Timestamp Converter β convert Unix timestamps to/from formatted dates
- Cron Expression Parser β schedule tasks based on formatted dates
- JWT Decoder β inspect
iat/exptimestamps inside tokens - Java Online Compiler β try the DateTimeFormatter snippets above