PostgreSQL中带/不带时区的时间戳之间的差异

Posted

技术标签:

【中文标题】PostgreSQL中带/不带时区的时间戳之间的差异【英文标题】:Difference between timestamps with/without time zone in PostgreSQL 【发布时间】:2011-08-18 02:05:20 【问题描述】:

当数据类型为WITH TIME ZONEWITHOUT TIME ZONE 时,时间戳值在PostgreSQL 中的存储方式是否不同?可以用简单的测试用例来说明差异吗?

【问题讨论】:

This related answer 可能会有所帮助。 【参考方案1】:

the PostgreSQL documentation for date/time types 介绍了这些差异。是的,TIMETIMESTAMP 的处理方式在 WITH TIME ZONEWITHOUT TIME ZONE 之间有所不同。它不影响值的存储方式;它会影响它们的解释方式。

时区对这些数据类型的影响是文档中的covered specifically。不同之处在于系统可以合理地了解该值:

将时区作为值的一部分,该值可以在客户端呈现为本地时间。

没有时区作为值的一部分,明显的默认时区是 UTC,因此它是针对该时区呈现的。

行为的不同取决于至少三个因素:

客户端中的时区设置。 值的数据类型(即WITH TIME ZONEWITHOUT TIME ZONE)。 是否使用特定时区指定值。

以下是涵盖这些因素组合的示例:

foo=> SET TIMEZONE TO 'Japan';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+09
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 06:00:00+09
(1 row)

foo=> SET TIMEZONE TO 'Australia/Melbourne';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+11
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 08:00:00+11
(1 row)

【讨论】:

只有在引用插入/检索值的过程时才更正。但是读者应该明白,两种数据类型,timestamp with time zonetimestamp without time zone,在 Postgres 中 *not 实际上存储时区信息。您可以通过查看数据类型文档页面来确认这一点:两种类型占用相同数量的八位字节并且具有值的保存范围,因此没有存储时区信息的空间。页面的文字证实了这一点。用词不当:“不带 tz”表示“插入数据时忽略偏移量”,“带 tz”表示“使用偏移量调整到 UTC”。 数据类型在第二种情况下是用词不当:他们说“时区”,但实际上我们谈论的是与 UTC/GMT 的偏移量。时区实际上是关于夏令时 (DST) 和其他异常情况的偏移规则/历史。 我宁愿说偏移量是一个时区加上 DST 的规则。您无法发现给定偏移量的时区,但您可以发现给定时区和 DST 规则的偏移量。 引用official doc :所有时区感知日期和时间都以UTC 格式在内部存储。在显示给客户端之前,它们会转换为 TimeZone 配置参数指定的区域中的本地时间。 @igorsantos07 时区关于 DST 更改和其他更改的规则/历史记录集。你的措辞似乎是多余的。而且您所说的“偏移量是时区加上 DST 规则”是完全错误的:偏移量只是小时、分钟和秒的数量——不多也不少。【参考方案2】:

我试图解释它比引用的 PostgreSQL 文档更容易理解。

TIMESTAMP 变体都不存储时区(或偏移量),尽管名称暗示了这些。区别在于对存储数据的解释(以及在预期的应用程序中),而不是存储格式本身:

TIMESTAMP WITHOUT TIME ZONE 存储 local 日期时间(又名挂历日期和挂钟时间)。就 PostgreSQL 而言,它的时区是未指定的(尽管您的应用程序可能知道它是什么)。因此,PostgreSQL 不会对输入或输出进行时区相关的转换。如果该值以'2011-07-01 06:30:30' 输入数据库,那么无论您稍后在哪个时区显示,它仍然会显示 2011 年 07 月 01 日、06 小时、30 分钟和 30 秒(在某些情况下)格式)。此外,您在输入中指定的任何偏移量或时区都会被 PostgreSQL 忽略,因此 '2011-07-01 06:30:30+00''2011-07-01 06:30:30+05''2011-07-01 06:30:30' 相同。 对于 Java 开发人员:类似于 java.time.LocalDateTime

TIMESTAMP WITH TIME ZONE 存储 UTC 时间线上的一个点。它的外观(多少小时、多少分钟等)取决于您的时区,但它始终指的是同一个“物理”时刻(如实际物理事件的时刻)。这 输入在内部转换为 UTC,这就是它的存储方式。为此,必须知道输入的偏移量,因此当输入不包含明确的偏移量或时区(如'2011-07-01 06:30:30')时,假定它位于 PostgreSQL 会话的当前时区,否则为明确指定的偏移量或时间使用区域(如'2011-07-01 06:30:30+05')。输出显示转换为 PostgreSQL 会话的当前时区。 对于 Java 开发人员:它类似于 java.time.Instant(虽然分辨率较低),但对于 JDBC 和 JPA 2.2,您应该将其映射到 java.time.OffsetDateTime(当然也可以映射到 java.util.Datejava.sql.Timestamp)。

有人说TIMESTAMP 变体都存储UTC 日期时间。有点,但在我看来,这样说会让人困惑。 TIMESTAMP WITHOUT TIME ZONETIMESTAMP WITH TIME ZONE 一样存储,使用 UTC 时区渲染恰好给出与本地日期时间相同的年、月、日、小时、分钟、秒和微秒。但这并不意味着代表 UTC 解释所说的时间线上的点,它只是本地日期时间字段的编码方式。 (它是时间线上的一些点簇,因为实际时区不是 UTC;我们不知道它是什么。)

【讨论】:

TIMESTAMP WITH TIME ZONE 检索为Instant 没有任何问题。两者都代表 UTC 时间线上的一个点。在我看来,InstantOffsetDateTime 更可取,因为它更具自我记录性:TIMESTAMP WITH TIME ZONE 始终从数据库中检索为 UTC,Instant 始终使用 UTC,因此很自然匹配,而OffsetDateTime 可以携带其他偏移量。 @BasilBourque 不幸的是,当前的 JDBC 规范、JPA 2.2 规范以及 PostgreSQL JDBC 文档只提到 OffsetDateTime 作为映射的 Java 类型。我不确定 Instance 是否仍然在某处得到非官方支持。 问题,您说我在输入中指定的任何偏移量,例如 '2011-07-01 06:30:30+00''2011-07-01 06:30:30+05' 都会被忽略,但我可以做到 insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz); 并且它会正确地将其转换为 utc。其中日期是没有时区的时间戳。我试图了解带时区的时间戳的主要价值是什么并且遇到了麻烦。 @pk1m 你用::timestamptz 把事情复杂化了。这样,您将字符串转换为TIMESTAMP WITH TIME ZONE,然后将其进一步转换为WITHOUT TIME ZONE,这将存储从您的会话时区(可能是世界标准时间)。它仍然只是具有未指定偏移量(无区域)的本地时间戳。 我认为这解释得更好更准确。我发现最受欢迎的答案令人困惑和误导。谢谢。【参考方案3】:

这是一个应该有帮助的例子。如果您有带有时区的时间戳,则可以将该时间戳转换为任何其他时区。如果您没有基本时区,则无法正确转换。

SELECT now(),
   now()::timestamp,
   now() AT TIME ZONE 'CST',
   now()::timestamp AT TIME ZONE 'CST'

输出:

-[ RECORD 1 ]---------------------------
now      | 2018-09-15 17:01:36.399357+03
now      | 2018-09-15 17:01:36.399357
timezone | 2018-09-15 08:01:36.399357
timezone | 2018-09-16 02:01:36.399357+03

【讨论】:

声明“将无法正确转换” 根本不正确。您必须了解timestamptimestamptz 的含义。 timestamptz 表示绝对时间点 (UTC),而 timestamp 表示时钟在某个时区显示的内容。因此,将timestamptz 转换为时区时,您是在问在这个绝对时间点,纽约的时钟显示了什么? 而在“转换”timestamp 时,您是在问纽约时钟显示 x 的绝对时间点是什么? AT TIME ZONE 结构本身就是一个脑筋急转弯,即使您已经了解 WITHWITHOUT TIME ZONE 类型。因此,解释它们是一个奇怪的选择。 (:(AT TIME ZONEWITH TIME ZONE 时间戳转换为 WITHOUT TIME ZONE 时间戳,反之亦然......不是很明显。) now()::timestamp AT TIME ZONE 'CST' 没有意义,除非您在“CST”区的时钟会在什么时刻显示您当地时钟当前显示的时间【参考方案4】:

时间戳与时间戳

Postgres 中的 timestamptz 字段基本上只是 Postgres 实际上存储“标准化”UTC 时间的时间戳字段,即使输入字符串中给出的时间戳具有时区。

如果你输入的字符串是: 2018-08-28T12:30:00+05:30 ,当这个时间戳存储在数据库中时,它将被存储为 2018-08-28T07:00:00。

与简单的时间戳字段相比,此方法的优势在于您对数据库的输入将与时区无关,并且在来自不同时区的应用插入时间戳或将数据库服务器位置移动到不同时区时不会不准确。

引用文档:

对于带时区的时间戳,内部存储的值始终在 UTC(通用协调时间,传统上称为格林威治标准时间) 时间,格林威治标准时间)。指定了明确时区的输入值是 使用该时区的适当偏移量转换为 UTC。如果 输入字符串中没有说明时区,则假定为 在系统的 TimeZone 参数指示的时区,并且是 使用时区的偏移量转换为 UTC。给一个 简单的类比,timestamptz 值代表时间的瞬间, 任何观看它的人都在同一时刻。但是时间戳值只是 表示时钟的特定方向,这将表示 基于您的时区的不同时间实例。

对于几乎任何用例,timestamptz 几乎总是一个更好的选择。由于 timestamptz 和 timestamp 占用相同的 8 字节数据,因此这种选择变得更加容易。

来源: https://hasura.io/blog/postgres-date-time-data-types-on-graphql-fd926e86ee87/

【讨论】:

【参考方案5】:

运行以下命令查看 pgAdmin 中的差异:

create table public.testts (tz timestamp with time zone, tnz timestamp without time zone);
insert into public.testts values(now(), now());
select * from public.testts;

如果您在 Angular / Typescript / Node API / PostgreSql 环境中遇到类似的时间戳精度问题,希望我的complete answer and solution 能帮到你。

【讨论】:

【参考方案6】:

差异显示在PostgreSQL official docs 中。请参阅文档以进行深入挖掘。

简而言之,TIMESTAMP WITHOUT TIME ZONE 不会保存任何与时区相关的信息,如果您提供带有时区信息的日期时间,它只需要日期和时间并忽略时区

例如

当我将这个 12:13, 11 June 2021 IST 保存到 PostgreSQL 时,TIMESTAMP WITHOUT TIME ZONE 将拒绝时区信息并保存日期时间 12:13,11 June 2021

TIMESTAMP WITH TIME ZONE 的情况下,它以UTC 格式保存时区信息。

例如

当我将这个 12:13, 11 June 2021 IST 保存到 PostgreSQL TIMESTAMP WITH TIME ZONE 类型变量时,它会将这个时间解释为 UTC 值和 存储如下6:43,11 June 2021 UTC

注意:UTC + 5.30 是 IST

TIMESTAMP WITH TIME ZONE返回的时间转换时间会以UTC格式存储,我们可以将其转换为所需的时区,如IST或PST等。

所以PostgreSQL中推荐的时间戳类型是TIMESTAMP WITH TIME ZONETIMESTAMPZ

【讨论】:

那么您的答案中有哪些新内容未被其他/已接受的答案所涵盖

以上是关于PostgreSQL中带/不带时区的时间戳之间的差异的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL 错误地从没有时区的时间戳转换为有时区的时间戳

PostgreSQL:在不同时区的时间戳中添加间隔

在带有/不带时区的日期或时间戳的查询中处理 generate_series()

postgreSQL 将列数据类型更改为没有时区的时间戳

PostgreSQL在没有时区的时间戳类型的表中搜索

错误:COALESCE 类型时间戳没有时区和整数无法匹配(Postgresql)