调整JPA中的Postgres不带时区的时间戳到服务器时区

huangapple go评论89阅读模式
英文:

JPA adjust Postgres timestamp without timezone to server timezone

问题

我遇到了一些我无法理解的JPA行为。

  1. 我正在将特定的日期时间(例如2023-01-01T12:00:00Z,这是Instant)保存到不带时区的Postgresql Timestamp中。
  2. 我读取这个值并收到相同的日期时间:2023-01-01T12:00:00Z,这是预期的。
  3. 我将服务器时区从+0更改为+1,现在,当我启动应用程序时,它从数据库中读取的是2023-01-01T11:00:00,尽管在数据库中此列没有时区指示符。为什么JPA会调整这个时间?

谢谢任何想法。

英文:

I meet some JPA behavior I cannot understand.

  1. I'm saving specific dateTime ex. 2023-01-01T12:00:00Z (this is Instant) to Postgresql Timestamp without timezone.
  2. I'm reading this value and receive the same date: 2023-01-01T12:00:00Z which is expected.
  3. I change server timezone from +0 to +1 and now, when I start application, it reads from db: 2023-01-01T11:00:00 despite, in db this column has no timezone indicator. Why JPA adjust this time?

Thanks for any ideas

答案1

得分: 0

配置数据库时不会存储任何时区信息,可以将其视为仅存储本地日期时间。由客户端应用程序(例如您的JDBC驱动程序)在提取数据时定义时区。

因此,如果您配置JDBC驱动程序在存储日期时间和提取相同日期时间时具有不同的时区设置,将会发生时区偏移行为。

JDBC驱动程序的时区基本上取决于以下优先级。如果您没有显式配置高优先级项目的时区,则会默认为较低优先级项目。

  1. JDBC连接URL中的时区
  2. JVM时区
  3. 操作系统时区

Hibernate甚至提供了一种通过属性hibernate.jdbc.time_zone来配置时区的方法,其优先级甚至高于JDBC连接URL(详见此处)。

因此,我认为您的情况可以通过以下方式解释。即使您存储了2023-01-01T12:00:00 UTC

  1. 数据库将仅将其存储为本地日期时间,即2023-01-01 12:00:00
  2. 由于您将应用服务器的时区更改为UTC+1,在从数据库中提取数据时,您的Java应用程序会将其解释为2023-01-01 12:00:00 UTC+1
  3. 由于Java的Instant是基于UTC时区表示的,因此您会看到它为2023-01-01 11:00:00 UTC(注意:2023-01-01 12:00:00 UTC+1 = 2023-01-01 11:00:00 UTC)。

因此,如果您正在使用Hibernate,我建议配置hibernate.jdbc.time_zoneUTC,这与存储在数据库中的日期时间的预期时区一致。这样可以使您的应用程序具有更可预测的时区行为,不受JVM或服务器操作系统的时区设置的影响。

顺便说一下,这更多涉及JDBC驱动程序调整时间而不是JPA。即使您不使用JPA,也应该获得相同的行为。

英文:

As you configure DB will not store any timezone for that date time , you can think it just store it as a local date time. It is up to the client application (i.e JDBC driver in your case) to define its timezone when fetching it.

So if you configure the JDBC driver to have different timezones when storing a date time and fetching the same date time , such time shifting behaviour will happen.

The timezone of the JDBC driver is basically depending by the following priority . If you do not explicitly configure the timezone of the high priority items , it will then default to the lower priority items.

  1. Timezone in JDBC connection url
  2. JVM timezone
  3. OS timezone

Hibernate even provide a way to configure the timezone which have even higher priority than JDBC connection url through the property hibernate.jdbc.time_zone (see this for detail)

So I believe your case can be explained by the followings. Even though you store 2023-01-01T12:00:00 UTC :

  1. DB will store it as local date time only , that is 2023-01-01 12:00:00
  2. Since you change the timezone of the app server to UTC+1 , your Java app interprets it as 2023-01-01 12:00:00 UTC+1 when fetching it from DB.
  3. Since Java Instant is represented based on UTC timezone , you then see it as 2023-01-01 11:00:00 UTC (Note : 2023-01-01 12:00:00 UTC+1 = 2023-01-01 11:00:00 UTC )

So if you are using hibernate , I would suggest to configure hibernate.jdbc.time_zone to UTC which aligns with the expected timezone of the date time storing in DB. It makes your app have more deterministic timezone behaviour which will not affected by the timezone setting of JVM or server 's OS.

By the way , it is more about the JDBC driver that adjust the time but not JPA. You should get the same behaviour even you do not use JPA.

答案2

得分: 0

错误的列类型在您的表中

您为列选择了错误的数据类型。类型TIMESTAMP WITHOUT TIME ZONE不能表示一个瞬间,时间轴上的特定点。该类型仅存储日期和时间,例如明年1月23日中午。但我们无法知道这是否意味着东京的中午、图卢兹的中午还是托莱多的中午——这是三个相距数小时的完全不同的时刻。

您输入字符串末尾的Z表示与协调世界时(UTC)相差零小时、零分钟、零秒的偏移量。这个偏移量与日期和时间结合在一起,确定了一个时刻。

要存储该时刻,您需要将列定义为TIMESTAMP WITH TIME ZONE

这在Stack Overflow上已经多次讨论过,包括一些我写的答案。搜索以了解更多信息,例如使用Java类型LocalDateTime和一个列类型以及OffsetDateTime和另一个列类型。

如果使用正确的类型,所有问题都将消失。而且存储在数据库中的数据将变得有意义,而不是含糊不清。

写入:

OffsetDateTime odt = myInstant.atOffset(ZoneOffset.UTC);
myPreparedStatement.setObject(  , odt ) ;

阅读:

OffsetDateTime odt = myResultSet.getObject(  , OffsetDateTime.class ) ;  // 访问TIMESTAMP WITH TIME ZONE类型的列。 
Instant instant = odt.toInstant() ;

您的工具可能欺骗您

请了解,Postgres将所有TIMESTAMP WITH TIME ZONE值存储为与协调世界时(UTC)相差零小时、零分钟、零秒的偏移量。任何与输入一起提交的区域或偏移信息都用于将输入调整为UTC。

因此,从TIMESTAMP WITH TIME ZONE列检索到的值始终是UTC时间。但是,中间件、驱动程序和工具可能(不幸地)选择对检索到的值应用区域或偏移,从而传达了错误的信息。

英文:

Wrong column type in your table

>I'm saving specific dateTime ex. 2023-01-01T12:00:00Z (this is Instant) to Postgresql Timestamp without timezone.

You have chosen the wrong data type for your column. The type TIMESTAMP WITHOUT TIME ZONE cannot represent a moment, a specific point on the timeline. That type stores only a date with time-of-day, such as noon on Jan 23rd next year. But we have no way to know if that was meant to be noon in Tokyo, noon in Toulouse, or noon in Toledo — three very different moments, several hours apart.

The Z at the end of your input string means an offset of zero hours-minutes-seconds from UTC. That, combined with a date and time, determines a moment.

To store that moment you need to define your column as TIMESTAMP WITH TIME ZONE.

This has been covered many many times already on Stack Overflow, with many existing Answers including some written by me. Search to learn more such as using the Java type LocalDateTime with one column type and OffsetDateTime with the other column type.

If you use the correct type, all your problems will vanish. And the data stored in your database will be meaningful rather than ambiguous.

Writing:

OffsetDateTime odt = myInstant.atOffset( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;

Reading:

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;  // Accessing a column of type TIMESTAMP WITH TIME ZONE. 
Instant instant = odt.toInstant() ;

Your tools may lie to you

Understand that Postgres stores all TIMESTAMP WITH TIME ZONE values with an offset from UTC of zero hours-minutes-seconds. Any zone or offset info submitted with an input is used to adjust that input to UTC.

So, values retrieved from a TIMESTAMP WITH TIME ZONE column are always in UTC. However, middleware, drivers, and tooling may (unfortunately) choose to apply a zone or offset to retrieved values, thereby telling a lie.

huangapple
  • 本文由 发表于 2023年7月20日 21:18:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76730330.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定