【问题标题】:PostgreSQL "date at time zone" unexpected behaviourPostgreSQL“时区日期”意外行为
【发布时间】:2021-03-17 03:28:21
【问题描述】:

at time zone 运算符在 date 类型上的行为与 PostgreSQL 中的预期不同。

set time zone 'UTC';
select
  '2021-01-03'::date at time zone 'America/Chicago' as "value",
  pg_typeof('2021-01-03'::date at time zone 'America/Chicago') as "type";
x value type
Expected 2021-01-03 06:00:00+00 timestamp with time zone
Actual 2021-01-02 18:00:00 timestamp without time zone

DB Fiddle

在 PostgreSQL 服务器版本 10、11、12 和 13 上测试。

这种行为对我来说很奇怪,我无法找到任何描述或讨论它的地方。似乎 PSQL 隐式地将date 转换为timestamptz(使用系统时区),然后应用at time zone 运算符。我知道 at time zone 运算符仅(在文档中)为 timestamp(tz)time(tz) 类型定义。但是,我会假设 PSQL 会将 date 隐式转换为 timestamp 而不是 timestamptz,因为:

  1. 这是一种“安全”转换(无需假设时区)。只需附加00:00:00
  2. 它会产生 timestamptz,当在没有时区的类型上使用 at time zone 运算符时会出现这种情况。

当前的行为对我来说根本没有意义,我想不出一个可以从中受益的用例。您要求 timestamp with time zone 获取特定时区的日期,而您却得到了本地化到给定时区的系统时区日期的 timestamp

当然,一个简单的解决方法是先转换为timestamp

set time zone 'UTC';
select '2021-01-03'::date::timestamp at time zone 'America/Chicago';

我希望有人能对此有所了解。我错过了什么?也许一些关于如何将date 隐式转换为timestamp(tz) 的总体规则?这种行为是否记录在某处?

谢谢!

  • 迈克

【问题讨论】:

    标签: postgresql date datetime


    【解决方案1】:

    你问“为什么”。原因是AT TIME ZONE 不对date 进行操作,因此PostgreSQL 调用了一个隐式类型转换 来将其转换为不同的数据类型。由于缺少明确的指令,PostgreSQL 选择 首选数据类型作为日期时间,恰好是 timestamp with time zone

    运算符的数据类型解析规则记录在here,类型类别的首选类型可以在pg_type 系统目录的typispreferredtypcategory 列中找到。

    【讨论】:

    • 啊,我明白了!搜索“首选数据类型”将我带到 PSQL 类型上的 this doc,其中指出:“类别和首选参数可用于帮助控制在模棱两可的情况下将应用哪些隐式转换。”这导致我在运算符类型解析上找到this doc,它解释了如何使用隐式转换。 date 属于日期/时间D 类别,timestamptz 是该类别中的首选类型。所以timestamptz用于模糊隐式转换。
    • 是的,完全正确。我将添加指向答案的链接。
    【解决方案2】:

    根据 Postgresql documentationAT TIME ZONE 将不带时区的time stamp 转换为带时区的time stamp 和不同时区的时间值

    在第一个查询中,您尝试将at time zone 应用于date 类型,而不是time stamp

    set time zone 'UTC';
    select
      '2021-01-03'::date at time zone 'America/Chicago' as "value",
      pg_typeof('2021-01-03'::date at time zone 'America/Chicago') as "type";
    

    最终会被忽略,你会得到一个没有timezone的时间戳

    但在第二个查询中,

    set time zone 'UTC';
    select '2021-01-03'::date::timestamp at time zone 'America/Chicago';
    

    您首先将date 转换为time stamp,然后将其转换为所需的time zone, 这为您提供了带有时区的时间戳的预期结果。

    希望这能消除您的疑虑。

    【讨论】:

    • 对...这基本上就是我所说的。我的问题是“为什么?”也许您可以对此进行扩展:“最终会被忽略,您将获得没有时区的时间戳”。 “被忽略”是什么意思?什么被忽略?为什么会被忽略?
    猜你喜欢
    • 2021-01-18
    • 2021-06-02
    • 2018-09-16
    • 1970-01-01
    • 2020-05-19
    • 2016-12-14
    • 1970-01-01
    相关资源
    最近更新 更多