今天早上我遇到了以下问题:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
告诉我2011-12-30 05:30:00+00
女巫错了。
但接下来的查询:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'UTC-5';
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';
我看到了正确的日期2011-12-29 19:30:00
阻止您对我当地时区的提问:
SELECT current_setting('TIMEZONE');
current_setting
-----------------
UTC
(1 row)
有没有人回答为什么postgresql会以某种奇怪的方式转换timestamp without time zone
而转而花费5小时呢?
答案 0 :(得分:115)
timestamp without time zone AT TIME ZONE
重新解释一个timestamp
,因为它位于该时区中,目的是将其转换为UTC 。
timestamp with time zone AT TIME ZONE
在指定的时区将timestamptz
转换为timestamp
。
PostgreSQL使用ISO-8601时区,指定格林威治东部为正...除非您使用POSIX时区说明符,在这种情况下它遵循POSIX。随之而来的是精神错乱。
SQL中的时间戳和时区非常糟糕。这样:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
将未知类型的文字'2011-12-30 00:30:00'
解释为timestamp without time zone
,除非另有说明,否则Pg假定它位于本地TimeZone中。当您使用AT TIME ZONE
时,(根据规范)将重新解释作为时区timestamp with time zone
中的EST5EDT
,然后将其存储为UTC中的绝对时间 - 所以它从 EST5EDT
转换为到 UTC,即时区偏移减去。 x - (-5)
为x + 5
。
此时间戳调整为UTC存储,然后根据您的服务器TimeZone
设置进行调整以便显示,以便以当地时间显示。
如果您希望说“我在UTC时间有这个时间戳,并希望看到EST5EDT中的等效本地时间是什么”,如果您想独立于服务器TimeZone设置,您需要写一些类似的东西:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
AT TIME ZONE 'EST5EDT';
这表示“给定时间戳2011-12-30 00:30:00,在转换为timestamptz时将其视为UTC时间戳,然后将该时间戳转换为EST5EDT中的本地时间”。
可怕,不是吗?我想让公司与交谈,而不管是AT TIME ZONE
的疯狂语义决定者 - 它应该是timestamp CONVERT FROM TIME ZONE '-5'
和timestamptz CONVERT TO TIME ZONE '+5'
。此外,timestamp with time zone
实际上应该携带它的时区,而不是以UTC格式存储并自动转换为本地时间。
您原来的“作品”版本:
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';
如果TimeZone设置为UTC,将只是正确的,因为当未指定一个时,text-to-timestamptz强制转换假设为TimeZone。
两个问题相互抵消。
看起来有效的另一个版本是TimeZone独立的,但它只能起作用,因为两个问题会自行取消。首先,如上所述,timestamp without time zone AT TIME ZONE
将时间戳重新解释为在该时区内转换为UTC时间戳;这有效减去时区偏移量。
然而,由于我无法理解的原因,PostgreSQL使用带有反向符号的时间戳到我以前看到的大多数地方。见the documentation:
要记住的另一个问题是,在POSIX时区名称中,正偏移用于格林威治以西的位置。在其他地方,PostgreSQL遵循ISO-8601惯例,即正时区偏移位于格林威治以东。
这意味着EST5EDT
与+5
相同,而不是-5
。这就是为什么它的工作原理:因为你减去tz偏移量而不是添加它,但是你减去了一个否定的偏移量!
正确的做法是:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
AT TIME ZONE '+5';