10.3 Formatting and Parsing Dates
Overview
Dates must be formatted(converted to string) for display, and parsed(converted from string) when received from user input or external systems. DateTimeFormatter is the central class for both operations.
1. DateTimeFormatter Basics
DateTimeFormatter is immutable and thread-safe, unlike the old SimpleDateFormat. It can be used as a static constant with no concurrency issues.
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
LocalDate today = LocalDate.of(2026, 3, 23);
LocalDateTime now = LocalDateTime.of(2026, 3, 23, 14, 30, 45);
// format(): date → string
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formatted = today.format(formatter);
System.out.println(formatted); // 2026-03-23
// parse(): string → date
LocalDate parsed = LocalDate.parse("2026-03-23", formatter);
System.out.println(parsed); // 2026-03-23
System.out.println(parsed.getClass()); // class java.time.LocalDate
2. Predefined Formatters
DateTimeFormatter includes many ready-to-use constants for standard formats:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
LocalDate date = LocalDate.of(2026, 3, 23);
LocalDateTime dt = LocalDateTime.of(2026, 3, 23, 14, 30, 45);
ZonedDateTime zdt = ZonedDateTime.of(dt, ZoneId.of("Asia/Seoul"));
// ISO standard formats (most common)
System.out.println(date.format(DateTimeFormatter.ISO_DATE)); // 2026-03-23
System.out.println(dt.format(DateTimeFormatter.ISO_DATE_TIME)); // 2026-03-23T14:30:45
System.out.println(dt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); // 2026-03-23T14:30:45
System.out.println(zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); // 2026-03-23T14:30:45+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); // 2026-03-23T14:30:45+09:00[Asia/Seoul]
// Basic ISO (compact, no separators)
System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE)); // 20260323
// RFC-1123 (for HTTP headers)
System.out.println(zdt.format(DateTimeFormatter.RFC_1123_DATE_TIME)); // Mon, 23 Mar 2026 14:30:45 +0900
3. Custom Patterns with ofPattern()
Use ofPattern() to define any date/time string format you need:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
LocalDate date = LocalDate.of(2026, 3, 23);
LocalDateTime dt = LocalDateTime.of(2026, 3, 23, 14, 30, 45);
// Various date formats
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("yyyy.MM.dd");
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("MM/dd/yyyy");
System.out.println(date.format(f1)); // 2026-03-23
System.out.println(date.format(f2)); // 2026.03.23
System.out.println(date.format(f3)); // 03/23/2026
// Date + Time formats
DateTimeFormatter f4 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter f5 = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a");
DateTimeFormatter f6 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
System.out.println(dt.format(f4)); // 2026-03-23 14:30:45
System.out.println(dt.format(f5)); // 03/23/2026 02:30 PM
System.out.println(dt.format(f6)); // 2026-03-23T14:30:45
Pattern Symbols Reference
| Symbol | Meaning | Example |
|---|---|---|
yyyy | 4-digit year | 2026 |
yy | 2-digit year | 26 |
MM | 2-digit month (01-12) | 03 |
M | Month without zero-pad (1-12) | 3 |
MMM | Abbreviated month name | Mar |
MMMM | Full month name | March |
dd | 2-digit day (01-31) | 23 |
d | Day without zero-pad (1-31) | 23 |
EEE | Abbreviated weekday | Mon |
EEEE | Full weekday | Monday |
HH | Hour in 24h (00-23) | 14 |
hh | Hour in 12h (01-12) | 02 |
mm | Minutes (00-59) | 30 |
ss | Seconds (00-59) | 45 |
SSS | Milliseconds | 123 |
a | AM/PM | PM |
z | Timezone name | KST |
Z | Timezone offset | +0900 |
'text' | Literal text (escaped) | 'T' |
4. Parsing — String to Date
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
// Parse LocalDate
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date1 = LocalDate.parse("2026-03-23", dateFormatter);
System.out.println(date1); // 2026-03-23
// Parse different formats
LocalDate date2 = LocalDate.parse("23/03/2026", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
LocalDate date3 = LocalDate.parse("March 23, 2026", DateTimeFormatter.ofPattern("MMMM dd, yyyy"));
System.out.println(date2); // 2026-03-23
System.out.println(date3); // 2026-03-23
// Parse LocalDateTime
DateTimeFormatter dtFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse("2026-03-23 14:30:45", dtFormatter);
System.out.println(dt); // 2026-03-23T14:30:45
// ISO format can be parsed without specifying a formatter
LocalDate isoDate = LocalDate.parse("2026-03-23"); // uses ISO_LOCAL_DATE
LocalDateTime isoDt = LocalDateTime.parse("2026-03-23T14:30:45"); // uses ISO_LOCAL_DATE_TIME
// Error handling
try {
LocalDate invalid = LocalDate.parse("not-a-date", dateFormatter);
} catch (DateTimeParseException e) {
System.out.println("Parse error: " + e.getMessage());
// Text 'not-a-date' could not be parsed at index 0
}
5. Locale-Based Formatting
Display dates in different languages using DateTimeFormatter.ofLocalizedDate() or by specifying a Locale:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
LocalDate date = LocalDate.of(2026, 3, 23);
// Explicit locale
DateTimeFormatter koFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.KOREAN);
DateTimeFormatter enFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.US);
DateTimeFormatter frFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.FRANCE);
System.out.println(date.format(koFormatter)); // 2026년 3월 23일 월요일
System.out.println(date.format(enFormatter)); // Monday, March 23, 2026
System.out.println(date.format(frFormatter)); // lundi 23 mars 2026
// FormatStyle options: FULL, LONG, MEDIUM, SHORT
for (FormatStyle style : FormatStyle.values()) {
System.out.printf("%-8s: %s%n", style,
date.format(DateTimeFormatter.ofLocalizedDate(style).withLocale(Locale.US)));
}
// FULL : Monday, March 23, 2026
// LONG : March 23, 2026
// MEDIUM : Mar 23, 2026
// SHORT : 3/23/26
// Weekday name in different languages
DateTimeFormatter weekdayKo = DateTimeFormatter.ofPattern("EEEE", Locale.KOREAN);
DateTimeFormatter weekdayEn = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH);
System.out.println(date.format(weekdayKo)); // 월요일
System.out.println(date.format(weekdayEn)); // Monday
6. Converting Legacy Date to java.time
When working with older APIs that return java.util.Date or java.sql.Timestamp, convert them to java.time objects:
import java.time.*;
import java.util.Date;
// java.util.Date → Instant → LocalDateTime
Date legacyDate = new Date(); // old Date object
Instant instant = legacyDate.toInstant();
LocalDateTime modern = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println("Modern: " + modern);
// LocalDateTime → java.util.Date (for legacy API)
LocalDateTime ldt = LocalDateTime.of(2026, 3, 23, 14, 30, 0);
Instant toInstant = ldt.atZone(ZoneId.systemDefault()).toInstant();
Date backToLegacy = Date.from(toInstant);
System.out.println("Legacy: " + backToLegacy);
// java.sql.Date <-> LocalDate
java.sql.Date sqlDate = java.sql.Date.valueOf(LocalDate.of(2026, 3, 23));
LocalDate fromSql = sqlDate.toLocalDate();
System.out.println("From SQL: " + fromSql);
// java.sql.Timestamp <-> LocalDateTime
java.sql.Timestamp ts = java.sql.Timestamp.valueOf(
LocalDateTime.of(2026, 3, 23, 14, 30, 45));
LocalDateTime fromTs = ts.toLocalDateTime();
System.out.println("From Timestamp: " + fromTs);
7. Practical Example: Flexible Date Input Parser
A utility that accepts multiple common date formats from user input:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
public class FlexibleDateParser {
private static final List<DateTimeFormatter> FORMATTERS = List.of(
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("yyyy/MM/dd"),
DateTimeFormatter.ofPattern("yyyy.MM.dd"),
DateTimeFormatter.ofPattern("MM/dd/yyyy"),
DateTimeFormatter.ofPattern("dd-MM-yyyy"),
DateTimeFormatter.ofPattern("yyyyMMdd"),
DateTimeFormatter.ISO_LOCAL_DATE
);
public static LocalDate parse(String input) {
String trimmed = input.trim();
for (DateTimeFormatter formatter : FORMATTERS) {
try {
return LocalDate.parse(trimmed, formatter);
} catch (DateTimeParseException ignored) {
// try next format
}
}
throw new IllegalArgumentException("Cannot parse date: '" + input + "'");
}
public static String format(LocalDate date, String pattern) {
return date.format(DateTimeFormatter.ofPattern(pattern));
}
public static void main(String[] args) {
String[] inputs = {
"2026-03-23",
"2026/03/23",
"2026.03.23",
"03/23/2026",
"20260323",
};
for (String input : inputs) {
LocalDate date = parse(input);
System.out.printf("%-12s -> %s (weekday: %s)%n",
input, date, date.getDayOfWeek());
}
// Formatted output examples
LocalDate date = LocalDate.of(2026, 3, 23);
System.out.println("\nFormatted outputs:");
System.out.println(format(date, "yyyy-MM-dd")); // 2026-03-23
System.out.println(format(date, "MMMM dd, yyyy")); // March 23, 2026
System.out.println(format(date, "dd/MM/yyyy")); // 23/03/2026
System.out.println(format(date, "yyyyMMdd")); // 20260323
}
}
Always reuse DateTimeFormatter instances:
// BAD: creating a new formatter on every call
public String formatDate(LocalDate date) {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); // new object each time
}
// GOOD: define as a static constant
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public String formatDate(LocalDate date) {
return date.format(DATE_FORMATTER); // thread-safe, zero allocation
}
DateTimeFormatter vs SimpleDateFormat:
| Feature | DateTimeFormatter | SimpleDateFormat |
|---|---|---|
| Thread-safe | Yes | No (use ThreadLocal or recreate) |
| Immutable | Yes | No |
| Works with | java.time.* | java.util.Date |
| Locale support | Built-in | Limited |
Tip for databases: When storing dates in databases, prefer ISO format (yyyy-MM-dd) or use java.sql.Date.valueOf(localDate). Avoid locale-specific formats in persistence layers.