Timestamp.from 不注意 Instant 的时区

Timestamp.from not heeding timezone from Instant

当我尝试将 ZonedDateTime 转换为 Timestamp 时一切正常,直到我在以下代码中调用 Timestamp.from()

ZonedDateTime currentTimeUTC = ZonedDateTime.now(ZoneOffset.UTC);
currentTimeUTC = currentTimeUTC.minusSeconds(currentTimeUTC.getSecond());
currentTimeUTC = currentTimeUTC.minusNanos(currentTimeUTC.getNano());
return Timestamp.from(currentTimeUTC.toInstant());

ZonedDateTime.now(ZoneOffset.UTC); -> 2018-04-26T12:31Z
currentTimeUTC.toInstant() -> 2018-04-26T12:31:00Z
Timestamp.from(currentTimeUTC.toInstant()) -> 2018-04-26 14:31:00.0 
// (with Timezone of Europe/Berlin, which is currently +2)

为什么Timestamp.from()不注意即时设置的时区?

Instant 不保存时区信息。它只包含秒和纳米。 当您将 ZonedDateTime 转换为 Instant 时,信息会丢失。 转换为时间戳时,时间戳将保留默认时区,在您的情况下为 Europe/Berlin.

Instant class 没有时区,它只有自 unix 纪元以来的秒和纳秒值。 A Timestamp 也代表那个(纪元的计数)。

why is the debugger displaying this with a Z behind it?

问题出在 toString 方法中:

Instant.toString() 将秒和纳秒值转换为 UTC 中相应的 date/time - 因此最后是 "Z" - 我相信这样做是为了方便(为了使 API 更 "developer-friendly").

The javadoc for toString 说:

A string representation of this instant using ISO-8601 representation.
The format used is the same as DateTimeFormatter.ISO_INSTANT.

如果我们看一下 DateTimeFormatter.ISO_INSTANT javadoc:

The ISO instant formatter that formats or parses an instant in UTC, such as '2011-12-03T10:15:30Z'

由于调试器通常使用 toString 方法来显示变量值,这就是为什么您看到最后带有 "Z" 的 Instant,而不是 seconds/nanoseconds 的原因值。

另一方面,Timestamp.toString 使用 JVM 默认时区将 seconds/nanos 值转换为 date/time 字符串。

但是InstantTimestamp的值是一样的。您可以通过调用方法 Instant.toEpochMilliTimestamp.getTime 来检查两者是否会 return 相同的值。


注意:您可以使用 truncatedTo 方法代替调用 minusSecondsminusNanos

ZonedDateTime currentTimeUTC = ZonedDateTime.now(ZoneOffset.UTC);
currentTimeUTC = currentTimeUTC.truncatedTo(ChronoUnit.MINUTES);

这会将所有小于 ChronoUnit.MINUTES 的字段(在本例中为秒和纳秒)设置为零。

您也可以使用 withSecond(0)withNano(0),但在这种情况下,我认为 truncatedTo 更好,更直接。


注意 2:java.time API 的创建者还为 Java 6 和 7 以及项目的 github 问题 you can see a comment 做了一个反向移植关于 Instant.toString 的行为。这个问题的相关部分:

If we were really hard line, the toString of an Instant would simply be the number of seconds from 1970-01-01Z. We chose not to do that, and output a more friendly toString to aid developers

这强化了我的观点,即 toString 方法的设计是为了方便和易于使用。

tl;博士

  • 您对 Timestamp::toString 将 JVM 当前默认时区应用于对象内部 UTC 值的不幸行为感到困惑。
  • ➡ 使用Instant,从不使用Timestamp
  • 2018-04-26T12:31Z这样的字符串是标准的ISO 8601 format, with the Z being short for Zulu and meaning UTC

您的整个代码块可以替换为:

Instant.now()

……如:

myPreparedStatement.setObject( … , Instant.now() ) ;

详情

是正确的。 Instant 始终采用 UTC,Timestamp 也是如此,但 Timestamp::toString 应用时区。这种行为是那些有问题的遗留问题中许多糟糕的设计选择之一 classes.

我会补充一些其他想法。

对 UTC 使用 Instant

ZonedDateTime currentTimeUTC = ZonedDateTime.now(ZoneOffset.UTC);

虽然技术上是正确的,但这条线在语义上是错误的。如果您想用 UTC 表示时刻,请使用 Instant class。 Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds(最多九 (9) 位小数)。

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

避免遗留问题 Timestamp class

Timestamp.from(currentTimeUTC.toInstant());

虽然技术上是正确的,但使用我上面的建议,那将是:

Timestamp.from( instant ); // Convert from modern *java.time* class to troublesome legacy date-time class using new method added to the old class.

InstantTimestamp 之间不会丢失任何内容,因为两者都代表 UTC 中的一个时刻,分辨率为纳秒。然而……

根本不需要使用 java.sql.Timestamp! class 是现在遗留的麻烦的旧日期时间 classes 的一部分。它们完全被 JSR 310 定义的 java.time classes 取代。TimestampInstant 取代。

JDBC 4.2

从 JDBC 4.2 及更高版本开始,您可以直接与数据库交换 java.time 对象。

Insert/Update.

myPreparedStatement.setObject( … , instant ) ;

检索。

Instant instant = myResultSet.getObject( … , Instant.class ) ;

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

您可以直接与数据库交换 java.time 对象。使用 JDBC driver compliant with JDBC 4.2 或更高版本。不需要字符串,不需要 java.sql.* classes.

从哪里获得java.time classes?

  • Java SE 8, Java SE 9, Java SE 10,及以后
    • 内置。
    • 标准 Java API 的一部分,带有捆绑实施。
    • Java 9 添加了一些小功能和修复。
  • Java SE 6 and Java SE 7
  • Android
    • Android java.time classes.
    • 捆绑实施的更高版本
    • 对于较早的 Android (<26),ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See .

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.