如何将java.sql.Timestamp转换为java.time.OffsetDateTime?

时间:2017-04-04 20:07:58

标签: java scala java-8 slick

我正在研究Scala项目,我需要将OffsetDateTime类型映射到SQL Timestamp类型。在DB中我希望有UTC时间。

OffsetDateTimeTimestamp的转换很简单(来自this question的提示)并且按预期工作:

import java.time._
import java.sql.Timestamp
val ofsdatetime = OffsetDateTime.now()
// ofsdatetime: java.time.OffsetDateTime = 2017-04-04T21:46:33.567+02:00

val tstamp = Timestamp.valueOf(ofsdatetime.atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime())
// tstamp: java.sql.Timestamp = 2017-04-04 19:46:33.567

正如您所看到的,时区被移除,时间戳又回到了两个小时(UTC),太棒了!

Timestamp转换回OffsetDateTime 未按预期工作

OffsetDateTime.ofInstant(Instant.ofEpochMilli(tstamp.getTime), ZoneId.systemDefault())

// java.time.OffsetDateTime = 2017-04-04T19:46:33.567+02:00

时区已被添加到新创建的OffsetDateTime,但时间不正确(它仍然是UTC,我需要它适应实际时区)。

为什么呢?我做错了什么?

2 个答案:

答案 0 :(得分:10)

虽然java.sql.Timestamp存储了纪元毫秒,但.toString方法使用默认时区来渲染字符串。此外,.valueOf使用您的默认时区解释LocalDateTime

两者结合,导致第一次转换为"外观"是的,但事实上是错误的。价值" 2017-04-04 19:46:33.567"正在显示在您的默认TZ中,而不是UTC。

因为您通过了valueOf方法LocalDateTime(UTC),但它将其解释为LocalDateTime(您的默认TZ)。

以下是第一次转换错误的证明:

scala> val now = OffsetDateTime.now
now: java.time.OffsetDateTime = 2017-04-04T14:50:12.534-06:00

scala> Timestamp.valueOf(now.atZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime).getTime == now.toInstant.toEpochMilli
res54: Boolean = false

现在删除了.atZoneSameInstant

scala> Timestamp.valueOf(now.toLocalDateTime).getTime == now.toInstant.toEpochMilli
res53: Boolean = true

引用的stackoverflow问题的接受答案是错误的。

修复第一次转换后(删除.atZoneSameInstant),您的第二次转换应该可以正常工作。

答案 1 :(得分:6)

java.sql.Timestamp是一个long值的精简包装,表示自纪元(1970-01-01T00:00:00.000 UTC)以来的毫秒数 - 因此UTC时区隐含在java.sql.Timestamp中。它不能存储任何时区信息,但隐含在UTC中,并且只要每个人都知道,这一切都有效。无法在java.sql.Timestamp中存储时区信息。如果您需要记住输入数据中收到的时区,请将其另存为数据库中的单独列。您可以在java.sql.Timestamp中保存正确的时刻 - 但不能在输入数据中收到时区。为此你需要一个额外的领域。

由于您希望数据库日期为UTC,因此您可以从DB中检索数据:OffsetDateTime.ofInstant(Instant.ofEpochMilli(tstamp.getTime), ZoneId.of("UTC"))。这将是正确的时间点,但在UTC时区。在将OffsetDateTime保存到数据库之前,您无法从数据库中检索+0200java.sql.Timestamp时区中的事实,因为return list.stream() .collect(Collectors.groupingBy(e -> e, Collectors.counting())) .entrySet() .stream() .filter(e -> e.getValue() == 1) .map(Map.Entry::getKey) .collect(Collectors.toList()); 不存储时区组件。如果您需要该信息,则需要将其存储在数据库的单独列中。