我需要将表示DateTime的字符串解析为要操作的Java 8 DateTime对象.例如,有效字符串为(ALL datetime components (except year) are optional):

2015
2015-01
2015-01-01
2015-02-03T00
2015-02-03T00:30
2015-02-03T00:30:50
2015-02-03T00:30:50Z
2015-02-03T00:30:50.333Z
2015-02-03T00:30:50.333+03:00
2015-02-03T00:30:50.333+0300
2015-02-03T00:30:50.333-08
2015-02-03T00:30:50.333GMT+8
2015-02-03GMT+8
2015-02UTC-3
2015-02PST
...

我从https://stackoverflow.com/a/52823661/2028440找到的最好的代码(包括我从https://stackoverflow.com/a/65985789/2028440开始的调整),

                DateTimeFormatter dtf = new DateTimeFormatterBuilder()
                .appendValue(ChronoField.YEAR, 4)
                .appendPattern("[['-']MM[['-']dd[['T']HH[[':']mm[[':']ss['.'SSS]]]]]][Z][O][VV]")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                .parseDefaulting(ChronoField.NANO_OF_SECOND, 0)
                .toFormatter();

        String[] s = {
                "2018",
                "2018-10",
                "2018-10-15",
                "2018-10-15T12:00",
                "2018-10-15T12:00:30",
                "2018-10-15T12:00:30.123Z",
                "2018-10-15T12:00:30.123-0800",
                "2018-10-15T12:00:30.123UTC-08:00",
                "2018-10-15T12:00:30.123GMT+9"
        };
        for (String line : s) {
            System.out.println(LocalDateTime.parse(line, dtf));
        }

控制台输出为:

2018-01-01T00:00
2018-10-01T00:00
2018-10-15T00:00
2018-10-15T12:00
2018-10-15T12:00:30
2018-10-15T12:00:30.123
2018-10-15T12:00:30.123
2018-10-15T12:00:30.123
2018-10-15T12:00:30.123

但是它不能正确地将时间偏移量解析为它打印的最后三个值2018-10-15T12:00:30.123.

他们每个人也应该得到print the correct time offset英镑. 以下是最后3个测试字符串的正确输出:

"2018-10-15T12:00:30.123-08:00"
"2018-10-15T12:00:30.123-08:00"
"2018-10-15T12:00:30.123+09:00"

它类似于以下命令的输出:

System.out.println(ZonedDateTime.parse("2018-10-15T12:00:30.123-08:00").toString());

prints

2018-10-15T12:00:30.123-08:00

当我试图改变的时候:

            System.out.println(LocalDateTime.parse(line, dtf));

致:

            System.out.println(ZonedDateTime.parse(line, dtf));

我收到了这样的错误:

Exception in thread "main" java.time.format.DateTimeParseException: Text '2018' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2018-01-01T00:00 of type java.time.format.Parsed
    at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2017)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1952)
    at java.base/java.time.ZonedDateTime.parse(ZonedDateTime.java:598)

推荐答案

您使用了LocalDateTime.parse,所以当然只输出字符串的本地日期时间部分.

使用ZonedDateTime.parse时,某些字符串无法解析,因为没有偏移量或区域信息.我们可以通过添加一个

.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)

但是,这不会处理只有时区而没有偏移量信息的情况.(请注意,"2018-10-15T12:00:30.123 UTC-08:00"中的"UTC-08:00"被认为是一个时区ID)这样的字符串实际上是不明确的--只有一个时区和一个本地日期时间有时不足以在一个"重叠"的时区转换中得到ZonedDateTime.相同的本地日期时间将在不同的偏移量处出现两次.你必须做出 Select 哪一个的决定.

因此,不要在偏移量上也使用parseDefaulting,而是直接使用dtf.parse(line)并对得到的TemporalAccessor进行查询.有3个 case :

  • 临时访问器具有偏移量信息,使用该偏移量使ZonedDateTime
  • 时态访问器只有时区信息,使用时区表示ZonedDateTime,并记住在发生重叠转换时决定 Select 哪个时区
  • 临时访问器既没有偏移量也没有时区信息.使用协调世界时作为区域,使ZonedDateTime.

当字符串中同时存在偏移量和时区信息时,您还可以添加第四种情况来处理,如果需要,可以使用这两种情况来创建一个ZonedDateTime.

var accessor = dtf.parse(line);
var zone = accessor.query(TemporalQueries.zone());
var offset = accessor.query(TemporalQueries.offset());
var ldt = LocalDateTime.from(accessor);
if (offset != null) {
    System.out.println(ZonedDateTime.of(ldt, offset));
} else if (zone != null) {
    // this chooses the earlier time in an overlap
    System.out.println(ZonedDateTime.of(ldt, zone));

    // more generally...
//    var transition = zone.getRules().getTransition(ldt);
//    if (transition != null && transition.isOverlap()) {
//        ZonedDateTime.ofLocal(
//                ldt, zone, 
//                transition.getOffsetAfter() // or getOffsetBefore()
//        );
//    }
} else {
    System.out.println(ZonedDateTime.of(ldt, ZoneOffset.UTC));
}

以上输出:

2018-01-01T00:00Z
2018-10-01T00:00Z
2018-10-15T00:00Z
2018-10-15T12:00Z
2018-10-15T12:00:30Z
2018-10-15T12:00:30.123Z
2018-10-15T12:00:30.123-08:00
2018-10-15T12:00:30.123-08:00[UTC-08:00]
2018-10-15T12:00:30.123+09:00

如果您不想要[UTC-08:00]时区ID,可以使用toOffsetDateTime来获得OffsetDateTime.

Java相关问答推荐

Android -如何修复Java.time.zone. ZoneRulesExcept:未知时区ID:Europe/Kyiv

Java:根据4象限中添加的行数均匀分布行的公式

我想了解Java中的模块化.编译我的应用程序时,我有一个ResolutionException

Spring Batch 5-不要让它在数据库中自动创建表

Java模式匹配记录

使用JdkClientHttpRequestFactory通过Spring RestClient和Wiemock读取时达到EOF

DTO到实体,反之亦然,控制器和服务之间的哪一层应该处理转换?

Java:使用Class.cast()将对象转换为原始数组

测试期间未执行开放重写方法

如何从Keyloak映射Hibernate实体中的用户

Domino Designer 14中的保存代理添加了重影库

在添加AdMob时无法为Google Play构建应用程序包:JVM垃圾收集器崩溃和JVM内存耗尽

允许同时执行两个方法,但不能同时执行这两个方法

没有使用Lombok生成的参数

Java在操作多个属性和锁定锁对象时使用同步和易失性

如何在运行docker的应用程序中获取指定的配置文件

如何在Java中使用正则表达式拆分字符串

java21预览未命名的符号用于try-with-resources

如何使用命令行为Java应用程序生成烟雾测试用例

移动二维数组的行