Parsing dates in multiple formats in Joda-Time

Joda-Time is without a doubt the best way to work with dates and times in Java, unless you happen to be working exclusively on Java 8+, where you can use the new java.time library, also known as JSR 310.

With Joda you can easily parse dates, times and timestamps using DateTimeFormatter. For example you can use:

DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
LocalDate date = formatter.parseLocalDate("2015-12-25");
assertEquals(2015, date.getYear());
assertEquals(12, date.getMonthOfYear());
assertEquals(25, date.getDayOfMonth());

However, the library doesn’t have a method such as “tryParse”: it only has various versions of parseLocalDate, parseLocalTime, parseDateTime which all throw IllegalArgumentException if the given string doesn’t match the format.

So, what if you need to parse a date in one of two possible formats? You can obviously do something like this:

private static final List<DateTimeFormatter> FORMATTERS =
    Arrays.asList(
        DateTimeFormat.forPattern("dd/MM/yyyy"),
        DateTimeFormat.forPattern("yyyy-MM-dd"));

public static LocalDate parseDate(String inputString)
        throws IllegalArgumentException {
    for (DateTimeFormatter formatter : FORMATTERS) {
        try {
            return formatter.parseLocalDate(inputString);
        } catch (IllegalArgumentException e) {
            // Go on to the next format
        }
    }
    throw new IllegalArgumentException("Unsupported date format: " + inputString);
}

@Test
public void testMatchedFormatsReturnTheDate() {
    assertEquals(new LocalDate(2015, 12, 25), parseDate("25/12/2015"));
    assertEquals(new LocalDate(2015, 12, 25), parseDate("2015-12-25"));
}

@Test(expected = IllegalArgumentException.class)
public void testUnmatchedFormatThrows() {
    parseDate("25.12.2015");
}

However, this is not very nice, not to mention not very fast since throwing an exception in Java can be quite expensive.

A better alternative exists though: you can use DateTimeFormatterBuilder to create a DateTimeFormatter with multiple patterns. For example:

private static final DateTimeFormatter DATE_FORMATTER =
    new DateTimeFormatterBuilder()
        .append(null, new DateTimeParser[]{
                DateTimeFormat.forPattern("dd/MM/yyyy").getParser(),
                DateTimeFormat.forPattern("yyyy-MM-dd").getParser()})
        .toFormatter();

@Test
public void testMatchedFormatsReturnTheDate() {
    assertEquals(new LocalDate(2015, 12, 25), DATE_FORMATTER.parseLocalDate("25/12/2015"));
    assertEquals(new LocalDate(2015, 12, 25), DATE_FORMATTER.parseLocalDate("2015-12-25"));
}

@Test(expected = IllegalArgumentException.class)
public void testUnmatchedFormatThrows() {
    DATE_FORMATTER.parseLocalDate("25.12.2015");
}

This is a simpler alternative to the previous version, not only because it’s more readable (using exception as control flow is not a good design), but also because the returned object simply implements the same interface as a parser for just one format, allowing you to simply not care whether it supports one or more formats. Win-win!