带有时区的 PostgreSQL date()

Posted

技术标签:

【中文标题】带有时区的 PostgreSQL date()【英文标题】:PostgreSQL date() with timezone 【发布时间】:2012-06-22 23:19:57 【问题描述】:

我在从 Postgres 中正确选择日期时遇到问题 - 它们以 UTC 格式存储,但是 未正确使用 Date() 函数进行转换。

如果超过太平洋标准时间下午 4 点,将时间戳转换为日期会给我错误的日期。

2012-06-21 在这种情况下应该是2012-06-20

starts_at 列数据类型为 timestamp without time zone。以下是我的疑问:

不转换为 PST 时区:

Select starts_at from schedules where id = 40;

      starts_at      
---------------------
 2012-06-21 01:00:00

转换给出了这个:

Select (starts_at at time zone 'pst') from schedules where id = 40;
        timezone        
------------------------
 2012-06-21 02:00:00-07

但两者都不会转换为时区中的正确日期。

【问题讨论】:

【参考方案1】:

基本上你想要的是:

$ select starts_at AT TIME ZONE 'UTC' AT TIME ZONE 'US/Pacific' from schedules where id = 40

我从这篇文章中得到的解决方案如下,这是纯金!!!它非常清楚地解释了这个不平凡的问题,如果您想更好地了解 pstgrsql TZ 管理,请阅读它。

Expressing PostgreSQL timestamps without zones in local time

这就是正在发生的事情。首先,您应该知道'PST 时区比 UTC 时区晚 8 小时,因此例如 2014 年 1 月 1 日下午 4:30 PST(2014 年 1 月 1 日星期三 16:00:30 -0800)相当于 2014 年 1 月 2 日 00:30上午 UTC(2014 年 1 月 2 日星期四 00:00:30 +0000)。太平洋标准时间下午 4:00 之后的任何时间都会滑到第二天,解释为 UTC。

另外,正如 Erwin Brandstetter 上面提到的,postresql 有两种时间戳数据类型,一种有时区,一种没有。 如果您的时间戳包含时区,那么很简单:

$ select starts_at AT TIME ZONE 'US/Pacific' from schedules where id = 40

会起作用。但是,如果您的时间戳是无时区的,则执行上述命令将不起作用,您必须首先将您的无时区时间戳转换为带有时区的时间戳,即 UTC 时区,然后才将其转换为您想要的“PST”或“US/”太平洋”(在一些夏令时问题上是相同的。我认为你应该可以接受)。

让我用一个创建无时区时间戳的示例来演示。为方便起见,我们假设我们的本地时区确实是“PST”(如果不是,那么它会变得有点复杂,这对于本解释而言是不必要的)。

说我有:

$ select timestamp '2014-01-2 00:30:00' AS a, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'UTC' AS b,  timestamp '2014-01-2 00:30:00' AT TIME ZONE 'UTC' AT TIME ZONE 'PST' AS c, timestamp '2014-01-2 00:30:00' AT TIME ZONE 'PST' AS d

这将产生:

"a"=>"2014-01-02 00:30:00"   (This is the timezoneless timestamp)
"b"=>"2014-01-02 00:30:00+00" (This is the UTC TZ timestamp, note that up to a timezone, it is equivalent to the timezoneless one)
"c"=>"2014-01-01 16:30:00" (This is the correct 'PST' TZ conversion of the UTC timezone, if you read the documentation postgresql will not print the actual TZ for this conversion)
"d"=>"2014-01-02 08:30:00+00"

最后一个时间戳是在 postgresql 中将无时区时间戳从 UTC 转换为“PST”的所有混淆的原因。当我们写:

timestamp '2014-01-2 00:30:00' AT TIME ZONE 'PST' AS d

我们采用无时区时间戳并尝试将其转换为 'PST TZ(我们间接假设 postgresql 会理解我们希望它从 UTC TZ 转换时间戳,但 postresql 有自己的计划!)。在实践中,postgresql 所做的是它采用无时区时间戳('2014-01-2 00:30:00)并将其视为已经是“PST”TZ 时间戳(即:2014-01-2 00:30 :00 -0800) 并将其转换为 UTC 时区!!!所以它实际上将它提前 8 小时而不是向后推!因此我们得到 (2014-01-02 08:30:00+00)。

无论如何,最后一个(不直观的)行为是所有混乱的原因。如果您想要更彻底的解释,请阅读文章,实际上我得到的结果与最后一部分的结果有些不同,但总体思路是相同的。

【讨论】:

感谢您的详细解释。在我读到这篇文章之前,我完全不知所措! 我会说这是一个更好接受的答案,因为关于“首先转换为 UTC”的额外信息,因为这是我面临的问题(没有时区怪癖的时间戳)。 我使用starts_at::timestamptz at time zone "pst" 找到了相同的结果(通过使用 tz 进行转换跳过at time zone 'utc' 中间人) - 这是在下面的答案中提出的,但这是我在阅读本文之前的预感。这些在技术上确实相同吗? 被引文章移至icu.iorahealth.com/blog/2012/05/07/…【参考方案2】:

我在您的问题中没有看到starts_at确切 类型。您确实应该包含此信息,这是解决方案的关键。我得猜一下。

PostgreSQL 总是在内部存储 timestamp with time zone 类型的 UTC 时间。输入和输出(显示)调整为当前的timezone 设置或给定的时区。 AT TIME ZONE 的效果也会随着底层数据类型的变化而变化。见:

Ignoring time zones altogether in Rails and PostgreSQL

如果您从 timestamp [without time zone] 类型中提取 date,您将获得当前时区的日期。输出中的日期将与 timestamp 值的显示相同。

如果从timestamp with time zone(简称timestamptz)类型中提取date,则首先“应用”时区偏移。您仍然会得到当前时区的日期,它与时间戳的显示一致。同一时间点在欧洲部分地区转化为次日下午 4 点过后。例如在加利福尼亚。要获取某个时区的日期,请先申请AT TIME ZONE

因此,您在问题顶部的描述与您的示例相矛盾。

鉴于 starts_attimestamp [without time zone] 并且您服务器上的时间设置为本地时间。测试:

SELECT now();

它是否与墙上的时钟显示相同的时间?如果是(并且数据库服务器以正确的时间运行),则当前会话的 timezone 设置与您的本地时区一致。如果没有,您可能需要访问您的postgresql.conf 中的timezone 设置或您的客户端进行会话。 Details in the manual.

请注意,timezone 偏移量使用了与时间戳文字中显示的相反的符号。见:

Peculiar time zone handling in a Postgres database

只需从starts_at 获取您的本地日期

SELECT starts_at::date

等于:

SELECT date(starts_at)

顺便说一句,您的当地时间现在是 UTC-7,而不是 UTC-8,因为夏令时有效(不在人类更聪明的想法中)。

太平洋标准时间 (PST) 通常比 UTC(通用时区)“早”8 小时(timestamp 值更大),但在夏令时期间(如现在),它可以是 7 小时。这就是为什么 timestamptz 在您的示例中显示为 2012-06-21 02:00:00-07 的原因。构造AT TIME ZONE 'PST' 考虑了夏令时。这两个表达式产生不同的结果(一个在冬天,一个在夏天),并且在转换时可能会导致不同的日期:

SELECT '2012-06-21 01:00:00'::timestamp AT TIME ZONE 'PST'
     , '2012-12-21 01:00:00'::timestamp AT TIME ZONE 'PST'

【讨论】:

这有点正确 - “PST”没有考虑夏令时,请参阅 John Rennpferd 关于使用“US/Pacific”与“PST”或“PDT”的回答 @Erwin Brandstetter 如果我通过SELECT '2018-09-17'::date 键入一个日期字符串会发生什么?它会假设结果是 UTC 还是本地时间? date 就是这样:一个日期。不涉及时区。没有任何信息在地球上应该是日期。当(显式或隐式)转换为 timestamp / timestamptz 时,时区再次变得相关。【参考方案3】:

我知道这是一个旧的,但您可能需要考虑在投射时使用 AT TIME ZONE "US/Pacific" 以避免任何 PST/PDT 问题。所以

SELECT starts_at::TIMESTAMPTZ AT TIME ZONE "US/Pacific" 
  FROM schedules 
 WHERE ID = '40';

【讨论】:

如果您需要历史日期来表示它们发生的实际时间,这是绝对正确的。【参考方案4】:
cast(master.Stamp5DateTime as date) >= '05-05-2019' AND 

cast(master.Stamp5DateTime as date) <= '05-05-2019' 

【讨论】:

以上是关于带有时区的 PostgreSQL date()的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 将带有时区的 TIMESTAMP 转换为 DATE

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

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

如何在flyway创建的postgresql jdbc连接上设置时区?

Postgresql JDBC 使用时区读取数据

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