java.time.ZonedDateTime.parse and iso8601?

心已入冬 提交于 2020-08-24 06:30:14

问题


Why does the JDK8 DateTime library seem to not parse valid iso8601 date time strings? It chokes on time zone offsets expressed like "+01" instead of "+01:00"

This works:

java.time.ZonedDateTime.parse("2015-08-18T00:00+01:00")

This throws a parse exception:

java.time.ZonedDateTime.parse("2015-08-18T00:00+01")

From the iso8601 wikipedia page:

The offset from UTC is appended to the time in the same way that 'Z' was above, in the form ±[hh]:[mm], ±[hh][mm], or ±[hh]. So if the time being described is one hour ahead of UTC (such as the time in Berlin during the winter), the zone designator would be "+01:00", "+0100", or simply "+01".

EDIT: This looks like an actual legitimate bug in the JDK.

https://bugs.openjdk.java.net/browse/JDK-8032051

Wow, after testing that new date time stuff for years, I thought they would have caught something so obvious. I also thought the JDK author types were rigorous enough to use a better automated test suite.

UPDATE: This is completely fixed in the current jdk-9 build. I just confirmed. The exact same parse command showed above fails in the current jdk-8 build and works perfectly in jdk-9.

ADDENDUM: FWIW, RFC 3339 based on ISO-8601, does not allow for this short hand. You must specify minutes in the time zone offsets.


回答1:


You use this default formatter: ISO_OFFSET_DATE_TIME (because parse 2015-08-18T00:00+01:00).

In documentation:

This returns an immutable formatter capable of formatting and parsing the ISO-8601 extended offset date-time format. [...]

The offset ID. If the offset has seconds then they will be handled even though this is not part of the ISO-8601 standard. Parsing is case insensitive.

It's (you use only this for this default formatter):

The ID is minor variation to the standard ISO-8601 formatted string for the offset. There are three formats:

  • Z - for UTC (ISO-8601)
  • +hh:mm or -hh:mm - if the seconds are zero (ISO-8601)
  • +hh:mm:ss or -hh:mm:ss - if the seconds are non-zero (not ISO-8601) (don't +hh like ISO-8601).

It seems like java.time (JDK 8) don't full implements ISO-8601 in all.


This:

java.time.ZonedDateTime.parse("2015-08-18T00:00+01:00"); // works

corresponds to (roughly from source JDK):

DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
DateTimeFormatter formatter = builder
        .parseCaseInsensitive()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendOffsetId()
        .toFormatter();

java.time.ZonedDateTime.parse("2015-08-18T00:00+01:00", formatter); // it's same

You can create own DataTimeFormatter with DateTimeFormatterBuilder.

DateTimeFormatterBuilder builder2 = new DateTimeFormatterBuilder();
DateTimeFormatter formatter2 = builder2.parseCaseInsensitive()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendPattern("X") // eg.:
        .toFormatter();

java.time.ZonedDateTime.parse("2015-08-18T00:00+01", formatter2); // here you set +01

Instead of appendOffsetId() use appendPattern(String pattern) and set 'X' or 'x'.

Now, you can use your datatime 2015-08-18T00:00+01.


Or... Use default ISO_OFFSET_DATE_TIME and add postfix :00.

java.time.ZonedDateTime.parse("2015-08-18T00:00+01" + ":00");

But this last is bad solution.




回答2:


This is completely fixed in the current jdk-9 build. I just confirmed. The exact same parse command showed above fails in the current jdk-8 build and works perfectly in jdk-9.

Using the new jdk-9 shell:

➜  jdk-9 bin/jshell
|  Welcome to JShell -- Version 9-ea
|  For an introduction type: /help intro

jshell> java.time.ZonedDateTime.parse("2015-08-18T00:00+01")
$1 ==> 2015-08-18T00:00+01:00



回答3:


The code which is used is added by DateTimeFormatterBuilder.appendZoneId() which allows the formats for time zone as

For example, the following will parse:

"Europe/London"           -- ZoneId.of("Europe/London")
"Z"                       -- ZoneOffset.UTC
"UT"                      -- ZoneId.of("UT")
"UTC"                     -- ZoneId.of("UTC")
"GMT"                     -- ZoneId.of("GMT")
"+01:30"                  -- ZoneOffset.of("+01:30")
"UT+01:30"                -- ZoneOffset.of("+01:30")
"UTC+01:30"               -- ZoneOffset.of("+01:30")
"GMT+01:30"               -- ZoneOffset.of("+01:30")

You can define your own date time format to allow hour offsets, but many places in the world have fractions of an hour such as Nepal which is +05:45 and North Korea which recently changed to +08:30




回答4:


Thx @mkczyk said I made my own formatter for the job. I needed a parser for date's like

  1. "2016-02-14T18:32:04.150Z"
  2. "2016-02-14T21:32:04.150+04"
  3. "2016-02-14T21:32:04.150+04:00"

private static final DateTimeFormatter DATE_TIME_NANOSECONDS_OFFSET_FORMATTER = new DateTimeFormatterBuilder().parseCaseInsensitive().append(ISO_LOCAL_DATE_TIME) .appendFraction(ChronoField.NANO_OF_SECOND, 0, 3, true) .appendOffset("+HH:mm", "Z") .toFormatter();

Struggled a bit with appendfraction(..,..,..,true) considers nanoseconds as decimal of seconds.

ZonedDateTime zdt = ZonedDateTime.parse(textToParse, DATE_TIME_NANOSECONDS_OFFSET_FORMATTER);

And after that used to get UTC time from it.

zdt = ZonedDateTime.ofInstant(zdt.toInstant(), ZoneId.of("UTC"));


来源:https://stackoverflow.com/questions/32079459/java-time-zoneddatetime-parse-and-iso8601

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!