在Postgres中的时区之间转换

sle*_*vin 2 postgresql timezone timestamp timezone-offset timestamp-with-timezone

我试图了解Postgre中的时间戳和时区。我想我明白了,直到我写这篇文章为止。
专注于“在时区之间转换”部分。它有两个例子。

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

例子1

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

根据本文和我的理解,由于该函数的'2016-01-01 00:00'一部分timezone只是一个字符串,因此将其静默转换为默认的UTC。因此,'2016-01-01 00:00' UTC然后将其转换US/Pacifictimezone函数所要求的,即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
Run Code Online (Sandbox Code Playgroud)

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

请说明timezone何时获取时间戳并被要求转换时间戳时的行为。谢谢。

fph*_*ipe 5

让我解释两个例子:

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

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

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

我们知道timezone函数在timestamp和之间来回转换timestamptz

在此处输入图片说明

由于我们将其timestamptz作为输入,因此将输出timestamp。换句话说,它将绝对时间点转换2016-01-01 00:00Z为中的墙上时间US/Pacific,即洛杉矶的时钟在该绝对时间点上显示的时间。

在示例2中,我们做相反的事情,即取a timestamp并将其转换为a 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);
Run Code Online (Sandbox Code Playgroud)

您的示例的问题在于,您正在用单个列构造一个数据集。由于列只能具有一种类型,因此timestamptz即使计算出某些值timestamp(例如,值3),每一行(或在这种情况下为单个值)也将转换为同一类型。因此,您在此处有一个附加的隐式转换。

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

例子1

db=# SELECT timestamptz '2012-03-05 17:00:00+0';
      timestamptz
------------------------
 2012-03-05 17:00:00+00
Run Code Online (Sandbox Code Playgroud)

如您可能已经知道的,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
Run Code Online (Sandbox Code Playgroud)

现在,这是怎么回事?好吧,少于您最初的解释。该字符串被简单地解析为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
Run Code Online (Sandbox Code Playgroud)

那唯一改变的是如何timestamptz显示在屏幕上得到,即使用欧洲/柏林时区。

例子2

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

同样,仅解析日期。

例子3

db=# SELECT timestamp '2012-03-05 18:00:00+1';
      timestamp
---------------------
 2012-03-05 18:00:00
(1 row)
Run Code Online (Sandbox Code Playgroud)

这与相同'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)
Run Code Online (Sandbox Code Playgroud)

让我们更简单地重写:

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

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

例子5

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

让我们重写:

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

这是在问: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)
Run Code Online (Sandbox Code Playgroud)

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

db=# SELECT '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)
Run Code Online (Sandbox Code Playgroud)

我认为这很明显。

例子7

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

让我们重写:

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

首先,您将字符串解析为a timestamp,然后timestamptz使用当前设置将其转换为a timezone。如果更改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)
Run Code Online (Sandbox Code Playgroud)

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


我希望这可以使事情澄清。同样,理解之间的差别timestamp,并timestamptz为关键。考虑墙壁时间与绝对时间。