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:将字符串转换为没有时区的时间戳以更改时间日期时,我得到了意想不到的结果