Skip to main content

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

SymbolMeaningExample
yyyy4-digit year2026
yy2-digit year26
MM2-digit month (01-12)03
MMonth without zero-pad (1-12)3
MMMAbbreviated month nameMar
MMMMFull month nameMarch
dd2-digit day (01-31)23
dDay without zero-pad (1-31)23
EEEAbbreviated weekdayMon
EEEEFull weekdayMonday
HHHour in 24h (00-23)14
hhHour in 12h (01-12)02
mmMinutes (00-59)30
ssSeconds (00-59)45
SSSMilliseconds123
aAM/PMPM
zTimezone nameKST
ZTimezone 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
}
}

Pro Tips

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:

FeatureDateTimeFormatterSimpleDateFormat
Thread-safeYesNo (use ThreadLocal or recreate)
ImmutableYesNo
Works withjava.time.*java.util.Date
Locale supportBuilt-inLimited

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.