2

I have a date string which I want to parse as Instant 48962-08-06T23:16:59.000Z but unable to do as most of standard DateTimeFormatter rules are not supporting it.

Ref: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

Here is what I was trying to do, using my custom formatter

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendValue(YEAR, 5, 10, SignStyle.EXCEEDS_PAD)
            .appendLiteral('-')
            .appendValue(MONTH_OF_YEAR, 2)
            .appendLiteral('-')
            .appendValue(DAY_OF_MONTH, 2)
            .appendLiteral('T')
            .appendValue(HOUR_OF_DAY, 2).appendLiteral(':')
            .appendValue(MINUTE_OF_HOUR, 2).appendLiteral(':')
            .appendValue(SECOND_OF_MINUTE, 2)
            .appendFraction(NANO_OF_SECOND, 0, 9, true)
            .appendLiteral('Z')
            .toFormatter();
    
    Instant value = Instant.from(formatter.parse("48962-08-06T23:16:59.000Z"));

But it fails with Unable to obtain Instant from TemporalAccessor: {},ISO resolved to +48962-08-06T23:16:59 of type java.time.format.Parsed

  • 2
    What kind of year do these 5-digits represent? Is it from some culture-specific calendar or do you really want to parse a year that far in the future? – deHaar Sep 11 '20 at 09:03
  • 2
    @Fildor Instant supports max to `+1000000000-12-31T23:59:59.999999999Z` so how can we parse it? – Varun Dhall Sep 11 '20 at 09:03
  • @Fildor to me the question seems obvious, although he should probably mention it. And that is "how do I get an Instant object with the timestamp 48962-08-06T23:16:59.000Z ?" – Ivo Sep 11 '20 at 09:03
  • @deHaar just some future date we need to parse – Varun Dhall Sep 11 '20 at 09:04
  • Check my comment on deHaars answer: the high year number is a red herring, the problem is related to time zones (or the lack thereof in this case). – Joachim Sauer Sep 11 '20 at 09:38
  • Related: [parsing date/time to localtimezone](https://stackoverflow.com/questions/51206465/parsing-date-time-to-localtimezone). That question is asking about the outdated `SimpleDateFormat` class,. The error is similar, but manifests differently. Also related: [Elastic Search and Y10k (years with more than 4 digits)](https://stackoverflow.com/questions/62541394/elastic-search-and-y10k-years-with-more-than-4-digits). – Ole V.V. Sep 14 '20 at 05:28
  • From where comes the requirement to handle that point in time? Also asking because a possible explanation is that someone confused seconds with milliseconds since the epoch and really intended 2016-12-28T16:39:19.019Z. – Ole V.V. Sep 14 '20 at 05:35

2 Answers2

4

On my machine, the following code parses your future year:

public static void main(String[] args) {
    String dateTime = "48962-08-06T23:16:59.000Z";
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuuu-MM-dd'T'HH:mm:ss.SSSz");
    Instant instant = Instant.from(dtf.parse(dateTime));
    System.out.println(instant);
}

and outputs

+48962-08-06T23:16:59Z

I think the problem with your code is one (or both) of the lines

            .appendFraction(NANO_OF_SECOND, 0, 9, true)
            .appendLiteral('Z')

because the three digits after the dot and before the Z in the String might not be nanos of second but rather fraction of second.
And the other line might be problematic, too, but I can't really tell you why. I only know that my answer would throw an exception when the z (lower case) in the pattern is changed to a Z (upper case).

You could adjust that in your DateTimeFormatter and try again, might work.

deHaar
  • 17,687
  • 10
  • 38
  • 51
  • 2
    It is definitely the `Z`, that is the problem. OP parses what is basically a `LocalDateTime`: i.e. a date and time without time zone information. One *can not* convert that to an `Instant` without knowing what time zone to apply. So they need to *either* provide the timezone externally (by converting to a `LocalDateTime` and then using `toInstant()`, providing a time zone) or change the parsing rule to interpret the `Z` as a UTC indicator by replacing `.appendLiteral('Z')` with `.appendZoneId()`. – Joachim Sauer Sep 11 '20 at 09:38
3

I agree with you that the nice solution is using a builder. The following one parses years with both 4 and 5 digits:

    DateTimeFormatter formatter = new DateTimeFormatterBuilder()
            .appendValue(ChronoField.YEAR, 4, 5, SignStyle.NOT_NEGATIVE)
            .appendPattern("-MM-dd'T'")
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .appendOffsetId()
            .toFormatter();
    
    String dateTime = "48962-08-06T23:16:59.000Z";
    
    Instant i = formatter.parse(dateTime, Instant::from);
    
    System.out.println(i);

Output from the snippet is:

+48962-08-06T23:16:59Z

Your basic problem is that your string doesn’t conform with ISO 8601, the standard that the classes of java.time parse as their default, that is, without any explicit formatter. Years with more than 4 digits can be allowed in ISO 8601, but then a sign is required (as you can also see in the output above).

What went wrong in your code? Joachim Sauer is correct in the comment: Hardcoding Z as a literal is wrong. It’s an offset of 0 from UTC and needs to be parsed as an offset. Since your formatter didn’t parse any offset, it was unable to determine a point int time, an instant, from the parsed information. This was what your error message meant:

Unable to obtain Instant from TemporalAccessor: {},ISO resolved to +48962-08-06T23:16:59 of type java.time.format.Parsed

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