关于类型:PostgreSQL中有/无时区的时间戳之间的差异

Difference between timestamps with/without time zone in PostgreSQL

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


PostgreSQL文档中包含日期/时间类型的差异。是的,TIMETIMESTAMP的治疗方法不同于一个WITH TIME ZONEWITHOUT TIME ZONE。它不影响值的存储方式;它影响值的解释方式。

文档中专门介绍了时区对这些数据类型的影响。差异来自系统对价值的合理了解:

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

  • 如果不将时区作为值的一部分,则明显的默认时区为UTC,因此将为该时区呈现该时区。

这种行为取决于至少三个因素:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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)


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

无论名称如何,TIMESTAMP变体都不存储时区(或偏移量)。区别在于对存储数据的解释(以及在预期应用中),而不是存储格式本身:

  • TIMESTAMP WITHOUT TIME ZONE存储本地日期时间(aka.挂历日期和挂钟时间)。据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开发人员来说,它类似于EDCOX1(12)。

  • TIMESTAMP WITH TIME ZONE在UTC时间线上存储一个点。它的外观(小时、分钟等)取决于您的时区,但它总是指相同的"物理"瞬间(如实际物理事件的瞬间)。这个输入在内部转换为UTC,这就是它的存储方式。为此,必须知道输入的偏移量,因此当输入不包含显式偏移量或时区(如'2011-07-01 06:30:30')时,假定它位于PostgreSQL会话的当前时区,否则将使用显式指定的偏移量或时区(如'2011-07-01 06:30:30+05')。将显示转换为PostgreSQL会话当前时区的输出。对于Java开发人员来说,它类似于EDCOX1×16(虽然分辨率较低),但是对于JDBC和JPA 2.2,您应该将其映射到EDCOX1×17(或EDCOX1 18)或EDCOX1(19)当然。

有人说两个TIMESTAMP变体都存储UTC日期时间。有点,但在我看来,这样说很难理解。TIMESTAMP WITHOUT TIME ZONE的存储方式与TIMESTAMP WITH TIME ZONE的存储方式类似,使用UTC时区呈现时,恰好给出与本地日期时间相同的年、月、日、小时、分钟、秒和微秒。但它并不代表UTC解释所说的时间线上的点,而是本地日期时间字段的编码方式。(它是时间线上的一些点簇,因为实时区域不是UTC;我们不知道它是什么。)


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

1
2
3
4
SELECT now(),
   now()::TIMESTAMP,
   now() AT TIME ZONE 'CST',
   now()::TIMESTAMP AT TIME ZONE 'CST'

输出:

1
2
3
4
5
-[ 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