Postgre中时区之间的转换

时间:2018-01-02 23:51:09

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

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

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

示例1

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

根据文章和我的理解,因为'2016-01-01 00:00'函数的timezone部分只是一个字符串,所以它会以静默方式转换为默认的UTC。因此,'2016-01-01 00:00' UTC根据US/Pacific函数的要求将timezone转换为2015-12-31 16:00:00,即db=# SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamp); outputs 2016-01-01 08:00:00+00

示例2

'2016-01-01 00:00'::timestamp

对不起,我不明白为什么,那里的解释没有帮助。好的,timezone函数的US/Pacific部分不再是字符串,而是实际的时间戳。在什么时区?如果是UTC,则输出必须与示例1相同。因此它会自动转换为US/Pacific?然后输出是UTC?但为什么?我在timezone而不是UTC中要求timezone

请解释```{r, results='asis', eval=(opts_knit$get('rmarkdown.pandoc.to') == 'latex')} cat('\\pagebreak') ``` 在获取时间戳并被要求转换时的行为方式。谢谢。

2 个答案:

答案 0 :(得分:4)

让我解释两个例子:

我们假设时区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之间来回转换:

enter image description here

由于我们将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'::timestamp是一个timestamp,即一个待机时间。它没有时区概念。

我认为您可能还没有完全理解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(例如值3),每一行(在这种情况下为单个值)也会转换为相同的类型,即timestamp。 )。因此,您在此处有一个额外的隐式转换。

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

示例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)

这就是问:时区墙上的时钟偏差为+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)

让我们改写:

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,然后使用当前设置的timestamptz将其转换为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)

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

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

答案 1 :(得分:1)

这是我的理解。请光临我。
我的默认时区,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-not-care-I-will-pre-this-my-my-default-UTC-input to UTC save to UTC output。

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-not-care-I-will-pre-this-my-my-default-UTC-input to UTC save to UTC output。

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-not-care-I-will-pre-this-my-my-default-UTC-input to UTC save to UTC output

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

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');
时间不知情的数据,但我可以将其转换为时间感知。根据{{​​3}},由于没有时区信息,因此可以在默认的UTC时区中对其进行解析。 因此,时间感知的UTC数据按原样保存,但在输出之前将其转换为US/Pacific。这就是为什么article说"我们在2016-01-01 00:00 UTC获得加利福尼亚的时间。 "对于2015-12-31 16:00:00 UTC输入,输出为'2016-01-01 00:00',即加利福尼亚州的预留时间。

article也说"请注意,我们将时间戳作为字符串传递,该字符串被隐式地转换为时间戳"。
这可以写成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,我将根据'2016-01-01 00:00'US/Pacific转换回UTC。添加8小时从太平洋到UTC。我的UTC现在是2016-01-01 08:00:00+00。按原样保存。输出2016-01-01 08:00:00+00因为,I-dont-know-I-not-care-I-will-pre-this-my-my-default-UTC-input to UTC save to UTC output

同样,根据article" timezone(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输出。

由于