Single class to parse any Date Format in Java

Viewed 961

I have been parsing dates in the below formats. I maintain an array of these formats and parse every date string in all these formats.

The code I used was -

SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
simpleDateFormat.setTimeZone(timeZone); //timeZone is a java.util.TimeZone object       
Date date = simpleDateFormat.parse(dateString);

Now I want to parse yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX format as well but using SimpleDateFormat the 6 digit microseconds are not considered. So I looked into java.time package.

To parse yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX formats I will be needing OffsetDateTime class and for other formats, I need ZonedDateTime class. The format will be set in DateTimeFormatter class.

Is there a way to use a single class like SimpleDateFormat to pass all the formats?

1 Answers

Since your Java 8 doesn’t behave as would be reasonably expected, I suggest that a workaround is trying to parse without zone first. If a zone or an offset is parsed from the string, this will be used. If the parsing without zone fails, try with a zone. The following method does that:

private static void parseAndPrint(String formatPattern, String dateTimeString) {
    // Try parsing without zone first
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(formatPattern);
    Instant parsedInstant;
    try {
        parsedInstant = formatter.parse(dateTimeString, Instant::from);
    } catch (DateTimeParseException dtpe) {
        // Try parsing with zone
        ZoneId defaultZone = ZoneId.of("Asia/Calcutta");
        formatter = formatter.withZone(defaultZone);
        parsedInstant = formatter.parse(dateTimeString, Instant::from);
    }
    System.out.println("Parsed instant: " + parsedInstant);
}

Let’s try it:

    parseAndPrint("yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX", "2018-10-22T02:17:58.717853Z");
    parseAndPrint("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", "2018-10-22T02:17:58.717853");
    parseAndPrint("EEE MMM d HH:mm:ss zzz yyyy", "Mon Oct 22 02:17:58 CEST 2018");

Output on Java 8 is:

Parsed instant: 2018-10-22T02:17:58.717853Z
Parsed instant: 2018-10-21T20:47:58.717853Z
Parsed instant: 2018-10-22T00:17:58Z

The first example has an offset in the string and the last a time zone abbreviation in the string, and in both cases are these respected: the instant printed has adjusted the time into UTC (since an Instant always prints in UTC, its toString method makes sure). The middle example has got neither offset nor time zone in the string, so uses the default time zone of Asia/Calcutta specified in the method.

That said, parsing a three or four letter time zone abbreviation like CEST is a dangerous and discouraged practice since the abbreviations are often ambiguous. I included the example for demonstration only.

Is there a way to use a single class…?

I have used Instant for all cases, so yes there is a way to use just one class. The limitation is that you do not know afterward whether any time zone or offset was in the string nor what it was. You didn’t know when you were using SimpleDateFormat and Date either, so I figured it was OK?

A bug in Java 8?

The results from your demonstration on REX tester are disappointing and wrong and do not agree with the results I got on Java 11. It seems to me that you have been hit by a bug in Java 8, possibly this one: Parsing with DateTimeFormatter.withZone does not behave as described in javadocs.

Related