Java 基于解析的TemporalAccessor有条件地创建LocalDateTime、ZonedDateTime或OffsetDateTime

Java 基于解析的TemporalAccessor有条件地创建LocalDateTime、ZonedDateTime或OffsetDateTime,java,datetime,Java,Datetime,给定的DateTimeFormatter定义为: public static final DateTimeFormatter DATE_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append( ISO_LOCAL_DATE ) .optionalStart().appendLiteral( ' ' ).optionalEnd() .optionalS

给定的
DateTimeFormatter
定义为:

public static final DateTimeFormatter DATE_TIME = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .append( ISO_LOCAL_DATE )
        .optionalStart().appendLiteral( ' ' ).optionalEnd()
        .optionalStart().appendLiteral( 'T' ).optionalEnd()
        .append( ISO_LOCAL_TIME )
        .optionalStart().appendLiteral( ' ' ).optionalEnd()
        .optionalStart().appendZoneOrOffsetId().optionalEnd()
        .toFormatter();
我希望根据解析是否包含区域id、偏移量或两者都不包含,有条件地创建
LocalDateTime
ZonedDateTime
OffsetDateTime

到目前为止,我已经:

    DATE_TIME.parse(
            text,
            (temporal) -> {
                // see if there is an offset
                final ZoneOffset offset = temporal.query( TemporalQueries.offset() );
                if ( offset != null ) {
                    return OffsetDateTime.from( temporal );
                }

                // see if there is a tz
                final ZoneId zoneId = temporal.query( TemporalQueries.zoneId() );
                if ( zoneId != null ) {
                    return ZonedDateTime.from( temporal );
                }

                // otherwise its a LocalDateTime
                return LocalDateTime.from( temporal );
            }
    );
但我发现,区域偏移永远不会被“识别”-即使文本包含偏移,它也总是作为区域id报告。例如,给定
“1999-12-31 12:59:59+02:00”
,我希望出现
OffsetDateTime
。但是,
“+02:00”
始终解析为区域id。考虑到分区和偏移之间的相互作用,它最终会起作用。但是作为一个正确性问题(可能有点过头了),我想把它们看作是
OffsetDateTime

我是否错过了一些东西来做出区分


谢谢

看起来这实际上是Java8中的一个bug。如果将
DateTimeFormatter#parse
的值存储在
Temporal
变量中并打印其
getClass()
,则在使用Java 8编译和运行时会收到以下信息:

class java.time.ZonedDateTime
但是,在使用Java 11编译和运行时,输出是您所期望的:

class java.time.OffsetDateTime

我将四处搜索特定的bug报告,并编辑这个答案,如果我设法找到它的话。这样,我们就可以确定错误的原因以及修复错误的Java版本。

这并不是真正的答案。Jacob G.已经提供了这一点。我只想补充一下

Java8的变通方法 从
TemporalQueries.zone()
TemporalQueries.ZoneId()
中获得的
ZoneId
在从字符串解析偏移量时是一个
ZoneOffset
。编辑:
UTC+02:00
不会显示为
ZoneOffset
,但在
ZoneId
上调用
normalized()
会将其转换为一个。现在,
instanceof
操作符将告诉您得到了哪些

    for (String text : new String[] {
            "1999-12-31 12:59:59",
            "1999-12-31 12:59:59 +02:00",
            "1999-12-31 12:59:59 UTC+02:00",
            "1999-12-31 12:59:59 America/Porto_Velho",
    }) {
        TemporalAccessor parsed = DATE_TIME.parse(
                text ,
                (temporal) -> {
                    // see if there is an offset or tz
                    final ZoneId zoneOrOffset = temporal.query( TemporalQueries.zone() );
                    if ( zoneOrOffset != null ) {
                        ZonedDateTime zdt = ZonedDateTime.from( temporal );
                        // EDIT: call normalized() to convert a ZoneId
                        // with constant offset, e.g., UTC+02:00, to ZoneOffset
                        if (zoneOrOffset.normalized() instanceof ZoneOffset) {
                            return zdt.toOffsetDateTime();
                        } else {
                            return zdt;
                        }
                    }

                    // otherwise it's a LocalDateTime
                    return LocalDateTime.from( temporal );
                }
        );

        System.out.format(Locale.ENGLISH, "%-30s %s%n", parsed.getClass(), parsed);
    }
此代码段的输出为:

适用于Java9及更高版本的更简单方法 你工作太努力了。使用
DateTimeFormatter.parseBest()
应该在Java8中完成您的工作,并且在Java9和更高版本中也可以工作

    String text = "1999-12-31 12:59:59 +02:00";

    TemporalAccessor parsed = DATE_TIME.parseBest(text,
            OffsetDateTime::from, ZonedDateTime::from, LocalDateTime::from);

    System.out.println(parsed.getClass());
    System.out.println(parsed);
Java 9上的输出:


编辑:这种方法不会将
UTC+02:00
作为时区的字符串制作成
OFfsetDateTime

我无法复制。当我试图用你的代码解析
1999-12-31 12:59:59+02:00
时,我确实得到了一个
OffsetDateTime
。我使用的是Java 9.0.4。出于好奇,您将结果分配给什么类型<代码>时态?@erickson
时态
将是一个不错的选择。它比
TemporalAccessor
(我在“答案”中使用了它,它满足了我在那里的需要,即使
Object
也可以工作)。你的答案让我走上了“正确的道路”。我几乎可以使用Java8实现它,尽管出于某种原因JDK决定将
ZoneRegion
非公共化。在某些情况下(例如UTC+02:00),分区将作为
分区返回。在其他情况下,当
ZoneOffset
用于
ZoneId
时,我可以使用instanceof实现此功能。但是,由于
ZoneRegion
是非公开的,我无法做到这一点(没有糟糕的反思,ofc)@SteveEbersole我明白了。升级到Java9+不是一个选项吗?这是在Hibernate内部使用的(我正在重新处理查询中的时态文字支持)。不幸的是,我们仍然使用Java8作为基线,所以我需要支持它。更糟糕的是,我需要在8和9+上支持它:(我也没有在中找到它。我确信它一定在某个地方。
#parseBest
工作得很好,尽管我确实看到了与您在Java 8中所说的略有不同的行为,在Java 8中,它从不返回
OffsetDateTime
。我总是得到
LocalDateTime
zoneDateTime
。例如e value
“1999-12-31 12:59:59+02:00”
我得到了一个
ZoneDateTime
,其区域id(
ZoneDateTime#getZone
)为
“+02:00”
。一般来说,这对我来说很好-我通常对ZoneDateTime感兴趣,因为它更“持久性就绪”格式,尽管我有兴趣了解其后果。再次感谢Ole!这似乎与我的观察结果一致。我特意为Java 9和以后版本采用了更简单的方式。再次感谢您的报告。
    String text = "1999-12-31 12:59:59 +02:00";

    TemporalAccessor parsed = DATE_TIME.parseBest(text,
            OffsetDateTime::from, ZonedDateTime::from, LocalDateTime::from);

    System.out.println(parsed.getClass());
    System.out.println(parsed);
class java.time.OffsetDateTime
1999-12-31T12:59:59+02:00