首页 > 解决方案 > 在sql server中用UTC时间保存当前日期的问题

问题描述

我想在 sql server 2014 中使用时区保存日期UTC。我曾经ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("UTC"))使用 Hibernate 会话对象获取当前日期时间并将其保存到 db。在调试时我可以看到在 Java 程序中的日期是这样的2019-09-25T13:22:29.573Z[UTC],但是在保存到数据库列之后是这样的2019-09-25 18:53:23.3630000。它会根据系统时间自动转换时间部分。任何人都可以请建议是什么问题?我datetime2在数据库中创建此列时使用了数据类型。

标签: javahibernatesql-server-2014

解决方案


类型错误

您为列使用了错误的数据类型。

类型datetime2不能代表片刻。该类型仅存储日期和时间,但缺少时区或从 UTC 偏移的上下文。因此,如果您存储“2020 年 1 月 23 日中午”,我们无法知道您是指日本东京的中午、突尼斯突尼斯的中午,还是美国俄亥俄州托莱多的中午,三个不同的时刻相隔几个小时。这种错误类型类似于 SQL 标准类型TIMESTAMP WITHOUT TIME ZONE。Java 中的等价类是LocalDateTime.

如果您已经存储了数据,则需要重构您的数据库。基本上,添加一个正确类型的新列,为每一行复制数据,随时转换。当然,这仅在您知道每个保存的值中缺少的预期时区时才有效。

正确的类型

虽然我不使用 Microsoft SQL Server,但根据文档,您应该使用类型列datetimeoffset来记录 moment。此类型将任何提交的值调整为 UTC 以进行查询和排序,同时还记录偏移量。这种类型类似于 SQL 标准类型TIMESTAMP WITH TIME ZONE

通常最好将您的时刻存储在 UTC 中,偏移量为零时分秒。

该类Instant代表 UTC 中的一个时刻。

Instant instant = Instant.now() ;

如果你有一个ZonedDateTime,你可以提取一个Instant

Instant instant = zdt.toInstant() ;

我们想将其Instant直接保存到数据库中。不幸的是,JDBC 4.2 soec 不需要支持两个最常用的java.time类中的任何一个:InstantZonedDateTime. 该规范确实需要支持OffsetDateTime. 所以我们转换。

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ;

提交到数据库。

myPreparedStatement.setObject( … , odt ) ; 

取回。

OffsetDateTime odt = MyResultSet.getObject( … , OffsetDateTime.class ) ;

提取一个Instant以在您的代码中明确您需要 UTC。

Instant instant = odt.toInstant() ;

通过特定地区(时区)的人们使用的挂钟时间来查看这一刻。

ZoneId z = ZoneId.of( "Asia/Kolkata" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

Java(传统和现代)和标准 SQL 中的日期时间类型表。


推荐阅读