时间戳、时区、区域设置和服务器

mr *_*ven 2 java postgresql time localization timestamp-with-timezone

我的头开始因为时间戳和时区问题而旋转。在我的应用程序中,所有时间戳都保存为 UTC。提交时区单独保存。

环境如下:

  • 操作系统:旧金山 Digital Ocean Droplet 上的 CentOS
  • 应用服务器:Apache Tomcat
  • 后端:Java -> Hibernate -> Spring
  • 数据库:PostgreSQL

我解决的第一个问题是 PostgreSQL 默认时区设置America/Eastern在旧金山的服务器上。所以我改用了 UTC,但这只是问题的一部分,我不能 100% 确定它做了什么。仅供参考,PostgreSQL 中的列是TIMESTAMP WITH TIME ZONE.

测试的本地化日期,即 MST 或 UTC-07:

  • “2016-01-20 13:45:19.894 MST”

所有测试的插入中使用的 UTC 日期:

  • “2016-01-20 20:45:19.894Z”

第一次测试:--------------------------------------------------------

通过快速 SQL 插入,将输入正确的日期。当我查询时,我得到本地化日期,因为我的本地系统将其与 MST 偏移 -7 小时。

查询后日期:

  • “2016-01-20 13:45:19.894”

这确实是我本地插入的时间。

第二次测试:--------------------------------------------------------

这就是奇怪的地方。现在,我使用与 SQL 插入中相同的 UTC 时间戳通过应用程序输入日期。

现在看看我查询时会出现什么结果:

  • “2016-01-20 18:45:19.0”

如果使用原始 SQL 插入,则日期减去 7 小时即可得到本地时间。如果我的本地计算机从原始 UTC 时间戳中减去 7,则应用程序返回的原始 UTC 时间戳将加上 7 小时,得出:

  • “2016-01-21 01:45:19.0”

而不是我放进去的。呵呵。。。?

由于 SQL 插入成功且时区更改为 UTC,PostgreSQL 似乎已从可疑列表中删除。这意味着罪魁祸首可能是以下之一:

  • 爪哇
  • 雄猫
  • 中央操作系统

我的计划是有条不紊地将服务器上的所有内容更改为默认 UTC 时区。有谁知道这里发生了什么?我的计划将所有内容默认为 UTC 是一个好主意吗?

Bas*_*que 5

让我们把它一点一点地拆开。

\n\n

我不确定您的问题,但我怀疑您对 Postgres\xe2\x80\x99 在控制台会话中显示日期时间值的误导行为感到困惑,如下所述。

\n\n

UTC 中的 Postgres 商店

\n\n

在 Postgres 中,你应该使用TIMESTAMP WITH TIME ZONE,几乎从不使用TIMESTAMP WITHOUT TIME ZONE。仔细研究Postgres 文档。对于该WITH类型,Postgres 使用传入数据提供的任何时区或相对于 UTC 的偏移量来将给定日期时间调整为 UTC。Postgres始终TIMESTAMP WITH TIME ZONE以 UTC存储。传递的时区或偏移信息在用于调整 UTC 后会被忘记。此行为可能与其他数据库系统不同;据我所知 SQL 规范没有指定此类细节。

\n\n

通过 SQL 控制台查询时,生成的用于表示日期时间值的文本TIMESTAMP WITH TIME ZONE将应用 SQL session\xe2\x80\x99s 当前时区作为文本生成的一部分。这可能会产生误导,因为它并不意味着存储的数据位于该时区。实际上,TIMESTAMP WITH TIME ZONE始终以 UTC 存储。在控制台中进行实验,亲自查看,如下所示。请记住,您在下面看到的所有输出实际上代表同一时刻(我快速连续运行了所有四个输出),但它们的挂钟时间却截然不同。

\n\n

首先使用为交互式 SQL 会话建立的默认时区。对于我来说,我的开发工作站位于America/Los_Angeles(美国西海岸),与UTC 的偏移量-08:00

\n\n
SELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

2016-01-27 12:16:35.681057-08

\n
\n\n

尝试另一个时区,该时区位于加拿大西部,位于艾伯塔省,名为,标准时间和夏令时America/Edmonton的偏移量为。-07:00-06:00

\n\n
SET TIME ZONE \'America/Edmonton\'; -- Offset from UTC: -07:00\nSELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

2016-01-27 13:17:01.502281-07

\n
\n\n

尝试使用早于 UTC 的时区,而不是晚于 UTC 的时区。偏移量是+而不是-。另请注意,印度比印度早五个半小时。并非所有时区的偏移量都有整小时增量。因此,在这个例子中,每小时的分钟数47不是17上面看到的。

\n\n
SET TIME ZONE \'Asia/Kolkata\';  -- Offset from UTC: +05:30\nSELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

2016-01-28 01:47:28.95779+05:30

\n
\n\n

最后,尝试UTC。这意味着 的偏移量+00:00,因为所有其他时区都是根据 UTC 定义的。有时您会看到Z( 的缩写Zulu),它也表示 UTC。

\n\n
SET TIME ZONE \'UTC\';\nSELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

2016-01-27 20:17:50.86757+00

\n
\n\n

数据库连接

\n\n

在 JDBC 中,您应该调用getTimestamp您的ResultSet来获取java.sql.Timestamp. A始终java.sql.Timestamp采用UTC 格式

\n\n

令人烦恼且令人困惑的是,它的toString方法在生成作为日期时间值的文本表示形式的字符串时,会默默地应用 JVM\xe2\x80\x99s 当前的默认时区。因此,它看起来可能有时区,但实际上没有。这些旧的日期时间类本意是好的,但做出了一些不幸的设计选择,例如这种行为。尽快转换为 java.time 类型的另一个原因。

\n\n

java.time

\n\n

您应该将 java.sql 类型转换为 Java 8 及更高版本中内置的 java.time 类型。然后继续您的业务逻辑。

\n\n

基本的 java.time 类型为\xe2\x80\xa6 AnInstant是 UTC 时间轴上的时刻。Instant通过调用toInstantjava.sql.Timestamp获取时间戳。您的大部分工作应该采用 UTC,因此经常使用Instant. 应用时区 ( ZoneId) 来获取ZonedDateTime何时需要向用户展示他们预期/所需时区的内容。

\n\n

明确指定时区

\n\n

在所有 java.time 操作中,始终指定所需/预期的时区。如果省略,则静默应用 JVM\xe2\x80\x99s 当前默认时区。为什么我说\xe2\x80\x9ccurrent\xe2\x80\x9d?因为您的 JVM\xe2\x80\x99s 默认时区可以随时更改,甚至在运行时(!)。不要隐式地依赖默认值,要明确地依赖。同上Locale,但那是另一个讨论。

\n\n

避免旧的日期时间类

\n\n

避免与早期版本的 Java 捆绑在一起的旧 java.util.Date/.Calendar 类。他们是出了名的麻烦。它们已被 java.time 类取代。

\n\n

每件作品均以 UTC 时间为准

\n\n

请注意,从TIMESTAMP WITH TIME ZONEPostgres 中通过 JDBC 到java.sql.Timestampjava.timeInstant意味着一切都采用 UTC。数据库服务器硬件/操作系统的时区并不重要。SQL 会话的时区并不重要。Java 虚拟机\xe2\x80\x99s 主机服务器硬件/操作系统上的时区并不重要。JVM 中当前的默认时区并不重要。

\n\n

验证时钟设置是否正确

\n\n

重要的是您验证数据库服务器和 JVM 主机上的硬件时钟是否正确,并设置为适合其指定时区的正确时间。虽然将所有服务器\xe2\x80\x99 操作系统(以及 Postgres 和服务器 JVM)设置为 UTC 通常是最佳实践,但考虑到上述情况,这不是必需的。

\n\n

否,JDBC 驱动程序不适用区域

\n\n
\n

我相信 PostgreSQL JDBC 驱动程序正在尝试根据 JVM 来偏移 UTC 时间戳,该 JVM 会向服务器/操作系统询问其默认值。

\n
\n\n

不,你的信念不正确。Postgres 提供的 JDBC 驱动程序不应用 JVM\xe2\x80\x99s 当前默认时区java.sql.Timestamp。当从存储的数据库日期时间值中的 UTC 转换为同样采用 UTC 格式的 java.sql.Timestamp 值时,此类应用程序是无关的。

\n\n

使用正确的时区名称

\n\n

使用正确的时区名称。它们的格式为Continent/Region. 切勿使用 3-4 个字母的代码,因为MST它们既不是标准化的也不是唯一的。如果您指的是山区标准时间,请使用类似America/Edmonton或 的内容America/Denver

\n\n

交互式 SQL 会话

\n\n

在我看来,将 JDBC 与 Postgres 结合使用可以简化日期时间处理。搬家和搬家地点:

\n\n
\n

TIMESTAMP WITH TIME ZONE\xe2\x86\x94 java.sql.Timestamp\xe2\x86\x94 java.time.Instant

\n
\n\n

\xe2\x80\xa6 使一切保持简单、清晰和整洁。每个步骤都使用 UTC。稍后在您的应用程序中,您可以根据需要调整时区。

\n\n

但是 Postgres 的其他日期时间字符串输入/输出怎么样,例如 pgAdmin 中的交互式 SQL 会话?

\n\n

如果您的日期时间字符串缺少任何offset-from-UTC,则 Postgres 在转换为 UTC 进行内部存储的过程中会应用您的 SQL 会话\xe2\x80\x99s 当前默认时区。我认为传递没有任何偏移量或时区的日期时间字符串是一种不好的做法。

\n\n

如果您的日期字符串包含相对于 UTC 的偏移量,那么 Postgres 会使用该信息来调整 UTC 以进行内部存储。

\n\n

以下示例显示了这两种行为。我调用SET TIME ZONESQL 会话中的显式当前默认时区。时区有一月America/Los_Angles的偏移量。-08:00您可以方便地在自己的数据库中运行此代码,因为TEMPinCREATE TEMP TABLE意味着当 SQL 会话关闭时该表会自动删除。

\n\n

请注意在第二种情况下,小时与04的关系12。这两个值并不代表时间轴上的同一时刻;这是两个不同的时间点

\n\n
SET TIME ZONE \'America/Los_Angeles\';\n\nDROP TABLE IF EXISTS some_table_ ;\n\nCREATE TEMP TABLE IF NOT EXISTS some_table_  (\n    "some_datetime_" TIMESTAMP WITH TIME ZONE NOT NULL\n) ;\n\nINSERT INTO some_table_\nVALUES ( \'2016-01-23 12:34:01\' ); -- Lacking offset-from-UTC. *Not recommended*!\n\nINSERT INTO some_table_\nVALUES ( \'2016-01-23 12:34:02Z\' ); -- \'Z\' means Zulu = UTC.\n\nTABLE some_table_ ;\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

2016-01-23 12:34:01-08

\n\n

2016-01-23 04:34:02-08

\n
\n\n

非常重要:请清楚,上述行为是当您的列数据类型为TIMESTAMP WITH TIME ZONE. 另一种类型,TIMESTAMP WITHOUT TIME ZONE,几乎不应该使用,因为它意味着 \xe2\x80\x9c 不进行任何调整,忽略任何偏移或时区,只需获取这个日期和这个时间并将其盲目地填充到数据库 \xe2\x80\x9d 中。

\n