Java8 中将 java.util.Date 转换为 java.time.ZonedDateTime

107

在尝试将java.util.Date转换为java.time.LocalDate时,我遇到了以下异常。

java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: 2014-08-19T05:28:16.768Z of type java.time.Instant
代码如下:
public static Date getNearestQuarterStartDate(Date calculateFromDate){

    int[] quaterStartMonths={1,4,7,10};     
    Date startDate=null;

    ZonedDateTime d=ZonedDateTime.from(calculateFromDate.toInstant());
    int frmDateMonth=d.getMonth().getValue();

我使用 ZonedDateTime 类的方式有问题吗?根据文档,它应该能将 java.util.Date 对象转换为 ZonedDateTime。上面的日期格式是标准日期格式吗?如果不行,我需要退回使用Joda time吗?如果有人能提供一些建议,那就太好了。

4个回答

145

要将 Instant 转换为 ZonedDateTimeZonedDateTime 提供了方法ZonedDateTime.ofInstant(Instant, ZoneId)。 因此,如果您想在默认时区中获取 ZonedDateTime,则应该使用以下代码:

ZonedDateTime d = ZonedDateTime.ofInstant(calculateFromDate.toInstant(),
                                          ZoneId.systemDefault());

糟糕。谢谢。现在已经修复了。 - JB Nizet
如果我想将其转换为相同的时区怎么办?我相信日期始终是UTC,所以我可以使用ZoneId.UTC吗? - Didier A.
8
一个日期本身没有任何时区信息。它只是自一个精确时刻(1970-01-01T00:00 UTC)以来经过的毫秒数。并不存在所谓的“相同”时区。如果想让ZonedDateTime处于UTC时区,就必须传递ZoneOffset.UTC。 - JB Nizet
3
@DidierA。Date 类型与 Instant 类型类似,它们都是 UNIX 时间戳。此外,通常惯例认为 UNIX 时间戳是 UTC 时区时间。 - kevinarpe

44

要从Date获取ZonedDateTime,您可以使用:

calculateFromDate.toInstant().atZone(ZoneId.systemDefault())
你可以调用toLocalDate方法来获取一个LocalDate。请参见:将java.util.Date转换为java.time.LocalDate

以上解决方案对我也很有用,将其转换为LocalDate非常实用。但是有一个问题,从设计角度来看,只要提供了java.time.Instant,ZonedDateTime.from()应该按预期运行。但在这种情况下,它并没有按预期工作。这是某种不一致性吗? - Ironluca
3
ZonedDateTime.from() 需要一个 ZoneId 参数,但 Instant 没有这个参数。因此,调用 ZonedDateTime.from(instant) 会抛出异常。 - JodaStephen

16

assylias的答案JB Nizet的答案都是正确的:

  1. 调用添加到旧类中的新转换方法java.util.Date::toInstant
  2. 调用Instant::atZone,传递ZoneId,得到ZonedDateTime

enter image description here

但是你的代码示例针对季度。如果需要,请继续阅读。

季度

不需要自己编写和测试处理季度的代码。使用已经编写和测试过的类。

org.threeten.extra.YearQuarter

< p > < em > java.time 类由< em > ThreeTen-Extra 项目扩展。在该库中提供的许多方便类中,您会发现< code > Quarter < code > YearQuarter

首先获取您的< code > ZonedDateTime

ZonedId z = ZoneID.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = myJavaUtilDate.toInstant().atZone( z ) ;

确定特定日期的年份和季度。
YearQuarter yq = YearQuarter.from( zdt ) ;

接下来我们需要该季度的起始日期。

LocalDate quarterStart = yq.atDay( 1 ) ;

虽然我不一定推荐这样做,但你可以使用一行代码而不是实现一个方法。

LocalDate quarterStart =                    // Represent a date-only, without time-of-day and without time zone.
    YearQuarter                             // Represent a specific quarter using the ThreeTen-Extra class `org.threeten.extra.YearQuarter`. 
    .from(                                  // Given a moment, determine its year-quarter.
        myJavaUtilDate                      // Terrible legacy class `java.util.Date` represents a moment in UTC as a count of milliseconds since the epoch of 1970-01-01T00:00:00Z. Avoid using this class if at all possible.
        .toInstant()                        // New method on old class to convert from legacy to modern. `Instant` represents a moment in UTC as a count of nanoseconds since the epoch of 1970-01-01T00:00:00Z. 
        .atZone(                            // Adjust from UTC to the wall-clock time used by the people of a particular region (a time zone). Same moment, same point on the timeline, different wall-clock time.
            ZoneID.of( "Africa/Tunis" )     // Specify a time zone using proper `Continent/Region` format. Never use 2-4 letter pseudo-zone such as `PST` or `EST` or `IST`. 
        )                                   // Returns a `ZonedDateTime` object.
    )                                       // Returns a `YearQuarter` object.
    .atDay( 1 )                             // Returns a `LocalDate` object, the first day of the quarter. 
;

顺便说一下,如果你能完全淘汰使用java.util.Date的话,请这么做。它是一个可怕的类,以及它的姊妹类如Calendar。只在必要时使用Date,当你正在与尚未更新到java.time的旧代码进行接口时。

关于java.time

java.time框架内置于Java 8及更高版本中。这些类替代了老旧的legacy日期时间类,如java.util.DateCalendarSimpleDateFormat

欲了解更多信息,请参阅Oracle教程。在Stack Overflow上搜索可获得许多示例和解释。规范为JSR 310

Joda-Time项目现处于maintenance mode,建议迁移到java.time类。

您可以直接使用符合JDBC 4.2或更高版本的JDBC驱动程序与数据库交换java.time对象。无需字符串,也无需java.sql.*类。

在哪里获取java.time类?

ThreeTen-Extra项目通过增加额外的类来扩展java.time。该项目是java.time可能未来添加内容的试验场所。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuartermore

0

对于我在Java 10中存储util.Date的UTC,答案对我无效。

Date.toInstant()似乎将EpochMillis转换为服务器的本地时区。

ZDT.ofInstant(instant,zoneId)和instant.atZone(zoneId)似乎只是在瞬间上标记TZ,但它已经混乱了。

我找不到防止Date.toInstant()使用系统时区干扰UTC时间的方法。

我发现唯一的解决方法是通过sql.Timestamp类进行操作:

new java.sql.Timestamp(date.getTime()).toLocalDateTime()
                                      .atZone(ZoneId.of("UTC"))
                                      .withZoneSameInstant(desiredTZ)

不,Date::toInstant没有转换。java.util.Datejava.time.Instant都是基于UTC的,根据定义始终是UTC。 Date表示自1970-01-01T00:00:00Z纪元以来毫秒数的计数,而Instant也是自同一纪元以来的计数,但分辨率更精细,以纳秒为单位。 - Basil Bourque
在这里使用LocalDateTime是完全错误的。该类故意缺乏任何时区或UTC偏移量的概念。因此,它无法表示一个瞬间,正如其类JavaDoc中所述。转换为LocalDateTime会丢弃有价值的信息,没有任何收益。这个答案是误导和不正确的。请参见assylias的正确答案。从Date获取ZonedDateTime所需的所有内容都是:myJavaUtilDate.toInstant().atZone(desiredZoneIdGoesHere) - Basil Bourque
Basil,我完全同意你的期望,但遗憾的是我们在PST JVM中观察到Java 10对UTC日期的行为并非如此。我们只在这里使用LocalDateTime作为中间变量,以免在标记TZ之前弄乱UTC时间。 - TheArchitect
再也没有理由使用 java.sql.Timestamp 了,现在已被 java.sql.OffsetDateTime 取代 -- 自 JDBC 4.2 起,我们可以直接与数据库交换一些 java.time 类型。 - Basil Bourque
关于你的“PST JVM”问题... 你可以,也应该,使用 java.time 编写代码,而不依赖于你的JVM当前默认时区。相反,你通常应该使用UTC (java.time.InstantOffsetDateTime 设置为 ZoneOffset.UTC),并始终传递可选的 ZoneId/ZoneOffset。另一个问题是,永远不要使用伪时区“PST”。正确的时区名称采用 Continent/Region 格式,例如 America/Los_Angeles。请参阅维基百科列表 - Basil Bourque
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接