2

我更喜欢所有时间都在 UTC,除了它们显示的时候。在最后一刻,它们可以转换为本地时间,仅用于显示。

我以前总是在 MySQL 中存储 unix 时间戳(自纪元以来的秒数),在整数字段中。我正在使用其他人的数据库架构,我无法更改它使用DATETIME存储原始时间甚至没有偏移量的数据库架构。

如何使用 UTC 格式的数据库检索和发送时间对象,因此数据库绝对不进行任何转换?

虽然是为 PostgreSQL 编写的,但我尝试遵循 Basil Bourque 在以下问题上的出色回答。我Instant在任何地方都使用,除非他显示我OffsetDateTime在与 JDBC 交互时使用 an,因为它是唯一保证受 JDBC 4.2 支持的现代 moment 类。

我正在使用 MySql Connector/J 8.0.27,并且正在使用&preserveInstants=false&connectionTimeZone=UTC.

让我们看一下DATETIME数据库中存储为“2022-01-14 11:00:00”的 a,它被理解为 UTC。

resultSet.getString(columnName); // "2022-01-14 11:00:00"
resultSet.getObject(columnName, OffsetDateTime.class).toString() // "2022-01-14T11:00-04:00"
resultSet.getObject(columnName, OffsetDateTime.class).toInstant().toString() // "2022-01-14T15:00:00Z"

当它创建时OffsetDateTime,它分配了“-04:00”的系统或服务器(同一台机器)偏移量并搞砸了一切。我认为我的连接选项&preserveInstants=false&connectionTimeZone=UTC应该已经停止了。

我知道我可以立即将偏移量设置为“+00:00” .withOffsetSameLocal(ZoneOffset.UTC).toInstant(),但是我如何避免这样做并让它直接创建一个OffsetDateTimeUTC 格式的?

4

1 回答 1

2

TIMESTAMP WITHOUT TIME ZONE

您已经确定了核心问题:您的表的作者将该列定义为 MySQLDATETIME类型。该类型仅表示具有时间的日期,但缺少时区或偏移量的上下文。该 MySQLDATETIME列类似于 SQL 标准类型的TIMESTAMP WITHOUT TIME ZONE.

java.time.LocalDateTime

DATETIME因此,您在从该列检索值时使用了错误的 Java 类。OffsetDateTime与其使用,不如使用LocalDateTime。与DATETIME( 和TIMESTAMP WITHOUT TIME ZONE) 一样,Java 类型LocalDateTime表示具有时间但没有区域或偏移量的日期。

LocalDateTime ldt = myResultSet.getObject( … , LocalDateTime.class ) ;

确定片刻

您可以分配一个偏移量或时区来精确定位一个时刻,即时间线上的一个点。

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

或者:

ZoneId z = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ;

你说:

它分配了“-04:00”的系统或服务器(同一台机器)偏移量并将一切搞砸了。

遍历路线上的某些部分试图帮助您实现代码的意图,即从仅日期和时间的值开始,并以分配的偏移量结束。很可能您的 JDBC 驱动程序是分配偏移量的部分。由于 MySQL 没有存储偏移量DATETIME,因此需要分配一些OffsetDateTime偏移量才能生成您请求的对象。

如上所示,更好的方法是在使用缺少偏移量的数据库列时不请求对象。OffsetDateTime

您问:

如何使用 UTC 格式的数据库检索和发送时间对象,因此数据库绝对不进行任何转换?

您不能在 UTC 中看到的时刻与缺少从 UTC 偏移的概念的数据库列交换。

如果要在 SQL 数据库中表示时刻,则必须使用适当的数据类型,类似于 SQL 标准类型的类型TIMESTAMP WITH TIME ZONE

您在将带有偏移量的日期时间值与仅日期时间的数据库列交换时所要求的就像要求将日元价格存储在单纯的数字列中一样。您可以假装这些记录值代表日元价格,但您不知道。有人也可以在那里以欧元或比索存储价格。

由于您似乎对当前的工作情况感到困惑,您可以假装您的TIMESTAMP WITHOUT TIME ZONE 列存储 UTC 值。本质上,这就是我上面的代码示例所做的。

要在写入值时假装,请调整为 UTC,然后提取LocalDateTime.

ZonedDateTime zdt = ZonedDateTime.now( z ) ;
OffsetDateTime odt = zdt.withOffsetSameInstant( ZoneOffset.UTC ) ; 
LocalDateTime ldt = odt.toLocalDateTime() ;
myPreparedStatement.setObject( … , ldt ) ;

这种伪装的做法充满了危险,而且是不负责任的。该数据库正在跟踪虚构,而不是真相。请务必记录此 hack,因为任何刚接触该系统的程序员都会被彻底搞糊涂。

顺便说一下,请注意类型名称TIMESTAMP WITH TIME ZONETIMESTAMP WITHOUT TIME ZONE用词不当。根据我被告知的情况以及我在草稿中看到的情况,SQL 标准实际上只意味着偏移量,而不是真正的时区。这就是为什么OffsetDateTime在 JDBC 中映射的唯一 Java 类与TIMESTAMP WITH TIME ZONE.

于 2022-01-15T16:56:58.573 回答