1. Overview
In this tutorial, weβll review the Java 8 DateTimeFormatter class and its formatting patterns. Weβll also discuss possible use cases for this class.
We can use DateTimeFormatter to uniformly format dates and times in an app with predefined or user-defined patterns.
Further reading:
Migrating to the Java Date Time API
Converting Between LocalDate and SQL Date
An Introduction to InstantSource in Java
2. DateTimeFormatter With Predefined Instances
DateTimeFormatter comes with multiple predefined date/time formats that follow ISO and RFC standards. For example, we can use the ISO_LOCAL_DATE instance to parse a date such as β2018-03-09β:
DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.of(2018, 3, 9));
To parse a date with an offset, we can use ISO_OFFSET_DATE to get an output like β2018-03-09-03:00β:
DateTimeFormatter.ISO_OFFSET_DATE.format(LocalDate.of(2018, 3, 9).atStartOfDay(ZoneId.of("UTC-3")));
Most of the predefined instances of the DateTimeFormatter class are focused on the ISO-8601 standard. ISO-8601 is an international standard for date and time formatting.
There is, however, one different predefined instance that parses RFC-1123, Requirement for Internet Hosts, published by the IETF:
DateTimeFormatter.RFC_1123_DATE_TIME.format(LocalDate.of(2018, 3, 9).atStartOfDay(ZoneId.of("UTC-3")));
This snippet generates βFri, 9 Mar 2018 00:00:00 -0300.β
Sometimes we have to manipulate the date we receive as a String of a known format. For this, we can make use of the parse() method:
LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse("2018-03-09")).plusDays(3);
The result of this code snippet is a LocalDate representation for March 12th, 2018.
3. DateTimeFormatter With FormatStyle
Sometimes we may want to print dates in a human-readable way.
In such cases, we may use java.time.format.FormatStyle enum (FULL, LONG, MEDIUM, SHORT) values with our DateTimeFormatter:
LocalDate anotherSummerDay = LocalDate.of(2016, 8, 23);
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(anotherSummerDay));
The output of these different formatting styles of the same date are:
Tuesday, August 23, 2016
August 23, 2016
Aug 23, 2016
8/23/16
We may also use predefined formatting styles for date and time. To use FormatStyle with time, we have to use the ZonedDateTime instance, otherwise, a DateTimeException will be thrown:
LocalDate anotherSummerDay = LocalDate.of(2016, 8, 23);
LocalTime anotherTime = LocalTime.of(13, 12, 45);
ZonedDateTime zonedDateTime = ZonedDateTime.of(anotherSummerDay, anotherTime, ZoneId.of("Europe/Helsinki"));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)
.format(zonedDateTime));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
.format(zonedDateTime));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
.format(zonedDateTime));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
.format(zonedDateTime));
Note that we used the ofLocalizedDateTime() method of DateTimeFormatter this time.
The output we get is:
Tuesday, August 23, 2016 1:12:45 PM EEST
August 23, 2016 1:12:45 PM EEST
Aug 23, 2016 1:12:45 PM
8/23/16 1:12 PM
We can also use FormatStyle to parse a date time String, converting it to ZonedDateTime, for example.
We can then use the parsed value to manipulate the date and time variable:
ZonedDateTime dateTime = ZonedDateTime.from(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)
.parse("Tuesday, August 23, 2016 1:12:45 PM EET"));
System.out.println(dateTime.plusHours(9));
The output of this snippet is β2016-08-23T22:12:45+03:00[Europe/Bucharest].β Notice that the time has changed to β22:12:45.β
4. DateTimeFormatter With Custom Formats
Predefined and built-in formatters and styles can cover a lot of situations. However, sometimes we need to format a date and time somewhat differently. This is when custom formatting patterns come into play.
4.1. DateTimeFormatter for Date
Suppose we want to present a java.time.LocalDate object using a regular European format like 31.12.2018. To do this, we could call the factory method DateTimeFormatter.ofPattern(βdd.MM.yyyyβ).
This will create an appropriate DateTimeFormatter instance that we can use to format our date:
String europeanDatePattern = "dd.MM.yyyy";
DateTimeFormatter europeanDateFormatter = DateTimeFormatter.ofPattern(europeanDatePattern);
System.out.println(europeanDateFormatter.format(LocalDate.of(2016, 7, 31)));
The output of this code snippet will be β31.07.2016.β
There are many different pattern letters that we can use to create a format for dates that will suit our needs:
Symbol Meaning Presentation Examples
------ ------- ------------ -------
u year year 2004; 04
y year-of-era year 2004; 04
M/L month-of-year number/text 7; 07; Jul; July; J
d day-of-month number 10
This is an extract of the official Java documentation to DateTimeFormatter class.
The number of letters in the pattern format is significant.
If we use a two-letter pattern for the month, weβll get a two-digit month representation. If the month number is less than 10, it will be padded with a zero. When we donβt need the mentioned padding with zeroes, we can use a one-letter pattern βM,β which will show January as β1.β
If we happen to use a four-letter pattern for the month, βMMMM,β then weβll get a βfull formβ representation. In our example, it would be βJuly.β A five-letter pattern, βMMMMM,β will make the formatter use the βnarrow form.β In our case, βJβ would be used.
Likewise, custom formatting patterns can also be used to parse a String that holds a date:
DateTimeFormatter europeanDateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
System.out.println(LocalDate.from(europeanDateFormatter.parse("15.08.2014")).isLeapYear());
This code snippet checks whether the date β15.08.2014β is one of a leap year, which it isnβt.
4.2. DateTimeFormatter for Time
There are also pattern letters that can be used for time patterns:
Symbol Meaning Presentation Examples
------ ------- ------------ -------
H hour-of-day (0-23) number 0
m minute-of-hour number 30
s second-of-minute number 55
S fraction-of-second fraction 978
n nano-of-second number 987654321
Itβs quite simple to use DateTimeFormatter to format a java.time.LocalTime instance. Suppose we want to show time (hours, minutes and seconds) delimited with a colon:
String timeColonPattern = "HH:mm:ss";
DateTimeFormatter timeColonFormatter = DateTimeFormatter.ofPattern(timeColonPattern);
LocalTime colonTime = LocalTime.of(17, 35, 50);
System.out.println(timeColonFormatter.format(colonTime));
This will generate output β17:35:50.β
If we want to add milliseconds to the output, we should add βSSSβ to the pattern:
String timeColonPattern = "HH:mm:ss SSS";
DateTimeFormatter timeColonFormatter = DateTimeFormatter.ofPattern(timeColonPattern);
LocalTime colonTime = LocalTime.of(17, 35, 50).plus(329, ChronoUnit.MILLIS);
System.out.println(timeColonFormatter.format(colonTime));
This gives us the output β17:35:50 329.β
Note that βHHβ is an hour-of-day pattern that generates the output of 0-23. When we want to show AM/PM, we should use lower-case βhhβ for hours and add an βaβ pattern:
String timeColonPattern = "hh:mm:ss a";
DateTimeFormatter timeColonFormatter = DateTimeFormatter.ofPattern(timeColonPattern);
LocalTime colonTime = LocalTime.of(17, 35, 50);
System.out.println(timeColonFormatter.format(colonTime));
The generated output is β05:35:50 PM.β
We may want to parse a time String with our custom formatter and check if itβs before noon:
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss a");
System.out.println(LocalTime.from(timeFormatter.parse("12:25:30 AM")).isBefore(LocalTime.NOON));
The output of this last snippet shows that the given time is actually before noon.
4.3. DateTimeFormatter for Time Zones
Often we want to see a time zone of some specific date-time variable. If we use New York-based date-time (UTC -4), we can use βzβ pattern-letter for time-zone name:
String newYorkDateTimePattern = "dd.MM.yyyy HH:mm z";
DateTimeFormatter newYorkDateFormatter = DateTimeFormatter.ofPattern(newYorkDateTimePattern);
LocalDateTime summerDay = LocalDateTime.of(2016, 7, 31, 14, 15);
System.out.println(newYorkDateFormatter.format(ZonedDateTime.of(summerDay, ZoneId.of("UTC-4"))));
This will generate the output β31.07.2016 14:15 UTC-04:00.β
We can parse date time strings with time zones just like we did earlier:
DateTimeFormatter zonedFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm z");
System.out.println(ZonedDateTime.from(zonedFormatter.parse("31.07.2016 14:15 GMT+02:00")).getOffset().getTotalSeconds());
The output of this code is β7200β seconds, or 2 hours, as weβd expect.
We have to make sure that we provide a correct date time String to the parse() method. If we pass β31.07.2016 14:15β without a time zone to the zonedFormatter from the last code snippet, weβll get a DateTimeParseException.
4.4. DateTimeFormatter Using Locales
Itβs possible not only to use a specific zone and get a correct time but also to produce a date formatting that would use a specific locale format. Letβs check it with US locale:
LocalDate date = LocalDate.of(2023, 9, 18);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd, yy: EEE").withLocale(Locale.US);
String formattedDate = date.format(formatter);
Here, we would expect to see the following output:
Sep 18, 23: Mon
Letβs try to use a more explicit formatting:
LocalDate date = LocalDate.of(2023, 9, 18);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM dd, yyyy: EEEE").withLocale(Locale.US);
String formattedDate = date.format(formatter);
This would produce the following result:
September 18, 2023: Monday
Now, we can change the local, and it would produce a correctly formatted date. In this case, weβll be using Korea. The code would be similar to the snippets above and isnβt included for brevity. Weβll have the following output in the first case:
9μ 18, 23: μ
And this for a more explicit one:
9μ 18, 2023: μμμΌ
This is a very convenient way to produce correct formatting for any local, and Java provides this opportunity out of the box.
4.5. DateTimeFormatter for Instant
DateTimeFormatter comes with a great ISO instant formatter called ISO_INSTANT. As the name implies, this formatter provides a convenient way to format or parse an instant in UTC.
According to the official documentation, an instant cannot be formatted as a date or time without specifying a time zone. So, attempting to use ISO_INSTANT on LocalDateTime or LocalDate objects will lead to an exception:
@Test(expected = UnsupportedTemporalTypeException.class)
public void shouldExpectAnExceptionIfInputIsLocalDateTime() {
DateTimeFormatter.ISO_INSTANT.format(LocalDateTime.now());
}
However, we can use ISO_INSTANT to format a ZonedDateTime instance without any issue:
@Test
public void shouldPrintFormattedZonedDateTime() {
ZonedDateTime zonedDateTime = ZonedDateTime.of(2021, 02, 15, 0, 0, 0, 0, ZoneId.of("Europe/Paris"));
String formattedZonedDateTime = DateTimeFormatter.ISO_INSTANT.format(zonedDateTime);
Assert.assertEquals("2021-02-14T23:00:00Z", formattedZonedDateTime);
}
As we can see, we created our ZonedDateTime with βEurope/Parisβ time zone. However, the formatted result is in UTC.
Similarly, when parsing to ZonedDateTime, we need to specify the time zone:
@Test
public void shouldParseZonedDateTime() {
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault());
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2021-10-01T05:06:20Z", formatter);
Assert.assertEquals("2021-10-01T05:06:20Z", DateTimeFormatter.ISO_INSTANT.format(zonedDateTime));
}
Failing to do so will lead to DateTimeParseException:
@Test(expected = DateTimeParseException.class)
public void shouldExpectAnExceptionIfTimeZoneIsMissing() {
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2021-11-01T05:06:20Z", DateTimeFormatter.ISO_INSTANT);
}
Itβs also worth mentioning that parsing requires specifying at least the seconds field. Otherwise, DateTimeParseException will be thrown.
4.6. Locale Specific Patterns
Java 19 introduces a new method ofLocalizedPattern(String), whose name might be misleading, so it worth to mention it here. The methodβs purpose isnβt to provide localized formatting as we saw in the previous examples with ofPattern(String) combined with locale.
The main goal of this method is to provide the best matching for a provided pattern given a locale. The best description of its intention can be found in the Unicode LDML specification. They use the Japanese calendar as an example. In most cases, the year should be associated with the era. While using the Japanese locale, all the patterns that include years will match with the most fitting pattern for a given locale and, thus, contain the era.
Another non-intuitive thing about this method is that it would throw an exception if it cannot match a specified pattern, which might happen often. The code might produce errors even if the patterns are valid and would work with the ofPattern method.
5. Conclusion
In this article, we discussed how to use the DateTimeFormatter class to format dates and times. We also examined real-life example patterns that often arise when we work with date-time instances.
