java.time.format.DateTimeParseException could not be parsed at index 0

Steve picture Steve · Oct 23, 2017 · Viewed 9.9k times · Source

I am trying to tell Gson how to parse LocalDateTime and LocalDate, but I'm getting this error, which looks to me like it should match the format. I'm thinking there's either something I don't understand about parsing dates or something I don't understand about Gson.

java.time.format.DateTimeParseException: Text '2017101800000700' could not be parsed at index 0

Gson gson = new GsonBuilder().registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
    @Override
    public LocalDateTime deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        return LocalDateTime.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
    }
  }).registerTypeAdapter(LocalDate.class, new JsonDeserializer<LocalDate>() {
    @Override
    public LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        return LocalDate.parse(json.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ofPattern("yyyyMMdd"));
    }
  }).create();

Answer

user7605325 picture user7605325 · Oct 24, 2017

As @Jon Skeet said in the comments, your pattern has 1 extra digit when compared to the input string, so yyyyMMddHHmmssSSS won't work: the input 2017101800000700 has 16 digits, while the pattern yyyyMMddHHmmssSSS expects 17.


Although the last part (0700) looks like an UTC offset, it's missing a + or - sign (so it should be +0700 or -0700). The offset represents the difference from UTC, and without a sign, it's ambiguous: you can't say if it's ahead or behind UTC.

And even if it's really an offset, I couldn't find a way to parse without a sign: I tried with all the available options and none worked. A sign is always required, so parsing it as an offset is not possible, unless you make an arbitrary assumption (such as "it's positive") and change the input manually, like this:

// assuming the offset "0700" is positive (7 hours ahead UTC)
String dateStr = "2017101800000700";

// insert the "+" manually, so input becomes 201710180000+0700
dateStr = dateStr.substring(0, 12) + "+" + dateStr.substring(12, 16);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmXX");
System.out.println(LocalDateTime.parse(dateStr, fmt)); // 2017-10-18T00:00

This will result in a LocalDateTime equals to:

2017-10-18T00:00


Another alternative is to treat 07 as seconds and the last 2 zeroes as fractions of second.

In this case, a pattern such as yyyyMMddHHmmssSS won't work due to a bug in Java 8 API.

The same link above also provides the workaround: use a java.time.format.DateTimeFormatterBuilder with a java.time.temporal.ChronoField for the fraction of seconds.

String dateStr = "2017101800000700";
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // date/time
    .appendPattern("yyyyMMddHHmmss")
    // milliseconds (with 2 digits)
    .appendValue(ChronoField.MILLI_OF_SECOND, 2)
    // create formatter
    .toFormatter();
System.out.println(LocalDateTime.parse(dateStr, fmt)); // 2017-10-18T00:00:07

This will parse the following LocalDateTime:

2017-10-18T00:00:07

Note that it's different from the previous one, because now we're considering 07 to be the seconds.