Perils of Java Date Formats

Java has a rather sordid history with dates. In the beginning, Date was suppose to be the way to manage dates, which sounded rather logical. You could construct a Date from various component values (month, day, year and time), and internally it was measured as the number of milliseconds from epoch.

Once JDK 1.1 came out, the advent of Calendar brought some new capabilities and deprecated many of the rudimentary functions present in Date. From a Calendar, it was possible to add and subtract specific components in a date; for example, you could add 90 days and determine what the new Date will be. With JDK 1.8 the introduction of a revamped date and time API brings this in line with JSR 310 and further enhances the handling of dates and times.

Formatting dates is the process of converting strings to dates and vice versa. For example, if a user inputs “01/01/2015” you may use something like SimpleDateFormat to convert this to a real Date:

SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
Date dt = df.parse("01/01/2015");

The resulting date:

Thu Jan 01 00:00:00 EST 2015

What about something like this:

SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
Date dt = df.parse("13/49/2015");

Would the result be some kind of error? After all, there are only 12 months in the year and no month has more than 31 days. Unfortunately, the result is the following date:

Thu Feb 18 00:00:00 EST 2016

It simply applies the extra month and days to the date and brings it into February of the following year. This is the idea behind lenient date parsing, which you can disable:

SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
df.setLenient(false);
Date dt = dateFormatInput.parse("13/49/2015");

sun-stone

The result here is now an exception:

Exception in thread "main" java.text.ParseException: Unparseable date: "13/49/2015"

What about the new date and time API, does it improve on this at all?

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate ld = LocalDate.parse("03/08/2015", dtf);

There are a number of benefits here, LocalDate is a date-only entity without a time component. This makes it possible to represent dates without respect to time. Additionally, formatting an invalid date:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate ld = LocalDate.parse("13/49/2015", dtf);

This results in an exception that clearly identifies why the parse has failed:

Exception in thread "main" java.time.format.DateTimeParseException: Text '13/49/2015' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 13

This is unambiguous and does not require lenient to be applied to the formatter. Of course the new date time API’s for Java provide mechanisms to parse dates with time components:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss");
LocalDateTime ld = LocalDateTime.parse("03/08/2015 02:00:00", dtf);

Running this produces:

2015-03-08T02:00

This is not totally entirely correct in the context of daylight savings time, for EDT (America/New York) the 2AM hour was skipped on 3/8/15. Fortunately, the Java date time API offers a ZonedDateTime which can take this into consideration:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss").withZone(ZoneId.systemDefault());
LocalDateTime ld = LocalDateTime.parse("03/08/2015 02:00:00", dtf);
ZonedDateTime zd = ZonedDateTime.ofLocal(ld, ZoneId.systemDefault(), ZoneOffset.UTC);

The result zoned date time is correctly reported:

2015-03-08T03:00:00-04:00[America/New_York]

There are plenty of other reasons to start leveraging the new Java date time API, in addition to feature parity with the existing Java Date and DateFormat API’s, the new date time API provides methods to adjust dates just like the Calendar has been able to do. For example:

LocalDate ld = LocalDate.now().plusDays(2);

This creates a LocalDate for today and adds 2 days to it using a builder pattern. By comparison, the Calendar operation would appear like so:

Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, 2);

Same operation of adding two days to the current date; however, it takes two lines of code and requires you to select a field from the somewhat cryptic set of possible fields on the Calendar. For example, do you think you can add Calendar.FEBRUARY to the calendar? Probably not, but it certainly won’t complain when you do it.

If you are still supporting a JDK earlier than 1.8, the Joda library is functionally equivalent and would be a great way to get started.