要了解的关键事项
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';