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


Answers:


155

PostgreSQL文档中涵盖了日期/时间类型的差异。是的,治疗TIMETIMESTAMP一个之间的不同WITH TIME ZONEWITHOUT TIME ZONE。它不会影响值的存储方式。它影响它们的解释方式。

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

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

  • 如果没有时区作为值的一部分,则明显的默认时区为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)

88
仅在引用插入/检索值的过程时更正。但是读者应该理解,Postgres 中的两种数据类型timestamp with time zonetimestamp without time zone没有实际存储时区信息。您可以在数据类型doc页面上一目了然地确认这一点:两种类型占用相同数量的八位位组,并且具有值的保存范围,因此没有空间存储时区信息。页面文本确认了这一点。误称:“无tz”表示“在插入数据时忽略偏移量”,“有tz”表示“使用偏移量调整到UTC”。
罗勒·布尔克2014年

41
数据类型在第二种说法中使用不当:它们说“时区”,但实际上我们是在谈论与UTC / GMT的偏差。时区实际上是一个偏移量以及有关夏令时(DST)和其他异常的规则/历史记录。
罗勒·布尔克

4
我宁愿说偏移量是时区加上DST的规则。您无法找到给定偏移量的时区,但是可以找到给定时区和DST规则的偏移量。
igorsantos15年

3
引用官方文档所有时区感知日期和时间都内部存储在UTC中。在将它们显示给客户端之前,它们会在TimeZone配置参数指定的区域中转换为本地时间。
Guillaume Husta

2
@ igorsantos07时区有关DST更改和其他更改的一组规则/历史记录。您的措辞似乎是多余的。您所说的“偏移量是时区加上DST规则”的说法是完全错误的:偏移量仅是数小时,数分钟和数秒,仅此而已。
罗勒·布尔克

34

与参考的PostgreSQL文档相比,我试图更容易地解释它。

顾名思义,这两种TIMESTAMP变体都不会存储时区(或偏移量)。不同之处在于对存储数据(和预期应用程序)的解释,而不是存储格式本身:

  • TIMESTAMP WITHOUT TIME ZONE存储本地日期时间(又称挂历日期和挂钟时间)。就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'它们与just相同'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 ZONE像一样存储TIMESTAMP WITH TIME ZONE,使用UTC时区呈现的时间恰好与当地日期时间相同的年,月,日,小时,分钟,秒和微秒。但这并不是要表示UTC解释所表示的时间线上的点,这只是对本地日期时间字段进行编码的方式。(这是时间线上的点簇,因为实时区域不是UTC;我们不知道它是什么。)


TIMESTAMP WITH TIME ZONE将a 检索为a 没有错Instant。两者都代表UTC时间轴上的一个点。Instant在我看来,OffsetDateTime它更可取,因为它更易于自我记录:A TIMESTAMP WITH TIME ZONE始终以UTC的形式从数据库中检索,而a Instant始终以UTC形式存在,因此是自然匹配,而an OffsetDateTime可以携带其他偏移量。
罗勒·布尔克

@BasilBourque不幸的是,当前的JDBC规范,JPA 2.2规范以及PostgreSQL JDBC文档仅提到OffsetDateTime了映射的Java类型。我不确定Instance某个地方是否仍非官方支持。
ddekany

问题,您说我在输入中指定的任何偏移量(例如'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

@ pk1m您会使问题复杂化::timestamptz。这样,您就可以将字符串转换为TIMESTAMP WITH TIME ZONE,并在将其进一步转换WITHOUT TIME ZONE为时,该字符串将存储从会话时区(可能是UTC)看到的该瞬间的“挂历”日期和挂钟时间。它仍然仍然是具有未指定偏移量的本地时间戳记(无区域)。
ddekany

我正在使用python,这就是插入时间戳感知的datettime对象时插入的内容。在我看来,将时间戳记与时区一起使用是有价值的,但是处理时区不是必需的。
PK1m

12

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

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

5
陈述“将不会正确转换”是完全不正确的。你必须明白什么timestamptimestamptz意味着什么。timestamptz表示绝对时间点(UTC),而timestamp表示在特定时区显示的时钟。因此,当转换timestamptz为时区时,您要问的是在这个绝对时间点纽约的时钟显示了什么?而当“转换” a时timestamp,您要问的是纽约的时钟显示x的绝对时间点
fphilipe

AT TIME ZONE即使您已经了解了WITHvs. WITHOUT TIME ZONE类型,该构造也是它自己的脑筋急转弯。因此,对它们进行解释是一个很好的选择。(:(AT TIME ZONEWITH TIME ZONE时间戳转换为WITHOUT TIME ZONE时间戳,反之亦然...不太明显。)
ddekany18年

now()::timestamp AT TIME ZONE 'CST'毫无意义,除非您将“ CST”区域的时钟在何时显示本地时钟的当前时间
Jasen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.