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

Posted

技术标签:

【中文标题】PostgreSQL 错误地从没有时区的时间戳转换为有时区的时间戳【英文标题】:PostgreSQL wrong converting from timestamp without time zone to timestamp with time zone 【发布时间】:2014-02-12 04:06:15 【问题描述】:

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

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】:

要了解的关键事项

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 假定它在本地时区中。当您使用 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 时间戳,然后将该 timestamptz 转换为 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';

【讨论】:

@RichardHuxton 我在我的时代也想出了一些非常愚蠢的东西,我不希望有人拿着球棒追赶 me ;-) 所以不.曾经读过代码,喃喃自语“这是什么运球白痴写的”……然后意识到是你? 我写了一篇很长的article 来介绍 PostgreSQL 中的时间戳和时区,其中会详细介绍。可能对登陆这里的人有所帮助。

以上是关于PostgreSQL 错误地从没有时区的时间戳转换为有时区的时间戳的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL:将字符串转换为没有时区的时间戳以更改时间日期时,我得到了意想不到的结果

错误:COALESCE 类型时间戳没有时区和整数无法匹配(Postgresql)

PostgreSQL:在不同时区的时间戳中添加间隔

PostgreSQL在没有时区的时间戳类型的表中搜索

带有时区的 PostgreSQL date()

将 postgresql 中的 UTC 时区转换为 EST(本地时间)