3

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?

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Possible duplicate of [Java string to date conversion](https://stackoverflow.com/questions/4216745/java-string-to-date-conversion), check this answer https://stackoverflow.com/a/4216767/4892907 – xxxvodnikxxx Jan 07 '19 at 11:29
  • It depends on the result you require. If something akin to a `Date` (that is, without time zone or offset) is fine for you, just use `Instant` and `yourDateTimeFormatter.parse(yourString, Instant::from)`. – Ole V.V. Jan 07 '19 at 11:30
  • 1
    What did your search bring up? There are a couple of pretty similar questions out there already. – Ole V.V. Jan 07 '19 at 11:31
  • 1
    Also “any format” is clearly impossible. Could you be more specific about your format requirements? Maybe give 5 examples? – Ole V.V. Jan 07 '19 at 11:33
  • @OleV.V. I am setting the timeZone using setTimeZone but yyyy-MM-dd'T'HH:mm:ss.SSSSSSXXX format already has timezone in that. If I use ZonedDateTime, the timeZone specified using withZone() is used not the timezone in the date string. If I use OffsetDateTime, other formats cannot be parsed. – Forever NewUser Jan 07 '19 at 11:34
  • Uhu? “If a zone has been parsed directly from the text, …, then this override zone (that is, the zone from `withZone()`) has no effect.” (from [`withZone` documentation](https://docs.oracle.com/javase/10/docs/api/java/time/format/DateTimeFormatter.html#withZone(java.time.ZoneId))) – Ole V.V. Jan 07 '19 at 11:37
  • 1
    Related: [SimpleDateFormat Ignore Characters](https://stackoverflow.com/questions/33575947/simpledateformat-ignore-characters). Take a look, and search for more. – Ole V.V. Jan 07 '19 at 11:50
  • @OleV.V. When the text has Z at the end and withZone() sets the time zone to IST, the withZone does have effect. – Forever NewUser Jan 07 '19 at 12:45
  • That may be because `Z` is an offset, not a time zone. java.time distinguishes. Do you need the offset ( `Z`) from the string? – Ole V.V. Jan 07 '19 at 12:55
  • @OleV.V. https://rextester.com/LNT50546. If you check this, the time is 02:17 UTC but it is displayed as 02:17 IST because withZone() is used. – Forever NewUser Jan 07 '19 at 12:56
  • That’s disappointing and wrong indeed. On my Java 11 I got `2018-10-22T07:47:58.717853+05:30[Asia/Calcutta]`, which is the correct point in time. And if I use `OffsetDateTime.parse(date, istFormat)` instead, I do get `2018-10-22T02:17:58.717853Z`, that is, the offset from the string, not from the formatter. – Ole V.V. Jan 07 '19 at 13:03
  • @OleV.V. I'm using Java 8 – Forever NewUser Jan 07 '19 at 13:10

1 Answers1

2

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.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161