关于datetime:带时区的PostgreSQL日期函数date()

PostgreSQL date() with timezone

我在从Postgres正确选择日期时遇到了一个问题-这些日期存储在UTC中,但是未正确使用date()函数进行转换。

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

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

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

不转换为PST时区:

1
2
3
4
5
SELECT starts_at FROM schedules WHERE id = 40;

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

转换可以得到:

1
2
3
4
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管理,请阅读它。

在本地时间表达无区域的PostgreSQL时间戳

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

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

1
$ SELECT starts_at AT TIME ZONE 'US/Pacific' FROM schedules WHERE id = 40

会工作。但是,如果时间戳是无时区的,则执行上述命令将不起作用,并且必须首先将无时区时间戳转换为具有时区(即UTC时区)的时间戳,然后将其转换为所需的"pst"或"us/pacific"(在某些夏令时问题上是相同的)。我想你也可以。

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

说我有:

1
$ 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

这将产生:

1
2
3
4
"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"的所有混淆的原因。当我们写:

1
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)。

总之,最后一个(不直观的)行为是所有混乱的原因。读这篇文章,如果你想更深入的解释,我实际上得到的结果与上一部分的结果有点不同,但总的想法是一样的。


在你的问题中,我看不到确切类型的starts_at。你真的应该包括这些信息,这是解决方案的关键。我得猜一下。

基本上,PostgreSQL总是在内部存储timestamp with time zone类型的UTC时间值。只有显示器随当前的timezone设置而变化。AT TIME ZONE构造的效果也随着基础数据类型的变化而变化。更多细节:

  • 在Rails和PostgreSQL中完全忽略时区

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

如果从类型timestamp with time zone(简称timestamptz)中提取date,则首先"应用"时区偏移。您仍然可以得到当前时区的日期,这与时间戳的显示一致。同样的时间点在欧洲的一些地方也被翻译成第二天,比如在加利福尼亚,下午4点就过去了。要获得某个时区的日期,请先应用AT TIME ZONE

因此,你在问题顶部所描述的与你的例子相矛盾。

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

1
SELECT now();

它和墙上的钟显示的时间一样吗?如果是(并且DB服务器以正确的时间运行),则当前会话的timezone设置与本地时区一致。如果没有,您可以访问您的postgresql.conf中的timezone设置或您的客户进行会话。手册中的详细信息。

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

  • Postgres数据库中的特殊时区处理

starts_at获取本地日期

1
SELECT starts_at::DATE

等于:

1
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'考虑了夏令时。这两个表达式产生不同的结果(一个在冬季,一个在夏季),并且在投射时可能产生不同的日期:

1
2
SELECT '2012-06-21 01:00:00'::TIMESTAMP AT TIME ZONE 'PST'
     , '2012-12-21 01:00:00'::TIMESTAMP AT TIME ZONE 'PST'


我知道这是一个旧版本,但您可能想考虑在广播时使用时区"美国/太平洋",以避免任何PST/PDT问题。所以

select从id='40'的日程表开始在时区"US/Pacific"的时间戳"U at::Timestamptz";