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

sai*_*heg 48 postgresql timezone

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

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
Run Code Online (Sandbox Code Playgroud)

回报我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';
Run Code Online (Sandbox Code Playgroud)

我看到了正确的约会 2011-12-29 19:30:00

防止您对我当地时区的提问:

SELECT  current_setting('TIMEZONE');
current_setting
-----------------
     UTC
(1 row)
Run Code Online (Sandbox Code Playgroud)

有没有人回答为什么postgresql会转换timestamp without time zone一些奇怪的方式而不是带走5个小时呢?

Cra*_*ger 122

要理解的关键事项

timestamp without time zone AT TIME ZONE 为了将其转换为UTC,将 a 重新解释timestamp为该时区.

timestamp with time zone AT TIME ZONE 在指定的时区 a 转换timestamptz为a timestamp.

PostgreSQL使用ISO-8601时区,指定格林威治东部为正...除非您使用POSIX时区说明符,在这种情况下它遵循POSIX.精神错乱随之而来.

为什么第一个产生意外结果

SQL中的时间戳和时区非常糟糕.这个:

select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';
Run Code Online (Sandbox Code Playgroud)

将未知类型的文字解释'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';
Run Code Online (Sandbox Code Playgroud)

这表示"给定时间戳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';
Run Code Online (Sandbox Code Playgroud)

只有在TimeZone设置为UTC时才会正确,因为当未指定timeZone时,text-to-timestamptz强制转换假设为TimeZone.

为什么第三个有效

两个问题相互抵消了.

看起来有效的另一个版本是TimeZone独立的,但它只能起作用,因为两个问题会自行取消.首先,如上所述,将时间戳timestamp without time zone AT TIME ZONE 重新解释为在该时区内转换为UTC时间戳; 这有效地减去了时区偏移量.

但是,由于我无法理解的原因,PostgreSQL使用带有反向符号的时间戳到我以前看到的大多数地方.查看文档:

另外要记住的一个问题是,在POSIX时区名称中,正偏移用于格林威治以西的位置.在其他地方,PostgreSQL遵循ISO-8601惯例,正面时区偏移位于格林威治以东.

这意味着它EST5EDT是相同的+5,而不是-5.这就是为什么它的工作原理:因为你减去tz偏移而不是添加它,但是你减去了一个否定的偏移量!

相反,你需要做的就是:

select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE '+5';
Run Code Online (Sandbox Code Playgroud)

  • @atmaish可能是由于夏令时.;) (7认同)
  • @RichardHuxton在我的时间里,我也想出了一些非常愚蠢的东西,我不希望有人跟着我玩*蝙蝠;-)所以没有.曾经读过代码,嘀咕诸如"什么运球白痴写这个"......然后意识到这是你? (6认同)
  • 为什么我1小时前没找到这个! (2认同)
  • 我写了一篇关于 PostgreSQL 中的时间戳和时区的长篇文章,其中详细介绍了更多细节。可能对登陆这里的人有帮助。 (2认同)