6

I have some legacy KML documents which includes a time stamp entry. Why is the below date not valid when using Instant to parse? Both methods are suppose to parse ISO 8601 formatted dates.

String dateString = "2017-12-04T08:06:60Z"

Using

java.time.Instant.parse(dateString)

throws an error

"DateTimeParseException Text 2017-12-04T08:06:60Z could not be parsed at index 0."

However, when using

Date myDate =   javax.xml.bind.DatatypeConverter.parseDateTime( dateString )

myDate is parsed correctly....

Jeryl Cook
  • 989
  • 17
  • 40
  • 60 is not a valid number of seconds, is it? Also, class `java.util.Calendar` is lenient by default so I guess it accepts 60 seconds. – Abra Mar 22 '19 at 16:48
  • It can be for a leap second. But I don't *think* `java.time` supports leap seconds. (And I don't believe that's a valid leap second.) – Jon Skeet Mar 22 '19 at 16:55

2 Answers2

6
  1. 60 seconds isn't a valid time. Meaning that this is invalid 2017-12-04T08:06:60Z, if it was 60 seconds then the minute should have incremented and your time would be 2017-12-04T08:07:00Z
  2. Using a valid date and then parsing the String would work just fine:

    String date = "2017-12-04T08:07:00Z";
    System.out.println(Instant.parse(date));
    

Also java.time ignores leap seconds. From the docs:

Implementations of the Java time-scale using the JSR-310 API are not required to provide any clock that is sub-second accurate, or that progresses monotonically or smoothly. Implementations are therefore not required to actually perform the UTC-SLS slew or to otherwise be aware of leap seconds. JSR-310 does, however, require that implementations must document the approach they use when defining a clock representing the current instant. See Clock for details on the available clocks.

Nicholas K
  • 15,148
  • 7
  • 31
  • 57
  • 3
    "60 seconds isn't a valid time" - well, that all depends on your view of leap seconds. But I don't think *this particular* date/time is valid even if you include leap seconds, as that wasn't a time that a leap second was observed. – Jon Skeet Mar 22 '19 at 16:56
  • i get ya...but it seems like having 60Z is valid in the IISO 8601, and thus javax.xml.bind.DatatypeConverter works and doesn't throw an exception...and automatically increments to 7:00Z. – Jeryl Cook Mar 22 '19 at 16:57
  • There's no need to use a specific `DateTimeFormatter` for this pattern - `Instant.parse` is fine for ISO-8601 values with a "Z" zone offset. – Jon Skeet Mar 22 '19 at 16:58
  • @JonSkeet: Point taken. Thanks. – Nicholas K Mar 22 '19 at 17:02
  • 1
    DatatypeConverter automatically increments the date...to 2017-12-04T08:07:00Z...ok. i am processing KML so can't adjust the data so will have to continue to use this class. – Jeryl Cook Mar 22 '19 at 17:08
3

The accepted answer is fine. I just have two things to add:

  1. You can parse the string with the invalid second value of 60 by using ResolverStyle.LENIENT.
  2. Since Jon Skeet in a comment mentioned a possible leap second: It’s not a valid leap second. java.time does support the parsing of a (valid) leap second.

Parsing your string

    DateTimeFormatter lenientFormatter
            = DateTimeFormatter.ISO_OFFSET_DATE_TIME
                    .withResolverStyle(ResolverStyle.LENIENT);
    String dateString = "2018-12-04T08:06:60Z";
    Instant myInstant = lenientFormatter.parse(dateString, Instant::from);
    System.out.println(myInstant);

Output:

2018-12-04T08:07:00Z

So the overflowing second value of 60 has been rolled into a full minute.

By the way, javax.xml.bind.DatatypeConverter.parseDateTime parses into a Calendar (not a Date), which is how the returned object can in fact hold a second value of 60. It seems that it generally accepts a second value of 60, but throws an exception on 61.

Parsing a valid leap second

This does in no way answer your question, but I thought that it might be useful for future readers. A leap second is always the last second of the day, so 23:59:60. An Instant cannot hold this value, but you can query whether one was parsed. It’s supported via DateTimeFormatterBuilder.appendInstant(), and DateTimeFormatter.parsedLeapSecond().

    DateTimeFormatter leapSecondFormatter = new DateTimeFormatterBuilder()
            .appendInstant()
            .toFormatter();
    Instant myInstant
            = leapSecondFormatter.parse("2018-12-04T23:59:60Z", Instant::from);
    System.out.println(myInstant);

    TemporalAccessor parsed = leapSecondFormatter.parse("2018-12-04T23:59:60Z");
    System.out.println("Instant: " + parsed.query(Instant::from));
    System.out.println("Was a leap second parsed? "
            + parsed.query(DateTimeFormatter.parsedLeapSecond()));

Output:

2018-12-04T23:59:59Z
Instant: 2018-12-04T23:59:59Z
Was a leap second parsed? true

I don’t know why it had to be this complicated, but it works.

Link: Documentation of DateTimeFormatter.parsedLeapSecond

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