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

时间:2016-01-21 00:14:25

标签: java postgresql time localization timestamp-with-timezone

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

环境如下:

  • OS:旧金山数字海洋Droplet上的CentOS
  • App Server:Apache Tomcat
  • 后端:Java - > Hibernate - >弹簧
  • DB:PostgreSQL

我解决的第一个问题是PostgreSQL默认时区在旧金山的服务器上设置为America/Eastern。所以我切换到UTC,但这只是问题的一部分,而且我并不是百分之百确定甚至做了什么。仅供参考,PostgreSQL中的专栏是TIMESTAMP WITH TIME ZONE

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

  • " 2016-01-20 13:45:19.894 MST"

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

  • " 2016-01-20 20:45:19.894Z"

First Test:-------------------------------------------- -

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

查询后的日期:

  • " 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似乎已经脱离了可疑列表。这意味着罪魁祸首可能是以下之一:

  • 爪哇
  • Tomcat的
  • CentOS的

我的计划是有条不紊地将服务器上的任何内容和所有内容更改为默认为UTC时区。有没有人知道这里发生了什么?我的计划是否将所有内容都默认为UTC甚至是个好主意?

2 个答案:

答案 0 :(得分:3)

让我们一块一块地把它拉开。

我不确定你的问题,但我怀疑你对Postgres在控制台会话中呈现日期时间值的误导行为感到困惑,如下所述。

UTC格式的Postgres商店

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

通过SQL控制台查询时,生成的用于表示TIMESTAMP WITH TIME ZONE中日期时间值的文本将使SQL会话的当前时区作为文本生成的一部分应用。这可能会产生误导,因为意味着存储的数据位于该时区。实际上,TIMESTAMP WITH TIME ZONE始终以UTC格式存储。在您的控制台中进行实验,亲眼看看,如下所示。请记住,您在下面看到的所有输出几乎都代表相同的时刻(我快速连续四次运行),但他们的wall-clock time却完全不同。

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

SELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.
  

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

尝试另一个时区,这个区域位于加拿大西部,Alberta,名为America/Edmonton,标准时间偏移-07:00,夏令时偏移-06:00。 / p>

SET TIME ZONE 'America/Edmonton'; -- Offset from UTC: -07:00
SELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.
  

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

在UTC之前尝试一个时区而不是在它后面。偏移量为+而不是-。另请注意,印度提前五个小时。并非所有时区的偏移都有全时小时的增量。因此,在此示例中,分钟数为47,而不是上面显示的17

SET TIME ZONE 'Asia/Kolkata';  -- Offset from UTC: +05:30
SELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.
  

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

最后,试试UTC。这意味着偏移量为+00:00,因为所有其他时区都是根据UTC定义的。有时你会看到ZZulu的缩写),这也意味着UTC。

SET TIME ZONE 'UTC';
SELECT now();  -- Or query your `TIMESTAMP WITH TIME ZONE` column.
  

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

JDBC

在JDBC中,您应该在getTimestamp上致电ResultSet以获得java.sql.Timestamp java.sql.Timestamp 总是在UTC

令人讨厌,令人困惑的是,它的toString方法在生成作为日期时间值的文本表示的String时,会静默应用JVM的当前默认时区。所以它似乎似乎就像它有一个时区,而实际上却没有。这些旧的日期时间类是善意的,但做了一些不幸的设计选择,如这种行为。另一个原因是尽快转换为java.time类型。

java.time

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

基本的java.time类型是...... Instant是UTC时间轴上的一个时刻。通过在java.sql.Timestamp上调用Instant获取toInstant。您的大多数工作都应该是UTC,因此通常使用Instant。当您需要在预期/期望的时区内向用户展示时,应用时区(ZoneId)以获得ZonedDateTime

明确指定时区

在所有java.time操作中,始终指定所需/预期的时区。如果省略,则以静默方式应用JVM的当前默认时区。为什么我说“当前”?因为JVM的默认时区可以随时更改,即使在运行时(!)也是如此。不要隐含地依赖默认值,要明确。同上Locale,但这是另一个讨论。

避免旧日期时间类

避免使用与早期Java版本捆绑在一起的旧java.util.Date/.Calendar类。众所周知,它们很麻烦。它们已被java.time类取代。

每件都是UTC

请注意,从Postgres中的TIMESTAMP WITH TIME ZONE到JDBC java.sql.Timestamp再到java.time Instant意味着一切都是UTC。数据库服务器硬件/操作系统的时区无关紧要。 SQL会话的时区无关紧要。 Java虚拟机的主机服务器硬件/操作系统上的时区无关紧要。 JVM中的当前默认时区无关紧要。

验证时钟设置正确

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

不,JDBC驱动程序应用区域

  

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

不,你的信念不正确。 Postgres-provided JDBC driver不适用JVM当前的java.sql.Timestamp默认时区。从存储的数据库日期时间值中的UTC转换为也是UTC的java.sql.Timestamp值时,此类应用程序将无关紧要。

使用适当的时区名称

使用proper time zone names。它们的格式为Continent/Region。切勿使用像MST这样的3-4个字母代码,因为它们既不标准也不唯一。如果您的意思是Mountain Standard Time,请使用America/EdmontonAmerica/Denver

Interactive SQL Session

在我看来,在Postgres中使用JDBC简化了日期时间处理。往返:

  

TIMESTAMP WITH TIME ZONEjava.sql.Timestamp↔java.time.Instant

...让一切变得简单,清晰,整洁。每一步都使用UTC。稍后在您的应用中,您可以根据需要调整时区。

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

如果您的任何offset-from-UTC中缺少日期时间字符串,那么Postgres会在转换为UTC以进行内部存储的过程中应用您的SQL会话的当前默认时区。我认为传递日期时间字符串没有任何偏移或时区是一个坏习惯。

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

以下示例显示了这两种行为。我致电SET TIME ZONE来明确current default time zone in this SQL session。时区America/Los_Angles在1月份的偏移量为-08:00。您可以在自己的数据库中方便地运行此代码,因为TEMP中的CREATE TEMP TABLE表示在SQL会话关闭时会自动删除该表。

请注意,在第二种情况下,小时为0412。这两个值 not 代表时间轴上的相同时刻;这些两个不同的时间点

SET TIME ZONE 'America/Los_Angeles';

DROP TABLE IF EXISTS some_table_ ;

CREATE TEMP TABLE IF NOT EXISTS some_table_  (
    "some_datetime_" TIMESTAMP WITH TIME ZONE NOT NULL
) ;

INSERT INTO some_table_
VALUES ( '2016-01-23 12:34:01' ); -- Lacking offset-from-UTC. *Not recommended*!

INSERT INTO some_table_
VALUES ( '2016-01-23 12:34:02Z' ); -- 'Z' means Zulu = UTC.

TABLE some_table_ ;
  

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

     

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

非常重要:请注意,上述行为是您的列数据类型为TIMESTAMP WITH TIME ZONE的时间。另一种类型TIMESTAMP WITHOUT TIME ZONE几乎应该never be used,因为它意味着“不做任何调整,忽略任何偏移或时区,只需要把这个日期和时间用于盲目地填入数据库”。

答案 1 :(得分:0)

天啊,我找到了一个解决方案。所以我相信PostgreSQL JDBC驱动程序正在尝试基于JVM来偏移UTC时间戳,JVM会询问服务器/操作系统的默认值。如果它不是JDBC而不是Java本身。由于客户端在一个时区,服务器在另一个时区,而日期为UTC,因此发生了一些真正的时髦,并且输入了错误的时间戳。所以我想我可以更改服务器默认时区,即America / NewYork为UTC。 / p>

本文向我展示了如何操作:https://chrisjean.com/change-timezone-in-centos/

只需运行这两个命令:

备份原始的本地时间文件:

  

sudo mv / etc / localtime /etc/localtime.bak

使用正确的时区创建一个新的:

  

sudo ln -s / usr / share / zoneinfo / UTC / etc / localtime

一周的战斗归结为两个命令:/ 希望这是一个真正的解决方案,其他人可以从中获得一些价值。