带有时区的 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
的效果也会随着底层数据类型的变化而变化。见:
如果您从 timestamp [without time zone]
类型中提取 date
,您将获得当前时区的日期。输出中的日期将与 timestamp
值的显示相同。
如果从timestamp with time zone
(简称timestamptz
)类型中提取date
,则首先“应用”时区偏移。您仍然会得到当前时区的日期,它与时间戳的显示一致。同一时间点在欧洲部分地区转化为次日下午 4 点过后。例如在加利福尼亚。要获取某个时区的日期,请先申请AT TIME ZONE
。
因此,您在问题顶部的描述与您的示例相矛盾。
鉴于 starts_at
是 timestamp [without time zone]
并且您服务器上的时间设置为本地时间。测试:
SELECT now();
它是否与墙上的时钟显示相同的时间?如果是(并且数据库服务器以正确的时间运行),则当前会话的 timezone
设置与您的本地时区一致。如果没有,您可能需要访问您的postgresql.conf
中的timezone
设置或您的客户端进行会话。 Details in the manual.
请注意,timezone
偏移量使用了与时间戳文字中显示的相反的符号。见:
只需从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(本地时间)