Since nearly the beginning of Java, Java developers have worked with dates and times via the java.util.Date class (since JDK 1.0) and then the java.util.Calendar class (since JDK 1.1). During this time, hundreds of thousands (or maybe millions) of Java developers have formatted and parsed Java dates and times using java.text.DateFormat and java.text.SimpleDateFormat. Given how frequently this has been done over the years, it's no surprise that there are numerous online examples of and tutorials on parsing and formatting dates and times with these classes. The classic Java Tutorials cover these java.util and java.text classes in the Formatting lesson (Dates and Times). The new Date Time trail in the Java Tutorials covers Java 8's new classes for dates and times and their formatting and parsing. This post provides examples of these in action.
Before demonstrating Java 8 style date/time parsing/formatting with examples, it is illustrative to compare the Javadoc descriptions for DateFormat/SimpleDateFormat and DateTimeFormatter. The table that follows contains differentiating information that can be gleaned directly or indirectly from a comparison of the Javadoc for each formatting class. Perhaps the most important observations to make from this table are that the new DateTimeFormatter
is threadsafe and immutable and the general overview of the APIs that DateTimeFormatter
provides for parsing and formatting dates and times.
The remainder of this post uses examples to demonstrate formatting and parsing dates in Java 8 with the java.time constructs. The examples will use the following string patterns and instances.
/** Pattern to use for String representation of Dates/Times. */ private final String dateTimeFormatPattern = "yyyy/MM/dd HH:mm:ss z"; /** * java.util.Date instance representing now that can * be formatted using SimpleDateFormat based on my * dateTimeFormatPattern field. */ private final Date now = new Date(); /** * java.time.ZonedDateTime instance representing now that can * be formatted using DateTimeFormatter based on my * dateTimeFormatPattern field. * * Note that ZonedDateTime needed to be used in this example * instead of java.time.LocalDateTime or java.time.OffsetDateTime * because there is zone information in the format provided by * my dateTimeFormatPattern field and attempting to have * DateTimeFormatter.format(TemporalAccessor) instantiated * with a format pattern that includes time zone details * will lead to DateTimeException for instances of * TemporalAccessor that do not have time zone information * (such as LocalDateTime and OffsetDateTime). */ private final ZonedDateTime now8 = ZonedDateTime.now(); /** * String that can be used by both SimpleDateFormat and * DateTimeFormatter to parse respective date/time instances * from this String. */ private final String dateTimeString = "2014/09/03 13:59:50 MDT";
Before Java 8, the standard Java approach for dates and times was via the Date and Calendar classes and the standard approach to parsing and formatting dates was via DateFormat and SimpleDateFormat. The next code listing demonstrates these classical approaches.
Formatting and Parsing Java Dates with SimpleDateFormat/** * Demonstrate presenting java.util.Date as String matching * provided pattern via use of SimpleDateFormat. */ public void demonstrateSimpleDateFormatFormatting() { final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern); final String nowString = format.format(now); out.println( "Date '" + now + "' formatted with SimpleDateFormat and '" + dateTimeFormatPattern + "': " + nowString); } /** * Demonstrate parsing a java.util.Date from a String * via SimpleDateFormat. */ public void demonstrateSimpleDateFormatParsing() { final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern); try { final Date parsedDate = format.parse(dateTimeString); out.println("'" + dateTimeString + "' is parsed with SimpleDateFormat as " + parsedDate); } // DateFormat.parse(String) throws a checked exception catch (ParseException parseException) { out.println( "ERROR: Unable to parse date/time String '" + dateTimeString + "' with pattern '" + dateTimeFormatPattern + "'."); } }
With Java 8, the preferred date/time classes are no longer in the java.util package and the preferred date/time handling classes are now in the java.time package. Similarly, the preferred date/time formatting/parsing classes are no longer in the java.text package, but instead come from the java.time.format package.
The java.time
package offers numerous classes for modeling dates and/or times. These include classes that model dates only (no time information), classes that model times only (no date information), classes that model date and time information, classes that use timezone information, and classes that do not incorporate time zone information. The approach for formatting and parsing these is generally similar, though the characteristics of the class (whether it supports date or time or timezone information, for example) affects which patterns that can be applied. In this post, I use the ZonedDateTime class for my examples. The reason for this choice is that it includes date, time, and time zone information and allows me to use a matching pattern that involves all three of those characteristics like a Date or Calendar instance does. This makes it easier to compare the old and new approaches.
The DateTimeFormatter class provides ofPattern methods to provide an instance of DateTimeFormatter
based on the provided date/time pattern String. One of the format methods can then be called on that instance of DateTimeFormatter
to get the date and/or time information formatted as a String matching the provided pattern. The next code listing illustrates this approach to formatting a String
from a ZonedDateTime
based on the provided pattern.
/** * Demonstrate presenting ZonedDateTime as a String matching * provided pattern via DateTimeFormatter and its * ofPattern(String) method. */ public void demonstrateDateTimeFormatFormatting() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final String nowString = formatter.format(now8); out.println( now8 + " formatted with DateTimeFormatter and '" + dateTimeFormatPattern + "': " + nowString); }
Parsing a date/time class from a String based on a pattern is easily accomplished. There are a couple ways this can be accomplished. One approach is to pass the instance of DateTimeFormatter
to the static ZonedDateTime.parse(CharSequence, DateTimeFormatter) method, which returns an instance of ZonedDateTime
derived from the provided character sequence and based on the provided pattern. This is illustrated in the next code listing.
/** * Demonstrate parsing ZonedDateTime from provided String * via ZonedDateTime's parse(String, DateTimeFormatter) method. */ public void demonstrateDateTimeFormatParsingTemporalStaticMethod() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, formatter); out.println( "'" + dateTimeString + "' is parsed with DateTimeFormatter and ZonedDateTime.parse as " + zonedDateTime); }
A second approach to parsing ZonedDateTime
from a String is via DateTimeFormatter
's parse(CharSequence, TemporalQuery<T>) method. This is illustrated in the next code listing which also provides an opportunity to demonstrate use of a Java 8 method reference (see ZonedDateTime::from
).
/** * Demonstrate parsing ZonedDateTime from String * via DateTimeFormatter.parse(String, TemporaryQuery) * with the Temple Query in this case being ZonedDateTime's * from(TemporalAccessor) used as a Java 8 method reference. */ public void demonstrateDateTimeFormatParsingMethodReference() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final ZonedDateTime zonedDateTime = formatter.parse(dateTimeString, ZonedDateTime::from); out.println( "'" + dateTimeString + "' is parsed with DateTimeFormatter and ZoneDateTime::from as " + zonedDateTime); }
Very few projects have the luxury of being a greenfield project that can start with Java 8. Therefore, it's helpful that there are classes that connect the pre-JDK 8 date/time classes with the new date/time classes introduced in JDK 8. One example of this is the ability of JDK 8's DateTimeFormatter
to provide a descending instance of the pre-JDK 8 abstract Format class via the DateTimeFormatter.toFormat() method. This is demonstrated in the next code listing.
/** * Demonstrate formatting ZonedDateTime via DateTimeFormatter, * but using implementation of Format. */ public void demonstrateDateTimeFormatAndFormatFormatting() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final Format format = formatter.toFormat(); final String nowString = format.format(now8); out.println( "ZonedDateTime " + now + " formatted with DateTimeFormatter/Format (and " + format.getClass().getCanonicalName() + ") and '" + dateTimeFormatPattern + "': " + nowString); }
The Instant class is especially important when working with both pre-JDK 8 Date
and Calendar
classes in conjunction with the new date and time classes introduced with JDK 8. The reason Instant
is so important is that java.util.Date
has methods from(Instant) and toInstant() for getting a Date
from an Instant
and getting an Instant
from a Date
respectively. Because Instant
is so important in migrating pre-Java 8 date/time handling to Java 8 baselines, the next code listing demonstrates formatting and parsing instances of Instant
.
/** * Demonstrate formatting and parsing an instance of Instant. */ public void demonstrateDateTimeFormatFormattingAndParsingInstant() { // Instant instances don't have timezone information final Instant instant = now.toInstant(); final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( dateTimeFormatPattern).withZone(ZoneId.systemDefault()); final String formattedInstance = formatter.format(instant); out.println( "Instant " + instant + " formatted with DateTimeFormatter and '" + dateTimeFormatPattern + "' to '" + formattedInstance + "'"); final Instant instant2 = formatter.parse(formattedInstance, ZonedDateTime::from).toInstant(); out.println(formattedInstance + " parsed back to " + instant2); }
All of the above examples come from the sample class shown in the next code listing for completeness.
DateFormatDemo.javapackage dustin.examples.numberformatdemo; import static java.lang.System.out; import java.text.DateFormat; import java.text.Format; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; /** * Demonstrates formatting dates as strings and parsing strings * into dates and times using pre-Java 8 (java.text.SimpleDateFormat) * and Java 8 (java.time.format.DateTimeFormatter) mechanisms. */ public class DateFormatDemo { /** Pattern to use for String representation of Dates/Times. */ private final String dateTimeFormatPattern = "yyyy/MM/dd HH:mm:ss z"; /** * java.util.Date instance representing now that can * be formatted using SimpleDateFormat based on my * dateTimeFormatPattern field. */ private final Date now = new Date(); /** * java.time.ZonedDateTime instance representing now that can * be formatted using DateTimeFormatter based on my * dateTimeFormatPattern field. * * Note that ZonedDateTime needed to be used in this example * instead of java.time.LocalDateTime or java.time.OffsetDateTime * because there is zone information in the format provided by * my dateTimeFormatPattern field and attempting to have * DateTimeFormatter.format(TemporalAccessor) instantiated * with a format pattern that includes time zone details * will lead to DateTimeException for instances of * TemporalAccessor that do not have time zone information * (such as LocalDateTime and OffsetDateTime). */ private final ZonedDateTime now8 = ZonedDateTime.now(); /** * String that can be used by both SimpleDateFormat and * DateTimeFormatter to parse respective date/time instances * from this String. */ private final String dateTimeString = "2014/09/03 13:59:50 MDT"; /** * Demonstrate presenting java.util.Date as String matching * provided pattern via use of SimpleDateFormat. */ public void demonstrateSimpleDateFormatFormatting() { final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern); final String nowString = format.format(now); out.println( "Date '" + now + "' formatted with SimpleDateFormat and '" + dateTimeFormatPattern + "': " + nowString); } /** * Demonstrate parsing a java.util.Date from a String * via SimpleDateFormat. */ public void demonstrateSimpleDateFormatParsing() { final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern); try { final Date parsedDate = format.parse(dateTimeString); out.println("'" + dateTimeString + "' is parsed with SimpleDateFormat as " + parsedDate); } // DateFormat.parse(String) throws a checked exception catch (ParseException parseException) { out.println( "ERROR: Unable to parse date/time String '" + dateTimeString + "' with pattern '" + dateTimeFormatPattern + "'."); } } /** * Demonstrate presenting ZonedDateTime as a String matching * provided pattern via DateTimeFormatter and its * ofPattern(String) method. */ public void demonstrateDateTimeFormatFormatting() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final String nowString = formatter.format(now8); out.println( now8 + " formatted with DateTimeFormatter and '" + dateTimeFormatPattern + "': " + nowString); } /** * Demonstrate parsing ZonedDateTime from provided String * via ZonedDateTime's parse(String, DateTimeFormatter) method. */ public void demonstrateDateTimeFormatParsingTemporalStaticMethod() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, formatter); out.println( "'" + dateTimeString + "' is parsed with DateTimeFormatter and ZonedDateTime.parse as " + zonedDateTime); } /** * Demonstrate parsing ZonedDateTime from String * via DateTimeFormatter.parse(String, TemporaryQuery) * with the Temple Query in this case being ZonedDateTime's * from(TemporalAccessor) used as a Java 8 method reference. */ public void demonstrateDateTimeFormatParsingMethodReference() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final ZonedDateTime zonedDateTime = formatter.parse(dateTimeString, ZonedDateTime::from); out.println( "'" + dateTimeString + "' is parsed with DateTimeFormatter and ZoneDateTime::from as " + zonedDateTime); } /** * Demonstrate formatting ZonedDateTime via DateTimeFormatter, * but using implementation of Format. */ public void demonstrateDateTimeFormatAndFormatFormatting() { final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateTimeFormatPattern); final Format format = formatter.toFormat(); final String nowString = format.format(now8); out.println( "ZonedDateTime " + now + " formatted with DateTimeFormatter/Format (and " + format.getClass().getCanonicalName() + ") and '" + dateTimeFormatPattern + "': " + nowString); } /** * Demonstrate formatting and parsing an instance of Instant. */ public void demonstrateDateTimeFormatFormattingAndParsingInstant() { // Instant instances don't have timezone information final Instant instant = now.toInstant(); final DateTimeFormatter formatter = DateTimeFormatter.ofPattern( dateTimeFormatPattern).withZone(ZoneId.systemDefault()); final String formattedInstance = formatter.format(instant); out.println( "Instant " + instant + " formatted with DateTimeFormatter and '" + dateTimeFormatPattern + "' to '" + formattedInstance + "'"); final Instant instant2 = formatter.parse(formattedInstance, ZonedDateTime::from).toInstant(); out.println(formattedInstance + " parsed back to " + instant2); } /** * Demonstrate java.text.SimpleDateFormat and * java.time.format.DateTimeFormatter. * * @param arguments Command-line arguments; none anticipated. */ public static void main(final String[] arguments) { final DateFormatDemo demo = new DateFormatDemo(); out.print("\n1: "); demo.demonstrateSimpleDateFormatFormatting(); out.print("\n2: "); demo.demonstrateSimpleDateFormatParsing(); out.print("\n3: "); demo.demonstrateDateTimeFormatFormatting(); out.print("\n4: "); demo.demonstrateDateTimeFormatParsingTemporalStaticMethod(); out.print("\n5: "); demo.demonstrateDateTimeFormatParsingMethodReference(); out.print("\n6: "); demo.demonstrateDateTimeFormatAndFormatFormatting(); out.print("\n7: "); demo.demonstrateDateTimeFormatFormattingAndParsingInstant(); } }
The output from running the above demonstration is shown in the next screen snapshot.
ConclusionThe JDK 8 date/time classes and related formatting and parsing classes are much more straightforward to use than their pre-JDK 8 counterparts. This post has attempted to demonstrate how to apply these new classes and to take advantage of some of their benefits.
1 comment:
I ran the sample code on my windows machine. Formatted and parsed Instant are different in 7th case:
1: Date 'Sun Jul 01 14:54:32 IST 2018' formatted with SimpleDateFormat and 'yyyy/MM/dd HH:mm:ss z': 2018/07/01 14:54:32 IST
2: '2014/09/03 13:59:50 MDT' is parsed with SimpleDateFormat as Thu Sep 04 01:29:50 IST 2014
3: 2018-07-01T14:54:32.963832300+05:30[Asia/Calcutta] formatted with DateTimeFormatter and 'yyyy/MM/dd HH:mm:ss z': 2018/07/01 14:54:32 IST
4: '2014/09/03 13:59:50 MDT' is parsed with DateTimeFormatter and ZonedDateTime.parse as 2014-09-03T13:59:50-06:00[America/Denver]
5: '2014/09/03 13:59:50 MDT' is parsed with DateTimeFormatter and ZoneDateTime::from as 2014-09-03T13:59:50-06:00[America/Denver]
6: ZonedDateTime Sun Jul 01 14:54:32 IST 2018 formatted with DateTimeFormatter/Format (and java.time.format.DateTimeFormatter.ClassicFormat) and 'yyyy/MM/dd HH:mm:ss z': 2018/07/01 14:54:32 IST
7: Instant 2018-07-01T09:24:32.924Z formatted with DateTimeFormatter and 'yyyy/MM/dd HH:mm:ss z' to '2018/07/01 14:54:32 IST' 2018/07/01 14:54:32 IST parsed back to 2018-07-01T14:54:32Z
Post a Comment