user468587
user468587

Reputation: 5031

How to parse ISO compliant string to Date using Java Time library

I have timestamp in various format, example:

2018-07-21T00:50:39
2017-11-20T23:18:27.529Z

I need to parse them to java.util.Date, and have tried the following approach, they both worked for the first format but failed the second one.

Here are the methods I tried and it's error:

1.

private Date try3(String dateString) {
        return Date.from(Instant.parse(dateString).atZone(ZoneId.of("UTC")).toInstant());
    }

it failed at first format, error:

java.time.format.DateTimeParseException: Text '2017-11-20T23:18:28' could not be parsed at index 19

2.

private Date try2(String dateString) {
    return Date.from(LocalDateTime.parse(dateString).atZone(ZoneId.of("UTC")).toInstant());
}

for second format, this method throw error:

java.time.format.DateTimeParseException: Text '2017-11-20T23:18:27.529Z' could not be parsed, unparsed text found at index 23

3:

private Date try1(String dateString) {
    return Date.from(ZonedDateTime.parse(dateString,
                    DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss").withZone(ZoneId.of("UTC"))).toInstant());

}

for the second format, the above method throw error:

java.time.format.DateTimeParseException: Text '2017-11-20T23:18:27.529Z' could not be parsed, unparsed text found at index 19

I eventually ended up with Joda library:

private Date try4(String dateString) {
        return new DateTime(dateString).withZone(DateTimeZone.UTC).toDate();
    }

But really is there any way to do this in Java time library?

Upvotes: 0

Views: 134

Answers (2)

Anonymous
Anonymous

Reputation: 86389

You always want to obtain the same type, and I seem to understand that if no offset is in the string, you want UTC (it’s important to be clear about this). I suggest specifying a formatter with optional UTC offset and a default of UTC (zero offset) in case there isn’t any offset in the string:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
            .appendPattern("[X]")
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();

    String withoutZ = "2018-07-21T00:50:39";
    Instant anInstant = formatter.parse(withoutZ, Instant::from);
    System.out.println(anInstant);

    String withZ = "2017-11-20T23:18:27.529Z";
    Instant anotherInstant = formatter.parse(withZ, Instant::from);
    System.out.println(anotherInstant);

Output:

2018-07-21T00:50:39Z
2017-11-20T23:18:27.529Z

The square brackets in the format pattern string [X] denote that offset X is optional. The presence and absence of decimals on the seconds is handled by ISO_LOCAL_DATE_TIME.

I hope that you are aware that the Date class is poorly designed and long outdated, so generally you shouldn’t want one, and that you are only asking for one because you indispensably need one for a legacy API that you cannot change just now. In this case do the final conversion from Instant as in your question.

The techniques generally used for variable date/time formats include:

  1. Trying a number of known formats in turn.
  2. Taking a taste of the string before determining which format to use. In your case you might have branched on .endsWith("Z") as already suggested in Basil Bourque’s answer.
  3. Optional parts in the format pattern.
  4. DateTimeFormatterBuilder.parseDefaulting() for parts that may be absent from the parsed string.
  5. DateTimeFormatter.parseBest().

As you have seen, I am using items 3. and 4. here.

Upvotes: 1

Basil Bourque
Basil Bourque

Reputation: 340230

java.time

This has been covered countless times already. So search Stack Overflow for more info.

Never use Date or Calendar. Use only java.time. Likewise, Joda-Time is also supplanted by java.time (both being led by the same man, Stephen Colebourne).

Both your string inputs are in standard ISO 8601 format. The java.time classes use these formats by default when parsing/generating strings.

The first lacks an indicator of offset-from-UTC or time zone, so it does not represent a moment.

LocalDateTime.parse( "2018-07-21T00:50:39" )

The second in Z which means UTC, and is pronounced “Zulu”.

Instant.parse( "2017-11-20T23:18:27.529Z" )

To handle both formats in one method, search the input string for the presence of the Z and branch accordingly. Also, trap for the exception in case you receive unexpected inputs.

If you must have a java.util.Date object, see that class JavaDoc for new methods from & to… on the old classes that convert to/from the java.time classes. As I said, all this has been covered many many times, so search for more info.

Upvotes: 1

Related Questions