20.3 DateTimeFormatter Patterns and Construction
DateTimeFormatter provides comprehensive support for converting temporal objects to strings and parsing strings back to temporal objects. Mastering formatter patterns and construction techniques is essential for all date-time I/O operations.
Predefined Formatters
The DateTimeFormatter class provides a comprehensive set of predefined formatters for common date-time patterns.
// Predefined ISO 8601 formatters
ZonedDateTime zdt = ZonedDateTime.now();
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
Instant instant = Instant.now();
// ISO formatters for various temporal types
String isoDate = DateTimeFormatter.ISO_DATE.format(date); // 2025-01-15
String isoTime = DateTimeFormatter.ISO_TIME.format(time); // 14:30:45.123456789
String isoDateTime = DateTimeFormatter.ISO_DATE_TIME.format(dateTime); // 2025-01-15T14:30:45.123456789
String isoZonedDateTime = DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zdt); // 2025-01-15T14:30:45.123456789-08:00[America/Los_Angeles]
String isoOffsetDateTime = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zdt); // 2025-01-15T14:30:45.123456789-08:00
String isoInstant = DateTimeFormatter.ISO_INSTANT.format(instant); // 2025-01-15T22:30:45.123456789Z
String isoWeekDate = DateTimeFormatter.ISO_WEEK_DATE.format(date); // 2025-W03-3
String basicIsoDate = DateTimeFormatter.BASIC_ISO_DATE.format(date); // 20250115
System.out.println("ISO Date: " + isoDate);
System.out.println("ISO DateTime: " + isoDateTime);
System.out.println("Basic ISO Date: " + basicIsoDate);
// RFC 1123 date time (HTTP header format)
String rfc1123 = DateTimeFormatter.RFC_1123_DATE_TIME.format(
ZonedDateTime.now(ZoneId.of("GMT"))
); // Wed, 15 Jan 2025 22:30:45 GMT
// Parsing with predefined formatters
LocalDate parsedDate = LocalDate.parse("2025-01-15", DateTimeFormatter.ISO_DATE);
LocalDateTime parsedDateTime = LocalDateTime.parse(
"2025-01-15T14:30:45.123456789",
DateTimeFormatter.ISO_DATE_TIME
);
Custom Pattern Creation
Creating custom patterns provides flexibility for domain-specific formatting requirements.
// Simple pattern creation with ofPattern()
LocalDate date = LocalDate.of(2025, 1, 15);
LocalDateTime dateTime = LocalDateTime.of(2025, 1, 15, 14, 30, 45);
// Common format examples
DateTimeFormatter usDateFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
DateTimeFormatter euDateFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter isoFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String usFormat = usDateFormatter.format(date); // 01/15/2025
String euFormat = euDateFormatter.format(date); // 15/01/2025
String isoFormat = isoFormatter.format(date); // 2025-01-15
// Time patterns
DateTimeFormatter time24Hour = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter time12Hour = DateTimeFormatter.ofPattern("hh:mm:ss a");
DateTimeFormatter timeWithNanos = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS");
LocalTime time = LocalTime.of(14, 30, 45, 123456789);
System.out.println("24-hour: " + time24Hour.format(time)); // 14:30:45
System.out.println("12-hour: " + time12Hour.format(time)); // 02:30:45 PM
System.out.println("With nanos: " + timeWithNanos.format(time)); // 14:30:45.123456789
// Combined date-time patterns
DateTimeFormatter fullDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter readableDateTime = DateTimeFormatter.ofPattern("MMM dd, yyyy 'at' hh:mm a");
DateTimeFormatter europeanDateTime = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
System.out.println("Full: " + fullDateTime.format(dateTime)); // 2025-01-15 14:30:45
System.out.println("Readable: " + readableDateTime.format(dateTime)); // Jan 15, 2025 at 02:30 PM
System.out.println("European: " + europeanDateTime.format(dateTime)); // 15/01/2025 14:30
Pattern Symbol Reference
Pattern symbols define how different temporal fields are formatted.
// Comprehensive pattern symbol reference
LocalDate date = LocalDate.of(2025, 1, 15);
LocalTime time = LocalTime.of(14, 30, 45, 123456789);
LocalDateTime dateTime = LocalDateTime.of(date, time);
// Year symbols
DateTimeFormatter y = DateTimeFormatter.ofPattern("y"); // 2025
DateTimeFormatter yy = DateTimeFormatter.ofPattern("yy"); // 25
DateTimeFormatter yyyy = DateTimeFormatter.ofPattern("yyyy"); // 2025
DateTimeFormatter yyyyy = DateTimeFormatter.ofPattern("yyyyy"); // 02025
// Month symbols
DateTimeFormatter M = DateTimeFormatter.ofPattern("M"); // 1
DateTimeFormatter MM = DateTimeFormatter.ofPattern("MM"); // 01
DateTimeFormatter MMM = DateTimeFormatter.ofPattern("MMM"); // Jan
DateTimeFormatter MMMM = DateTimeFormatter.ofPattern("MMMM"); // January
DateTimeFormatter MMMMM = DateTimeFormatter.ofPattern("MMMMM"); // J (standalone)
// Day symbols
DateTimeFormatter d = DateTimeFormatter.ofPattern("d"); // 15
DateTimeFormatter dd = DateTimeFormatter.ofPattern("dd"); // 15
DateTimeFormatter D = DateTimeFormatter.ofPattern("D"); // 15 (day of year)
DateTimeFormatter DD = DateTimeFormatter.ofPattern("DD"); // 015 (day of year)
DateTimeFormatter E = DateTimeFormatter.ofPattern("E"); // Wed
DateTimeFormatter EEEE = DateTimeFormatter.ofPattern("EEEE"); // Wednesday
DateTimeFormatter EEEEE = DateTimeFormatter.ofPattern("EEEEE"); // W (standalone)
// Hour symbols (24-hour format)
DateTimeFormatter H = DateTimeFormatter.ofPattern("H"); // 14
DateTimeFormatter HH = DateTimeFormatter.ofPattern("HH"); // 14
// Hour symbols (12-hour format)
DateTimeFormatter h = DateTimeFormatter.ofPattern("h"); // 2
DateTimeFormatter hh = DateTimeFormatter.ofPattern("hh"); // 02
// Minute and second symbols
DateTimeFormatter m = DateTimeFormatter.ofPattern("m"); // 30
DateTimeFormatter mm = DateTimeFormatter.ofPattern("mm"); // 30
DateTimeFormatter s = DateTimeFormatter.ofPattern("s"); // 45
DateTimeFormatter ss = DateTimeFormatter.ofPattern("ss"); // 45
// Nanosecond precision symbols
DateTimeFormatter S = DateTimeFormatter.ofPattern("S"); // 1
DateTimeFormatter SS = DateTimeFormatter.ofPattern("SS"); // 12
DateTimeFormatter SSS = DateTimeFormatter.ofPattern("SSS"); // 123
DateTimeFormatter SSSSSSSSS = DateTimeFormatter.ofPattern("SSSSSSSSS"); // 123456789
// AM/PM indicator
DateTimeFormatter a = DateTimeFormatter.ofPattern("a"); // PM
// Timezone symbols
DateTimeFormatter z = DateTimeFormatter.ofPattern("z"); // PST (short name)
DateTimeFormatter zzzz = DateTimeFormatter.ofPattern("zzzz"); // Pacific Standard Time
ZonedDateTime zdt = ZonedDateTime.now();
DateTimeFormatter Z = DateTimeFormatter.ofPattern("Z"); // -0800
DateTimeFormatter ZZ = DateTimeFormatter.ofPattern("ZZ"); // -08:00
DateTimeFormatter X = DateTimeFormatter.ofPattern("X"); // -08
DateTimeFormatter XX = DateTimeFormatter.ofPattern("XX"); // -0800
DateTimeFormatter XXX = DateTimeFormatter.ofPattern("XXX"); // -08:00
DateTimeFormatter V = DateTimeFormatter.ofPattern("V"); // America/Los_Angeles
System.out.println("Year: " + yyyy.format(date)); // 2025
System.out.println("Full date: " + EEEE.format(date) + ", " + MMMM.format(date) + " " + d.format(date)); // Wednesday, January 15
System.out.println("Time: " + HH.format(dateTime) + ":" + mm.format(dateTime)); // 14:30
System.out.println("With nanos: " + SSSSSSSSS.format(dateTime)); // 123456789
Escaped Literals in Patterns
Literal text can be included in patterns to create readable formatted output.
// Patterns with literal text
LocalDate date = LocalDate.of(2025, 1, 15);
LocalTime time = LocalTime.of(14, 30, 45);
LocalDateTime dateTime = LocalDateTime.of(date, time);
// Single quotes escape literal text
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("'Date:' yyyy-MM-dd");
System.out.println(formatter1.format(date)); // Date: 2025-01-15
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("'Time:' HH:mm:ss");
System.out.println(formatter2.format(time)); // Time: 14:30:45
DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("'On' EEEE, MMMM dd, yyyy 'at' hh:mm a");
System.out.println(formatter3.format(dateTime)); // On Wednesday, January 15, 2025 at 02:30 PM
// Single quote escaping
DateTimeFormatter formatter4 = DateTimeFormatter.ofPattern("yyyy''MM"); // Contains apostrophe
System.out.println(formatter4.format(date)); // 2025'01
// Complex literal text
DateTimeFormatter invoice = DateTimeFormatter.ofPattern("'Invoice Date: 'dd MMMM yyyy");
System.out.println(invoice.format(date)); // Invoice Date: 15 January 2025
Localized Formatting
The withLocale() method enables formatting in different languages and cultural conventions.
// Localized formatting with different locales
LocalDate date = LocalDate.of(2025, 1, 15);
LocalTime time = LocalTime.of(14, 30, 45);
LocalDateTime dateTime = LocalDateTime.of(date, time);
// FormatStyle options: FULL, LONG, MEDIUM, SHORT
DateTimeFormatter fullStyle = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.US); // Wednesday, January 15, 2025
DateTimeFormatter longStyle = DateTimeFormatter
.ofLocalizedDate(FormatStyle.LONG)
.withLocale(Locale.US); // January 15, 2025
DateTimeFormatter mediumStyle = DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.US); // Jan 15, 2025
DateTimeFormatter shortStyle = DateTimeFormatter
.ofLocalizedDate(FormatStyle.SHORT)
.withLocale(Locale.US); // 1/15/25
System.out.println("Full: " + fullStyle.format(date));
System.out.println("Long: " + longStyle.format(date));
System.out.println("Medium: " + mediumStyle.format(date));
System.out.println("Short: " + shortStyle.format(date));
// Multi-locale examples
Locale[] locales = {Locale.US, Locale.UK, Locale.FRANCE, Locale.GERMANY,
Locale.JAPAN, new Locale("es", "ES")};
DateTimeFormatter mediumFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
System.out.println("\nMulti-locale DateTime Formatting:");
for (Locale locale : locales) {
String formatted = mediumFormatter.withLocale(locale).format(dateTime);
System.out.printf("%-15s: %s%n", locale.toString(), formatted);
}
// Combined date and time with locale
DateTimeFormatter dateTimeFormatter = DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.LONG, FormatStyle.SHORT)
.withLocale(Locale.FRANCE);
System.out.println(dateTimeFormatter.format(dateTime)); // 15 janvier 2025 14:30
DateTimeFormatterBuilder
The DateTimeFormatterBuilder class enables programmatic construction of complex formatters with optional sections, variable widths, and custom logic.
// Basic DateTimeFormatterBuilder usage
LocalDate date = LocalDate.of(2025, 1, 15);
LocalDateTime dateTime = LocalDateTime.of(date, LocalTime.of(14, 30, 45));
// Building a custom formatter
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.appendLiteral('-')
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.appendLiteral(' ')
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.appendLiteral(':')
.appendValue(ChronoField.SECOND_OF_MINUTE, 2);
DateTimeFormatter customFormatter = builder.toFormatter();
System.out.println(customFormatter.format(dateTime)); // 2025-01-15 14:30:45
// Optional sections with optionalStart() and optionalEnd()
DateTimeFormatterBuilder flexibleBuilder = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR, 4)
.appendLiteral('-')
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.optionalStart() // Start optional section
.appendLiteral(' ')
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(':')
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.optionalEnd(); // End optional section
DateTimeFormatter flexibleFormatter = flexibleBuilder.toFormatter();
System.out.println(flexibleFormatter.format(date)); // 2025-01-15
System.out.println(flexibleFormatter.format(dateTime)); // 2025-01-15 14:30
// Appending existing formatters
DateTimeFormatterBuilder compositeBuilder = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_DATE)
.appendLiteral(" at ")
.append(DateTimeFormatter.ISO_TIME);
DateTimeFormatter composite = compositeBuilder.toFormatter();
System.out.println(composite.format(dateTime)); // 2025-01-15 at 14:30:45.000000000
// Using appendPattern() for pattern strings within builder
DateTimeFormatterBuilder patternBuilder = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd")
.appendLiteral(" | ")
.appendPattern("HH:mm:ss");
DateTimeFormatter patternFormatter = patternBuilder.toFormatter();
System.out.println(patternFormatter.format(dateTime)); // 2025-01-15 | 14:30:45
// appendText() for text-based fields
DateTimeFormatterBuilder textBuilder = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
.appendLiteral(", ")
.appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
.appendLiteral(" ")
.appendValue(ChronoField.DAY_OF_MONTH);
DateTimeFormatter textFormatter = textBuilder.toFormatter(Locale.US);
System.out.println(textFormatter.format(date)); // Wednesday, January 15
Formatter Caching for Performance
Creating formatters is relatively expensive; caching improves performance in high-throughput applications.
// Formatter caching pattern
class FormatterCache {
private static final Map<String, DateTimeFormatter> cache =
new ConcurrentHashMap<>();
public static DateTimeFormatter getFormatter(String pattern) {
return cache.computeIfAbsent(pattern,
p -> DateTimeFormatter.ofPattern(p));
}
public static DateTimeFormatter getFormatter(String pattern, Locale locale) {
String key = pattern + "_" + locale;
return cache.computeIfAbsent(key,
k -> DateTimeFormatter.ofPattern(pattern).withLocale(locale));
}
public static void clearCache() {
cache.clear();
}
}
// Using cached formatters
DateTimeFormatter formatter1 = FormatterCache.getFormatter("yyyy-MM-dd");
DateTimeFormatter formatter2 = FormatterCache.getFormatter("yyyy-MM-dd"); // Same instance
System.out.println("Cached: " + (formatter1 == formatter2)); // true
LocalDate date = LocalDate.now();
System.out.println(formatter1.format(date));
Common Formatter Patterns
Reference collection of frequently-used patterns for various scenarios.
// Common formatter patterns
LocalDate date = LocalDate.of(2025, 1, 15);
LocalTime time = LocalTime.of(14, 30, 45);
LocalDateTime dateTime = LocalDateTime.of(date, time);
// ISO formats (recommended for APIs)
DateTimeFormatter iso = DateTimeFormatter.ISO_DATE;
// US formats
DateTimeFormatter usDateShort = DateTimeFormatter.ofPattern("M/d/yy"); // 1/15/25
DateTimeFormatter usDateMedium = DateTimeFormatter.ofPattern("MMM d, yyyy"); // Jan 15, 2025
DateTimeFormatter usDateLong = DateTimeFormatter.ofPattern("MMMM d, yyyy"); // January 15, 2025
DateTimeFormatter usTime12 = DateTimeFormatter.ofPattern("hh:mm:ss a"); // 02:30:45 PM
// European formats
DateTimeFormatter euDate = DateTimeFormatter.ofPattern("dd.MM.yyyy"); // 15.01.2025
DateTimeFormatter euDateSlash = DateTimeFormatter.ofPattern("dd/MM/yyyy"); // 15/01/2025
// Database formats
DateTimeFormatter dbDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter dbDateOnly = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// Log formats
DateTimeFormatter logFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
// RFC formats
DateTimeFormatter rfc1123 = DateTimeFormatter.RFC_1123_DATE_TIME;
System.out.println("US Date Short: " + usDateShort.format(date));
System.out.println("EU Date: " + euDate.format(date));
System.out.println("DB DateTime: " + dbDateTime.format(dateTime));
System.out.println("Log Format: " + logFormat.format(dateTime));
Best Practices
- Use predefined formatters when possible:
DateTimeFormatter.ISO_DATEis faster and more reliable than patterns. - Cache custom formatters: Store formatter instances to avoid repeated parsing of patterns.
- Use ISO-8601 for APIs: Ensures compatibility across systems and locales.
- Localize user-facing output: Use
ofLocalizedDate()andofLocalizedDateTime()for display. - Be explicit with patterns: Use the full pattern (e.g., "yyyy-MM-dd") rather than abbreviations.
- Use builder for complex formatters:
DateTimeFormatterBuilderprovides more control than patterns. - Validate parsed input: Always handle parsing exceptions and unexpected formats.
- Consider timezone in display: Use
ISO_OFFSET_DATE_TIMEorISO_ZONED_DATE_TIMEwhen timezone matters.