DateTimeFormatter in Java: Formatting and Parsing Dates with Patterns - Quiz
Total: 6 questions
1. What is DateTimeFormatter in Java, and how do you use it to turn a date into a string and a string into a date?
What is DateTimeFormatter in Java, and how do you use it to turn a date into a string and a string into a date?
java.time.format.DateTimeFormatter is an immutable, thread-safe class from Java 8 that formats a date and time into a string and parses a string back. You create one from a pattern with ofPattern("MM/dd/uuuu") or ofPattern(pattern, Locale), take a ready-made constant (ISO_LOCAL_DATE) or a localized format with ofLocalizedDate(FormatStyle). Letters that are not pattern symbols go in single quotes: "yyyy-MM-dd'T'HH:mm".
DateTimeFormatter f = DateTimeFormatter.ofPattern("MM/dd/uuuu");
String s = LocalDate.of(2024, 3, 15).format(f); // 03/15/2024 (same as f.format(date))
LocalDate d1 = LocalDate.parse("03/15/2024", f); // 2024-03-15
LocalDate d2 = f.parse("03/15/2024", LocalDate::from); // 2024-03-15
TemporalAccessor raw = f.parse("03/15/2024"); // a set of fields, not a LocalDate
LocalDate.parse("03/15/2024 10:00", f); // DateTimeParseException: ... unparsed text found at index 10
When parsing, the whole string must match the pattern. The withLocale(), withZone() and withResolverStyle() methods do not modify the formatter, they return a new one — assign the result.
2. Which predefined formatters does DateTimeFormatter offer, and what do they print?
Which predefined formatters does DateTimeFormatter offer, and what do they print?
For standard formats there are ready-made ISO-8601 constants and a few others. You don't create them, they are thread-safe, and they are what parse(text) without a formatter uses.
LocalDateTime dt = LocalDateTime.of(2024, 3, 15, 14, 30, 45, 123_000_000);
ZonedDateTime zdt = dt.atZone(ZoneId.of("America/New_York"));
DateTimeFormatter.ISO_LOCAL_DATE.format(dt); // 2024-03-15
DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(dt); // 2024-03-15T14:30:45.123
DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zdt); // 2024-03-15T14:30:45.123-04:00
DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zdt); // 2024-03-15T14:30:45.123-04:00[America/New_York]
DateTimeFormatter.ISO_INSTANT.format(zdt); // 2024-03-15T18:30:45.123Z
DateTimeFormatter.BASIC_ISO_DATE.format(dt); // 20240315
DateTimeFormatter.RFC_1123_DATE_TIME.format(zdt); // Fri, 15 Mar 2024 14:30:45 -0400
DateTimeFormatter.ISO_INSTANT.format(dt); // UnsupportedTemporalTypeException: Unsupported field: InstantSeconds
ISO formatters print the fraction of a second only when it is present and without trailing zeros, and accept 0 to 9 fraction digits when parsing. ISO_INSTANT needs an actual moment in time, so it does not work for a LocalDateTime without a zone. RFC_1123_DATE_TIME is the format of the HTTP Date and Last-Modified headers, and its names are always in English.
3. How does a Locale affect DateTimeFormatter, and what is the difference between MMMM and LLLL?
How does a Locale affect DateTimeFormatter, and what is the difference between MMMM and LLLL?
The Locale sets the language of month and day names, the AM/PM marker, the first day of the week and the shape of localized formats. Pass it as the second argument of ofPattern(pattern, locale) or with withLocale(); without it the formatter uses Locale.getDefault(Locale.Category.FORMAT), so parsing «June 5, 2018» fails on a server with de_DE. MMMM and LLLL look the same in English, but in languages with grammatical cases MMMM is the form used inside a date and LLLL is the standalone form for a header:
LocalDate date = LocalDate.of(2024, 3, 15);
Locale pl = Locale.forLanguageTag("pl");
date.format(DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy", Locale.US)); // Friday, March 15, 2024
YearMonth.of(2024, 3).format(DateTimeFormatter.ofPattern("MMMM yyyy", pl)); // marca 2024
YearMonth.of(2024, 3).format(DateTimeFormatter.ofPattern("LLLL yyyy", pl)); // marzec 2024
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
date.format(f.withLocale(Locale.US)); // March 15, 2024
date.format(f.withLocale(Locale.GERMANY)); // 15. März 2024
Localized formats (FULL, LONG, MEDIUM, SHORT) come from the JDK locale data and change between Java versions, so use an explicit pattern or an ISO formatter for files, APIs and databases. ofLocalizedDateTime(FormatStyle.FULL) or LONG on a LocalDateTime throws DateTimeException: Unable to extract ZoneId from temporal — these styles print the time zone.
4. How do you format and parse milliseconds with DateTimeFormatter?
How do you format and parse milliseconds with DateTimeFormatter?
The letter S means a fraction of a second, and the number of letters is the number of digits: SSS is milliseconds, SSSSSS microseconds, SSSSSSSSS nanoseconds. Extra digits are cut off without rounding. The letter n prints the nano-of-second as a plain integer, so it is wrong for a fraction:
LocalTime t = LocalTime.of(14, 30, 45, 123_456_789);
t.format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")); // 14:30:45.123
t.format(DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS")); // 14:30:45.123456
LocalTime.of(14, 30, 45, 5_000_000)
.format(DateTimeFormatter.ofPattern("HH:mm:ss.n")); // 14:30:45.5000000 (5 ms looks like half a second)
When parsing, ss.SSS requires exactly three digits: "2024-03-15 14:30:45.5" fails with DateTimeParseException ... at index 20. If the number of digits is not known in advance, use appendFraction():
DateTimeFormatter flexible = new DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd HH:mm:ss")
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
.toFormatter();
LocalDateTime.parse("2024-03-15 14:30:45.5", flexible); // 2024-03-15T14:30:45.500
LocalDateTime.parse("2024-03-15 14:30:45", flexible); // 2024-03-15T14:30:45
In Java 8 the pattern without separators yyyyMMddHHmmssSSS cannot parse "20240315143045123" (JDK-8031085); since Java 9 it works. The Java 8 workaround is appendPattern("yyyyMMddHHmmss") followed by appendValue(ChronoField.MILLI_OF_SECOND, 3).
5. What is DateTimeFormatterBuilder for, and which problems does it solve?
What is DateTimeFormatterBuilder for, and which problems does it solve?
DateTimeFormatterBuilder assembles a formatter piece by piece when a single ofPattern() is not enough: case-insensitive parsing, optional parts of the string, default values, a custom base for two-digit years, a fraction of variable length. It finishes with toFormatter().
DateTimeFormatter caseInsensitive = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // before appendPattern, otherwise it has no effect
.appendPattern("dd MMM uuuu")
.toFormatter(Locale.ENGLISH);
LocalDate.parse("15 MAR 2024", caseInsensitive); // 2024-03-15 (without parseCaseInsensitive: error at index 3)
DateTimeFormatter optionalTime = new DateTimeFormatterBuilder()
.appendPattern("uuuu-MM-dd[ HH:mm]")
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.toFormatter();
LocalDateTime.parse("2024-03-15", optionalTime); // 2024-03-15T00:00
DateTimeFormatter twoDigitYear = new DateTimeFormatterBuilder()
.appendPattern("MM/dd/")
.appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
.toFormatter();
LocalDate.parse("03/15/99", twoDigitYear); // 1999-03-15
LocalDate.parse("03/15/99", DateTimeFormatter.ofPattern("MM/dd/yy")); // 2099-03-15
The yy pattern always maps the year to 2000–2099, so birth dates from legacy systems need appendValueReduced(). Square brackets also work in a plain ofPattern(), but without parseDefaulting() a string without a time cannot be parsed into a LocalDateTime.
6. Is DateTimeFormatter thread-safe, and why is it better than SimpleDateFormat?
Is DateTimeFormatter thread-safe, and why is it better than SimpleDateFormat?
Yes. DateTimeFormatter is immutable and thread-safe: one instance can live in a static final field and be used from any number of threads at once without synchronization.
public static final DateTimeFormatter US_DATE = DateTimeFormatter.ofPattern("MM/dd/uuuu");
String s = LocalDate.of(2024, 3, 15).format(US_DATE); // 03/15/2024
java.text.SimpleDateFormat keeps intermediate state in an internal Calendar field, so a shared instance produces wrong dates or fails with NumberFormatException under concurrent use. With it you had to create a new object per call or wrap it in a ThreadLocal. In DateTimeFormatter the withLocale(), withZone() and withResolverStyle() methods return a new object, so a shared constant cannot be changed by another thread.