【问题标题】:PostgreSQL date() with timezone带有时区的 PostgreSQL date()
【发布时间】: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

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

【问题讨论】:

    标签: postgresql datetime timezone postgresql-9.1 rails-postgresql


    【解决方案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' 中间人) - 这是在下面的答案中提出的,但这是我在阅读本文之前的预感。这些在技术上确实相同吗?
    【解决方案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_attimestamp [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' 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-04
      • 2013-03-27
      • 2014-01-24
      • 2010-12-12
      • 2016-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多