我正在研究Scala项目,我需要将OffsetDateTime
类型映射到SQL Timestamp
类型。在DB中我希望有UTC时间。
从OffsetDateTime
到Timestamp
的转换很简单(来自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,我需要它适应实际时区)。
为什么呢?我做错了什么?
答案 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
保存到数据库之前,您无法从数据库中检索+0200
在java.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());
不存储时区组件。如果您需要该信息,则需要将其存储在数据库的单独列中。