【问题标题】:Converting Between Timezones in Postgres在 Postgres 中的时区之间转换
【发布时间】:2018-06-12 15:47:55
【问题描述】:

我正在尝试了解 Postgre 中的时间戳和时区。我想我明白了,直到我红了this 文章。
专注于“时区之间的转换”部分。它有两个例子。

(考虑默认时区配置为 UTC。)

示例 1

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00'); outputs 2015-12-31 16:00:00

根据文章和我的理解,因为timezone函数的'2016-01-01 00:00'部分只是一个字符串,所以它被默默地转换为默认的UTC。因此,根据timezone 函数的要求,从'2016-01-01 00:00' UTC 转换为US/Pacific,即2015-12-31 16:00:00

示例 2

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp); outputs 2016-01-01 08:00:00+00

对不起,我不明白为什么,那里的解释也无济于事。好的,timezone 函数的'2016-01-01 00:00'::timestamp 部分不再是字符串,而是实际的时间戳。在哪个时区?如果是 UTC,则输出必须与示例 1 相同。所以它会自动转换为 US/Pacific?那么输出是UTC?但为什么?我在timezone 中要求US/Pacific 而不是UTC。

请解释timezone 在获取时间戳并被要求对其进行转换时的行为。谢谢。

【问题讨论】:

  • docs 非常有用,如果您还没有看到它们的话。摘录:For timestamp with time zone, the internally stored value is always in UTC. An input value that has an explicit time zone specified is converted to UTC using the appropriate offset for that time zone. If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system's TimeZone parameter, and is converted to UTC using the offset for the timezone zone.
  • @bma 没有冒犯,但你没有帮助我。我看过文档。这两个示例的默认时区都是 UTC。示例 2 具有时区数据。所以在内部它被存储为UTC。在这种情况下,默认时区也恰好是 UTC。所以应该和例子1一样。不是吗?为什么?除非字符串到 UTC 和时间戳到 UTC 给出不同的结果。谢谢
  • 您说得对,这对重新阅读您的问题并没有太大帮助(对此我深表歉意)。 timezones 有一个详尽的答案,但它似乎没有解决您演示的隐式字符串 -> 时间戳 -> 时间戳转换。如果显式转换字符串并定义时间戳,则在 #2 中会得到与在 #1 中相同的结果。 SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp at time zone 'UTC');
  • @bma 我终于明白了。我想。检查我的答案。谢谢

标签: postgresql timezone timestamp timezone-offset timestamp-with-timezone


【解决方案1】:

让我解释一下这两个例子:

在这两种情况下,我们都假设时区为 UTC(即SET timezone TO UTC)。

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00');
      timezone
---------------------
 2015-12-31 16:00:00
(1 row)

这等效于SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz),即Postgres 将字符串隐式转换为timestamptz

我们知道timezone 函数在timestamptimestamptz 之间来回转换:

由于我们给它一个timestamptz 作为输入,它会输出一个timestamp。换句话说,它将绝对时间点2016-01-01 00:00Z 转换为US/Pacific 中的挂壁时间,即洛杉矶的时钟在该绝对时间点显示的内容。

在示例 2 中,我们正在做相反的事情,即采用 timestamp 并将其转换为 timestamptz。换句话说,我们在问:洛杉矶的时钟显示2016-01-01 00:00的绝对时间点是什么?

你提到:

好的,时区函数的'2016-01-01 00:00'::timestamp 部分不再是字符串,而是实际的时间戳。在哪个时区?

'2016-01-01 00:00'::timestamptimestamp,即挂墙时间。它没有时区的概念。

我想你可能还没有完全理解timestamptimestamptz 之间的区别,这是这里的关键。只需将它们视为墙上时间,即在世界某处挂在墙上的时钟上显示的时间,以及绝对时间,即我们宇宙中的绝对时间.

您在自己的回答中所举的例子并不十分准确。

SELECT ts FROM  (VALUES
(timestamptz '2012-03-05 17:00:00+0') -- outputs 2012-03-05 17:00:00+00 --1
,(timestamptz '2012-03-05 18:00:00+1') -- outputs 2012-03-05 17:00:00+00 --2
,(timestamp   '2012-03-05 18:00:00+1') -- outputs 2012-03-05 18:00:00+00 --3
,(timestamp   '2012-03-05 11:00:00'  AT TIME ZONE '+6') -- outputs 2012-03-05 17:00:00+00 --4
,(timestamp   '2012-03-05 17:00:00'  AT TIME ZONE 'UTC') -- outputs 2012-03-05 17:00:00+00 --5
,(timestamp   '2012-03-05 17:00:00'::timestamp) -- outputs 2012-03-05 17:00:00+00 --6
,(timestamp   '2012-03-05 17:00:00'::timestamptz) -- outputs 2012-03-05 17:00:00+00 --7
    ) t(ts);

您的示例的问题是您正在构建一个包含单列的数据集。由于一列只能有一种类型,因此每一行(或本例中的单个值)都被转换为相同的类型,即timestamptz,即使某些值被计算为timestamp(例如值 3)。因此,您在这里有一个额外的隐式转换。

让我们将示例拆分为单独的查询,看看发生了什么:

示例 1

db=# SELECT timestamptz '2012-03-05 17:00:00+0';
      timestamptz
------------------------
 2012-03-05 17:00:00+00

您可能已经知道,timestamptz '2012-03-05 17:00:00+0''2012-03-05 17:00:00+0'::timestamptz 是等价的(我更喜欢后者)。因此,为了使用与文章中相同的语法,我将重写:

db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00

现在,这里发生了什么?嗯,比你原来的解释要少。该字符串被简单地解析为timestamptz。当打印结果时,它使用当前设置的timezone 配置将其转换回底层数据结构的人类可读表示,即2012-03-05 17:00:00+00

让我们更改timezone 配置,看看会发生什么:

db=# SET timezone TO 'Europe/Berlin';
SET
db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 18:00:00+01

唯一改变的是如何timestamptz 打印在屏幕上,即使用欧洲/柏林时区。

示例 2

db=# SELECT timestamptz '2012-03-05 18:00:00+1';
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

同样,只是解析日期。

示例 3

db=# SELECT timestamp '2012-03-05 18:00:00+1';
      timestamp
---------------------
 2012-03-05 18:00:00
(1 row)

这与'2012-03-05 18:00:00+1'::timestamp 相同。这里发生的情况是,时区偏移量被简单地忽略了,因为您要求的是 timestamp

示例 4

db=# SELECT timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6';
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

让我们重写更简单:

db=# SELECT timezone('+6', '2012-03-05 11:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

这是在问:时区墙上的时钟显示 2012-03-05 11:00:00 时,偏移量为 +6 小时的绝对时间是多少?

示例 5

db=# SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC';
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

让我们重写:

db=# SELECT timezone('UTC', '2012-03-05 17:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

这是在问:UTC 时区墙上的时钟显示2012-03-05 17:00:00 的绝对时间是多少?

示例 6

db=# SELECT timestamp '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

在这里,您将两次投射到timestamp,这没有区别。让我们简化一下:

db=# SELECT '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

我认为这很清楚。

示例 7

db=# SELECT timestamp '2012-03-05 17:00:00'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

让我们重写:

db=# SELECT ('2012-03-05 17:00:00'::timestamp)::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

您首先将字符串解析为timestamp,然后使用当前设置的timezone 将其转换为timestamptz。如果我们更改 timezone,我们会得到其他信息,因为 Postgres 在将 timestamp(或缺少时区信息的字符串)转换为 timestamptz 时假定该时区:

db=# SET timezone TO 'Europe/Berlin';
SET
db=# SELECT ('2012-03-05 17:00:00'::timestamp)::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+01
(1 row)

这个以 UTC 表示的绝对时间是2012-03-05 16:00:00+00,因此与原始示例不同。


我希望这可以澄清事情。同样,了解timestamptimestamptz 之间的区别是关键。想想墙上时间与绝对时间。

【讨论】:

  • timestamptz 解析任何偏移量,以便读取日期。 timestamp 忽略它,因为没有这个概念。当指定特定时区时,将完成任何实际转换(非解析)。 timezonetimezone(clock) 转换为 timezonetz(global),反之亦然。如果使用SETpostgresql.conf 定义了不同的时区,则会进行任何最终的附加转换。这是“黄金法则”。我想我现在明白了。非常感谢您的时间和精力。
【解决方案2】:

这是我的理解。请告诉我。
我在postgresql.conf 中定义的默认时区是UTC。检查此代码

SELECT ts FROM  (VALUES
(timestamptz '2012-03-05 17:00:00+0') -- outputs 2012-03-05 17:00:00+00 --1
,(timestamptz '2012-03-05 18:00:00+1') -- outputs 2012-03-05 17:00:00+00 --2
,(timestamp   '2012-03-05 18:00:00+1') -- outputs 2012-03-05 18:00:00+00 --3
,(timestamp   '2012-03-05 11:00:00'  AT TIME ZONE '+6') -- outputs 2012-03-05 17:00:00+00 --4
,(timestamp   '2012-03-05 17:00:00'  AT TIME ZONE 'UTC') -- outputs 2012-03-05 17:00:00+00 --5
,(timestamp   '2012-03-05 17:00:00'::timestamp) -- outputs 2012-03-05 17:00:00+00 --6
,(timestamp   '2012-03-05 17:00:00'::timestamptz) -- outputs 2012-03-05 17:00:00+00 --7
    ) t(ts);

现在,假设这是 Postgre 说话:
为输出定义了特殊时区。所以我将以默认的 UTC 输出所有内容。我们走吧。

1 (timestamptz '2012-03-05 17:00:00+0')
这是时间感知数据,偏移量为 0,因此是 UTC。默认值也是 UTC。我将按原样保存(无需转换)并输出2012-03-05 17:00:00+00,因为 UTC 输入到 UTC 保存到 UTC 输出。

2 (timestamptz '2012-03-05 18:00:00+1')
也是时间感知数据,偏移量是 +1,所以它不是 UTC。偏移负 1 以将其转换为 UTC,因此我可以将其保存为 UTC,这是默认值。输出 2012-03-05 17:00:00+00 因为非 UTC 输入到 UTC 保存到 UTC 输出。

3 (timestamp '2012-03-05 18:00:00+1')
不知道时间的数据。忽略偏移量,假设这是默认的 UTC 并按原样保存。输出 2012-03-05 18:00:00+00 因为,I-dont-know-I-dont-care-I-will-pretend-this-is-my-default-UTC-input 到 UTC 保存到 UTC 输出。

4 (timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6')
又是不知道时间的数据。忽略偏移量(如果有)。然后将其转换为给定的AT TIME ZONE '+6' 偏移量,这样我就可以将其视为完整的无时间感知数据。所以我的最终数据是2012-03-05 17:00:00+00。但这仍然不是时间感知数据。所以,我会假设这是我的默认 UTC 并按原样保存。输出 2012-03-05 17:00:00+00 因为,I-dont-know-I-dont-care-I-will-pretend-this-is-my-default-UTC-input 到 UTC 保存到 UTC 输出。

5 (timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC')
和之前的数据一样,时间不敏感的数据。如果有的话,我会忽略偏移量。然后我将它转换为给定的AT TIME ZONE 'UTC',所以没有实际的转换,因为没有实际的偏移量(UTC偏移量为0)。所以我的最终数据是2012-03-05 17:00:00。但这仍然不是时间感知数据。所以,我会假设这是我的默认 UTC 并按原样保存。输出2012-03-05 17:00:00 因为,I-dont-know-I-dont-care-I-will-pretend-this-is-my-default-UTC-input 到 UTC 保存到 UTC 输出

6 (timestamp '2012-03-05 17:00:00'::timestamp)
这是不知道时间的数据,再次转换为不知道时间的数据。所以,像 4 一样,我会忽略任何 offset (如果有的话)。也没有AT TIME ZONE,所以没有转换。我最后的时间未知数据是'2012-03-05 17:00:00'。我将假设这是我的默认 UTC 并按原样保存。输出2012-03-05 17:00:00+00 因为,I-dont-know-I-dont-care-I-will-pretend-this-is-my-default-UTC-input 到 UTC 保存到 UTC 输出

7 (timestamp '2012-03-05 17:00:00'::timestamptz)
这是时间不感知数据,转换为时间感知数据。但是没有偏移,转换,什么都没有。所以,这是UTC。所以,我会按原样保存。输出 2012-03-05 17:00:00+00 因为 UTC 输入到 UTC 保存到 UTC 输出。

(希望以上内容对任何人都有帮助)

现在!关于文章
示例1
SELECT timezone('US/Pacific', '2016-01-01 00:00');
时间不感知数据,但我可以将其转换为时间感知数据。根据article,由于没有时区信息,所以可以在默认的UTC时区解析。 因此,时间感知的 UTC 数据按原样保存,但在输出之前将其转换为 US/Pacific。这就是为什么article 说“我们在 2016 年 1 月 1 日 00:00 UTC 获得加利福尼亚的挂墙时间。”对于'2016-01-01 00:00' UTC 输入,输出是 2015-12-31 16:00:00,即加利福尼亚的挂墙时间。

Article 还说“请注意,我们将时间戳作为字符串传递,它被隐式转换为 timestamptz”。
这可以写成SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz);,仍然输出2015-12-31 16:00:00。时间感知数据,没有偏移量,所以它的偏移量为 0,所以它的 UTC。 UTC 也是默认值,因此只需按原样保存即可。在输出之前将其转换为US/Pacific。这就是它再次输出2015-12-31 16:00:00 的原因。

由于“timezone(zone, timestamp)等价于符合SQL的构造timestamp AT TIME ZONE zone”,根据article,那么

SELECT timezone('US/Pacific', '2016-01-01 00:00');
SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz);
timestamptz '2016-01-01 00:00' at time zone 'US/Pacific'
timestamptz '2016-01-01 00:00+00' at time zone 'US/Pacific'

都一样
时间感知数据(或使其具有时间感知),无偏移,保存为UTC,转换后输出,为US/Pacific


示例 2
SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp);
不知道时间的数据。我可以将其转换为 UTC 默认值,如示例 1 中所示?不,因为已转换为时间不感知(::timestamp 部分)。我无能为力。它是时间感知数据。

我会忽略偏移量,如果有的话。与上面的 4 不同,没有定义偏移量,没有 AT TIME ZONE '+ or -X'。因此,为了获得 UTC,我将根据 US/Pacific'2016-01-01 00:00' 转换回 UTC。从太平洋到 UTC 增加 8 小时。我的 UTC 现在是 2016-01-01 08:00:00+00。按原样保存。输出2016-01-01 08:00:00+00 因为,I-dont-know-I-dont-care-I-will-pretend-this-is-my-default-UTC-input 到 UTC 保存到 UTC 输出

再一次,根据articletimezone(zone, timestamp)相当于符合SQL的构造timestamp AT TIME ZONE zone”,所以

SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp);
timestamp '2016-01-01 00:00' at time zone 'US/Pacific'
timestamp '2016-01-01 00:00+00' at time zone 'US/Pacific'

都一样

不知道时间的数据,忽略偏移,转换回 UTC,这是 UTC,保存为 UTC 输出为 UTC。

谢谢

【讨论】:

  • 你的问题让我想起了最近出版的一本关于这个主题的书,你可能会也可能不会觉得有趣:korban.net/postgres/book。我不以任何方式隶属于该网站,但查看该网站,它似乎非常明确地涵盖了该主题。
猜你喜欢
  • 2018-11-26
  • 1970-01-01
  • 1970-01-01
  • 2017-03-05
  • 2016-11-06
  • 2011-07-27
  • 1970-01-01
  • 2015-03-03
  • 1970-01-01
相关资源
最近更新 更多