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

时间:2014-01-22 07:55:05

标签: postgresql timezone

今天早上我遇到了以下问题:

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小时呢?

1 个答案:

答案 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格式存储并自动转换为本地时间。

为什么第二个工作(只要TimeZone = 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';