Thursday, September 11, 2014

Date/Time Formatting/Parsing, Java 8 Style

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.

Characteristic DateFormat/SimpleDateFormat DateTimeFormatter
Purpose "formats and parses dates or time in a language-independent manner" "Formatter for printing and parsing date-time objects."
Primarily Used With java.util.Date
java.util.Calendar
java.time.LocalDate
java.time.LocalTime
java.time.LocalDateTime
java.time.OffsetTime
java.time.OffsetDateTime
java.time.ZonedDateTime
java.time.Instant
Thread Safety "Date formats are not synchronized." "This class is immutable and thread-safe."
Direct Formatting format(Date) format(TemporalAccessor)
Direct Parsing parse(String) parse(CharSequence, TemporalQuery)
Indirect Formatting None [unless you use Groovy's Date.format(String) extension)] LocalDate.format(DateTimeFormatter)
LocalTime.format(DateTimeFormatter)
LocalDateTime.format(DateTimeFormatter)
OffsetTime.format(DateTimeFormatter)
OffsetDateTime.format(DateTimeFormatter)
ZonedDateTime.format(DateTimeFormatter)
Indirect Parsing None [unless you use deprecated Date.parse(String) or Groovy's Date.parse(String, String) extension] LocalDate.parse(CharSequence, DateTimeFormatter)
LocalTime.parse(CharSequence, DateTimeFormatter)
LocalDateTime.parse(CharSequence, DateTimeFormatter)
OffsetTime.parse(CharSequence, DateTimeFormatter)
OffsetDateTime.parse(CharSequence, DateTimeFormatter)
ZonedDateTime.parse(CharSequence, DateTimeFormatter)
Internationalization java.util.Locale java.util.Locale
Time Zone java.util.TimeZone java.time.ZoneId
java.time.ZoneOffset
Predefined Formatters None, but does offer static convenience methods for common instances:
getDateInstance()
getDateInstance(int)
getDateInstance(int, Locale)
getDateTimeInstance()
getDateTimeInstance(int, int)
getDateTimeInstance(int, int, Locale)
getInstance()
getTimeInstance()
getTimeInstance(int)
getTimeInstance(int, Locale)
ISO_LOCAL_DATE
ISO_LOCAL_TIME
ISO_LOCAL_DATE_TIME
ISO_OFFSET_DATE
ISO_OFFSET_TIME
ISO_OFFSET_DATE_TIME
ISO_ZONED_DATE_TIME
BASIC_ISO_DATE
ISO_DATE
ISO_DATE_TIME
ISO_ORDINAL_DATE
ISO_INSTANTISO_WEEK_DATE
RFC_1123_DATE_TIME

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.

  1. /** Pattern to use for String representation of Dates/Times. */  
  2. private final String dateTimeFormatPattern = "yyyy/MM/dd HH:mm:ss z";  
  3.   
  4. /** 
  5.  * java.util.Date instance representing now that can 
  6.  * be formatted using SimpleDateFormat based on my 
  7.  * dateTimeFormatPattern field. 
  8.  */  
  9. private final Date now = new Date();  
  10.   
  11. /** 
  12.  * java.time.ZonedDateTime instance representing now that can 
  13.  * be formatted using DateTimeFormatter based on my 
  14.  * dateTimeFormatPattern field. 
  15.  * 
  16.  * Note that ZonedDateTime needed to be used in this example 
  17.  * instead of java.time.LocalDateTime or java.time.OffsetDateTime 
  18.  * because there is zone information in the format provided by 
  19.  * my dateTimeFormatPattern field and attempting to have 
  20.  * DateTimeFormatter.format(TemporalAccessor) instantiated 
  21.  * with a format pattern that includes time zone details 
  22.  * will lead to DateTimeException for instances of 
  23.  * TemporalAccessor that do not have time zone information 
  24.  * (such as LocalDateTime and OffsetDateTime). 
  25.  */  
  26. private final ZonedDateTime now8 = ZonedDateTime.now();  
  27.   
  28.   
  29. /** 
  30.  * String that can be used by both SimpleDateFormat and 
  31.  * DateTimeFormatter to parse respective date/time instances 
  32.  * from this String. 
  33.  */  
  34. 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
  1. /** 
  2.  * Demonstrate presenting java.util.Date as String matching 
  3.  * provided pattern via use of SimpleDateFormat. 
  4.  */  
  5. public void demonstrateSimpleDateFormatFormatting()  
  6. {  
  7.    final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);  
  8.    final String nowString = format.format(now);  
  9.    out.println(  
  10.         "Date '" + now + "' formatted with SimpleDateFormat and '"  
  11.       + dateTimeFormatPattern + "': " + nowString);  
  12. }  
  13.   
  14. /** 
  15.  * Demonstrate parsing a java.util.Date from a String 
  16.  * via SimpleDateFormat. 
  17.  */  
  18. public void demonstrateSimpleDateFormatParsing()  
  19. {  
  20.    final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);  
  21.    try  
  22.    {  
  23.       final Date parsedDate = format.parse(dateTimeString);  
  24.       out.println("'" + dateTimeString + "' is parsed with SimpleDateFormat as " + parsedDate);  
  25.    }  
  26.    // DateFormat.parse(String) throws a checked exception  
  27.    catch (ParseException parseException)  
  28.    {  
  29.       out.println(  
  30.            "ERROR: Unable to parse date/time String '"  
  31.          + dateTimeString + "' with pattern '"  
  32.          + dateTimeFormatPattern + "'.");  
  33.    }  
  34. }  

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.

Formatting ZonedDateTime as String
  1. /** 
  2.  * Demonstrate presenting ZonedDateTime as a String matching 
  3.  * provided pattern via DateTimeFormatter and its 
  4.  * ofPattern(String) method. 
  5.  */  
  6. public void demonstrateDateTimeFormatFormatting()  
  7. {  
  8.    final DateTimeFormatter formatter =  
  9.       DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  10.    final String nowString = formatter.format(now8);  
  11.    out.println(  
  12.         now8 + " formatted with DateTimeFormatter and '"  
  13.       + dateTimeFormatPattern + "': " + nowString);  
  14. }  

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.

Parsing ZonedDateTime from String Using Static ZonedDateTime.parse Method
  1. /** 
  2.  * Demonstrate parsing ZonedDateTime from provided String 
  3.  * via ZonedDateTime's parse(String, DateTimeFormatter) method. 
  4.  */  
  5. public void demonstrateDateTimeFormatParsingTemporalStaticMethod()  
  6. {  
  7.    final DateTimeFormatter formatter =  
  8.       DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  9.    final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, formatter);  
  10.    out.println(  
  11.         "'" + dateTimeString  
  12.       + "' is parsed with DateTimeFormatter and ZonedDateTime.parse as "  
  13.       + zonedDateTime);  
  14. }  

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).

Parsing ZonedDateTime from String Using DateTimeFormatter.parse Method
  1. /** 
  2.  * Demonstrate parsing ZonedDateTime from String 
  3.  * via DateTimeFormatter.parse(String, TemporaryQuery) 
  4.  * with the Temple Query in this case being ZonedDateTime's 
  5.  * from(TemporalAccessor) used as a Java 8 method reference. 
  6.  */  
  7. public void demonstrateDateTimeFormatParsingMethodReference()  
  8. {  
  9.    final DateTimeFormatter formatter =  
  10.       DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  11.    final ZonedDateTime zonedDateTime = formatter.parse(dateTimeString, ZonedDateTime::from);  
  12.    out.println(  
  13.         "'" + dateTimeString  
  14.       + "' is parsed with DateTimeFormatter and ZoneDateTime::from as "  
  15.       + zonedDateTime);  
  16. }  

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.

Accessing Pre-JDK 8 Format from JDK 8's DateTimeFormatter
  1. /** 
  2.  * Demonstrate formatting ZonedDateTime via DateTimeFormatter, 
  3.  * but using implementation of Format. 
  4.  */  
  5. public void demonstrateDateTimeFormatAndFormatFormatting()  
  6. {  
  7.    final DateTimeFormatter formatter =  
  8.       DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  9.    final Format format = formatter.toFormat();  
  10.    final String nowString = format.format(now8);  
  11.    out.println(  
  12.         "ZonedDateTime " + now + " formatted with DateTimeFormatter/Format (and "  
  13.       + format.getClass().getCanonicalName() + ") and '"  
  14.       + dateTimeFormatPattern + "': " + nowString);  
  15. }  

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.

Formatting and Parsing Instances of Instant
  1. /** 
  2.  * Demonstrate formatting and parsing an instance of Instant. 
  3.  */  
  4. public void demonstrateDateTimeFormatFormattingAndParsingInstant()  
  5. {  
  6.    // Instant instances don't have timezone information  
  7.    final Instant instant = now.toInstant();  
  8.    final DateTimeFormatter formatter =  
  9.       DateTimeFormatter.ofPattern(  
  10.          dateTimeFormatPattern).withZone(ZoneId.systemDefault());  
  11.    final String formattedInstance = formatter.format(instant);  
  12.    out.println(  
  13.         "Instant " + instant + " formatted with DateTimeFormatter and '"  
  14.       + dateTimeFormatPattern + "' to '" + formattedInstance + "'");  
  15.    final Instant instant2 =  
  16.       formatter.parse(formattedInstance, ZonedDateTime::from).toInstant();  
  17.       out.println(formattedInstance + " parsed back to " + instant2);  
  18. }  

All of the above examples come from the sample class shown in the next code listing for completeness.

DateFormatDemo.java
  1. package dustin.examples.numberformatdemo;  
  2.   
  3. import static java.lang.System.out;  
  4.   
  5. import java.text.DateFormat;  
  6. import java.text.Format;  
  7. import java.text.ParseException;  
  8. import java.text.SimpleDateFormat;  
  9. import java.time.Instant;  
  10. import java.time.ZoneId;  
  11. import java.time.ZonedDateTime;  
  12. import java.time.format.DateTimeFormatter;  
  13. import java.util.Date;  
  14.   
  15. /** 
  16.  * Demonstrates formatting dates as strings and parsing strings 
  17.  * into dates and times using pre-Java 8 (java.text.SimpleDateFormat) 
  18.  * and Java 8 (java.time.format.DateTimeFormatter) mechanisms. 
  19.  */  
  20. public class DateFormatDemo  
  21. {  
  22.    /** Pattern to use for String representation of Dates/Times. */  
  23.    private final String dateTimeFormatPattern = "yyyy/MM/dd HH:mm:ss z";  
  24.   
  25.    /** 
  26.     * java.util.Date instance representing now that can 
  27.     * be formatted using SimpleDateFormat based on my 
  28.     * dateTimeFormatPattern field. 
  29.     */  
  30.    private final Date now = new Date();  
  31.   
  32.    /** 
  33.     * java.time.ZonedDateTime instance representing now that can 
  34.     * be formatted using DateTimeFormatter based on my 
  35.     * dateTimeFormatPattern field. 
  36.     * 
  37.     * Note that ZonedDateTime needed to be used in this example 
  38.     * instead of java.time.LocalDateTime or java.time.OffsetDateTime 
  39.     * because there is zone information in the format provided by 
  40.     * my dateTimeFormatPattern field and attempting to have 
  41.     * DateTimeFormatter.format(TemporalAccessor) instantiated 
  42.     * with a format pattern that includes time zone details 
  43.     * will lead to DateTimeException for instances of 
  44.     * TemporalAccessor that do not have time zone information 
  45.     * (such as LocalDateTime and OffsetDateTime). 
  46.     */  
  47.    private final ZonedDateTime now8 = ZonedDateTime.now();  
  48.   
  49.   
  50.    /** 
  51.     * String that can be used by both SimpleDateFormat and 
  52.     * DateTimeFormatter to parse respective date/time instances 
  53.     * from this String. 
  54.     */  
  55.    private final String dateTimeString = "2014/09/03 13:59:50 MDT";  
  56.   
  57.    /** 
  58.     * Demonstrate presenting java.util.Date as String matching 
  59.     * provided pattern via use of SimpleDateFormat. 
  60.     */  
  61.    public void demonstrateSimpleDateFormatFormatting()  
  62.    {  
  63.       final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);  
  64.       final String nowString = format.format(now);  
  65.       out.println(  
  66.            "Date '" + now + "' formatted with SimpleDateFormat and '"  
  67.          + dateTimeFormatPattern + "': " + nowString);  
  68.    }  
  69.   
  70.    /** 
  71.     * Demonstrate parsing a java.util.Date from a String 
  72.     * via SimpleDateFormat. 
  73.     */  
  74.    public void demonstrateSimpleDateFormatParsing()  
  75.    {  
  76.       final DateFormat format = new SimpleDateFormat(dateTimeFormatPattern);  
  77.       try  
  78.       {  
  79.          final Date parsedDate = format.parse(dateTimeString);  
  80.          out.println("'" + dateTimeString + "' is parsed with SimpleDateFormat as " + parsedDate);  
  81.       }  
  82.       // DateFormat.parse(String) throws a checked exception  
  83.       catch (ParseException parseException)  
  84.       {  
  85.          out.println(  
  86.               "ERROR: Unable to parse date/time String '"  
  87.             + dateTimeString + "' with pattern '"  
  88.             + dateTimeFormatPattern + "'.");  
  89.       }  
  90.    }  
  91.   
  92.    /** 
  93.     * Demonstrate presenting ZonedDateTime as a String matching 
  94.     * provided pattern via DateTimeFormatter and its 
  95.     * ofPattern(String) method. 
  96.     */  
  97.    public void demonstrateDateTimeFormatFormatting()  
  98.    {  
  99.       final DateTimeFormatter formatter =  
  100.          DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  101.       final String nowString = formatter.format(now8);  
  102.       out.println(  
  103.            now8 + " formatted with DateTimeFormatter and '"  
  104.          + dateTimeFormatPattern + "': " + nowString);  
  105.    }  
  106.   
  107.    /** 
  108.     * Demonstrate parsing ZonedDateTime from provided String 
  109.     * via ZonedDateTime's parse(String, DateTimeFormatter) method. 
  110.     */  
  111.    public void demonstrateDateTimeFormatParsingTemporalStaticMethod()  
  112.    {  
  113.       final DateTimeFormatter formatter =  
  114.          DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  115.       final ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, formatter);  
  116.       out.println(  
  117.            "'" + dateTimeString  
  118.          + "' is parsed with DateTimeFormatter and ZonedDateTime.parse as "  
  119.          + zonedDateTime);  
  120.    }  
  121.   
  122.    /** 
  123.     * Demonstrate parsing ZonedDateTime from String 
  124.     * via DateTimeFormatter.parse(String, TemporaryQuery) 
  125.     * with the Temple Query in this case being ZonedDateTime's 
  126.     * from(TemporalAccessor) used as a Java 8 method reference. 
  127.     */  
  128.    public void demonstrateDateTimeFormatParsingMethodReference()  
  129.    {  
  130.       final DateTimeFormatter formatter =  
  131.          DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  132.       final ZonedDateTime zonedDateTime = formatter.parse(dateTimeString, ZonedDateTime::from);  
  133.       out.println(  
  134.            "'" + dateTimeString  
  135.          + "' is parsed with DateTimeFormatter and ZoneDateTime::from as "  
  136.          + zonedDateTime);  
  137.    }  
  138.   
  139.    /** 
  140.     * Demonstrate formatting ZonedDateTime via DateTimeFormatter, 
  141.     * but using implementation of Format. 
  142.     */  
  143.    public void demonstrateDateTimeFormatAndFormatFormatting()  
  144.    {  
  145.       final DateTimeFormatter formatter =  
  146.          DateTimeFormatter.ofPattern(dateTimeFormatPattern);  
  147.       final Format format = formatter.toFormat();  
  148.       final String nowString = format.format(now8);  
  149.       out.println(  
  150.            "ZonedDateTime " + now + " formatted with DateTimeFormatter/Format (and "  
  151.          + format.getClass().getCanonicalName() + ") and '"  
  152.          + dateTimeFormatPattern + "': " + nowString);  
  153.    }  
  154.   
  155.    /** 
  156.     * Demonstrate formatting and parsing an instance of Instant. 
  157.     */  
  158.    public void demonstrateDateTimeFormatFormattingAndParsingInstant()  
  159.    {  
  160.       // Instant instances don't have timezone information  
  161.       final Instant instant = now.toInstant();  
  162.       final DateTimeFormatter formatter =  
  163.          DateTimeFormatter.ofPattern(  
  164.             dateTimeFormatPattern).withZone(ZoneId.systemDefault());  
  165.       final String formattedInstance = formatter.format(instant);  
  166.       out.println(  
  167.            "Instant " + instant + " formatted with DateTimeFormatter and '"  
  168.          + dateTimeFormatPattern + "' to '" + formattedInstance + "'");  
  169.       final Instant instant2 =  
  170.          formatter.parse(formattedInstance, ZonedDateTime::from).toInstant();  
  171.       out.println(formattedInstance + " parsed back to " + instant2);  
  172.    }  
  173.   
  174.    /** 
  175.     * Demonstrate java.text.SimpleDateFormat and 
  176.     * java.time.format.DateTimeFormatter. 
  177.     * 
  178.     * @param arguments Command-line arguments; none anticipated. 
  179.     */  
  180.    public static void main(final String[] arguments)  
  181.    {  
  182.       final DateFormatDemo demo = new DateFormatDemo();  
  183.       out.print("\n1: ");  
  184.       demo.demonstrateSimpleDateFormatFormatting();  
  185.       out.print("\n2: ");  
  186.       demo.demonstrateSimpleDateFormatParsing();  
  187.       out.print("\n3: ");  
  188.       demo.demonstrateDateTimeFormatFormatting();  
  189.       out.print("\n4: ");  
  190.       demo.demonstrateDateTimeFormatParsingTemporalStaticMethod();  
  191.       out.print("\n5: ");  
  192.       demo.demonstrateDateTimeFormatParsingMethodReference();  
  193.       out.print("\n6: ");  
  194.       demo.demonstrateDateTimeFormatAndFormatFormatting();  
  195.       out.print("\n7: ");  
  196.       demo.demonstrateDateTimeFormatFormattingAndParsingInstant();  
  197.    }  
  198. }  

The output from running the above demonstration is shown in the next screen snapshot.

Conclusion

The 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:

Jasvant Singh said...

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