如何将ZonedDateTime转换为日期?

Mil*_*hev 83 java java.util.date datetime-conversion java-8 zoneddatetime

我试图在我的数据库中设置服务器不可知日期时间,我相信这样做的最佳做法是设置UTC日期时间.我的数据库服务器是Cassandra,Java的db驱动程序只能理解Date类型.

所以假设在我的代码中我使用新的Java 8 ZonedDateTime来获取UTC now(ZonedDateTime.now(ZoneOffset.UTC)),我如何将这个ZonedDateTime实例转换为"遗留"Date类?

Sli*_*idi 136

您可以将ZonedDateTime转换为瞬间,您可以直接使用Date.

Date.from(java.time.ZonedDateTime.now().toInstant());
Run Code Online (Sandbox Code Playgroud)

  • 不,它将是您的区域系统默认的当前日期. (18认同)
  • @MilenKovachev你的问题没有意义 - 日期没有时区 - 它只代表一个时刻. (6认同)
  • @David呃没有 - "日期"是自纪元以来的毫秒数 - 所以它与UTC有关.如果您打印***,将使用默认时区,但Date类不知道用户的时区...例如,请参阅https://docs.oracle.com中的"映射"部分/javase/tutorial/datetime/iso/legacy.html并且LocalDateTime明确地没有引用时区 - 这可能被视为令人困惑...... (4认同)
  • 您的答案不必要地涉及“ZonedDateTime”。`java.time.Instant` 类是 `java.util.Date` 的直接替代品,虽然 `Instant` 使用更精细的纳秒分辨率而不是毫秒,但两者都表示 UTC 中的一个时刻。`Date.from( Instant.now() )` 应该是你的解决方案。或者就此而言,只是具有相同效果的“new Date()”,以UTC捕获当前时刻。 (4认同)
  • 更改默认时区只应作为最后的手段.这样做会在运行时立即影响该JVM中所有应用程序的所有线程中的所有代码.不太好.当然没有必要解决这个问题. (2认同)
  • 作为一般最佳实践,这些服务器都应将其操作系统时间设置设置为UTC。是的,您应该在UTC中执行几乎所有的业务逻辑。但是,设置JVM的当前默认时区并不是这样做的方法。依靠JVM当前的默认值是有风险的,因为* any *时刻* any *应用程序的* any *线程中的* any *代码会在运行时改变您的代码!您处理日期时间值的所有代码都应**指定所需/期望的时区**,无论是UTC还是某个特定时区。 (2认同)
  • @assylias 实际上,您的陈述没有意义。存储时间的格式意味着一个时区。日期是基于 UTC 的,不幸的是,Java 做了一些愚蠢的事情并且不会这样对待它,而且最重要的是,将时间视为本地 TZ 而不是 UTC。Data,LocalDateTime,ZonedDateTime 存储数据的方式意味着一个时区。它们被使用的方式就好像 TZ 不存在一样,这完全是错误的。java.util.Date 隐含地具有 JVM 默认的 TZ。人们将它视为任何不同的东西(包括它的 java 文档!)这一事实只是坏人。 (2认同)

Bas*_*que 53

TL;博士

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)
Run Code Online (Sandbox Code Playgroud)

虽然上面的代码没有意义.双方java.util.DateInstant表示UTC片刻,总是在UTC.上面的代码具有相同的效果:

new java.util.Date()  // Capture current moment in UTC.
Run Code Online (Sandbox Code Playgroud)

使用没有任何好处ZonedDateTime.如果您已经拥有ZonedDateTime,请通过提取a来调整为UTC Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)
Run Code Online (Sandbox Code Playgroud)

其他答案正确

答案通过ssoltanid正确解决您的具体问题,如何在新的学校java.time对象(转换ZonedDateTime),以一个老派的java.util.Date对象.Instant从ZonedDateTime中提取并传递给java.util.Date.from().

数据丢失

请注意,您将遭受数据丢失,因为自纪元以来Instant追踪纳秒,而追踪自纪元以来的毫秒数.java.util.Date

图表比较了毫秒,微秒和纳秒的分辨率

您的问题和意见提出了其他问题.

保持服务器的UTC

您的服务器通常应将其主机操作系统设置为UTC作为最佳做法.在我所知道的Java实现中,JVM将此主机操作系统设置作为其默认时区.

指定时区

但是你永远不应该依赖JVM的当前默认时区.启动JVM时传递的标志可以设置另一个时区,而不是选择主机设置.更糟糕的是:任何时候任何应用程序的任何线程中的任何代码都可以调用java.util.TimeZone::setDefault在运行时更改该默认值!

卡桑德拉Timestamp类型

任何体面的数据库和驱动程序都应自动处理调整传递的UTC日期时间以进行存储.我不使用Cassandra,但似乎对日期时间有一些基本的支持.文档说它的Timestamp类型是相同纪元(1970年第一个UTC时刻)的毫秒数.

ISO 8601

此外,Cassandra接受ISO 8601标准格式的字符串输入.幸运的是,java.time使用ISO 8601格式作为解析/生成字符串的默认值.在Instant类的toString实现将很好地做.

精度:毫秒级与Nanosecord

但首先我们需要将ZonedDateTime的纳秒精度降低到毫秒.一种方法是使用毫秒创建一个新的Instant.幸运的是,java.time有一些方便的转换为毫秒和从毫秒转换的方法.

示例代码

以下是Java 8 Update 60中的一些示例代码.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z
Run Code Online (Sandbox Code Playgroud)

或者根据这个Cassandra Java驱动程序文档,您可以传递一个java.util.Date实例(不要混淆java.sqlDate).所以你可以instantTruncatedToMilliseconds在上面的代码中创建juDate .

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );
Run Code Online (Sandbox Code Playgroud)

如果经常这样做,你可以做一个单行.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );
Run Code Online (Sandbox Code Playgroud)

但是创建一个小实用方法会更简洁.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}
Run Code Online (Sandbox Code Playgroud)

请注意所有这些代码与问题中的区别.问题的代码试图将ZonedDateTime实例的时区调整为UTC.但这不是必要的.概念:

ZonedDateTime = Instant + ZoneId

我们只提取已经是UTC的Instant部分(基本上是UTC,阅读类doc以获得精确的细节).


关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

现在处于维护模式Joda-Time项目建议迁移到java.time类.

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

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

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.


Dav*_*son 5

如果您使用的是适用于 Android的ThreeTen backport并且不能使用较新的Date.from(Instant instant)(至少需要 API 26),您可以使用:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());
Run Code Online (Sandbox Code Playgroud)

或者:

Date date = DateTimeUtils.toDate(zdt.toInstant());
Run Code Online (Sandbox Code Playgroud)

另请阅读Basil Bourque 的回答中的建议


Ave*_*vec 5

接受的答案对我不起作用。返回的日期始终是本地日期,而不是原始时区的日期。我住在 UTC+2。

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 
Run Code Online (Sandbox Code Playgroud)

我想出了两种从 ZonedDateTime 获取正确日期的替代方法。

假设您有夏威夷的 ZonedDateTime

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10
Run Code Online (Sandbox Code Playgroud)

或按照最初要求的 UTC

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));
Run Code Online (Sandbox Code Playgroud)

替代方案1

我们可以使用java.sql.Timestamp。它很简单,但它也可能会损害你的编程完整性

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());
Run Code Online (Sandbox Code Playgroud)

替代方案2

我们从毫秒创建日期(之前已在此回答)。请注意,本地 ZoneOffset 是必须的。

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);
Run Code Online (Sandbox Code Playgroud)