PostgreSQL 9.3.5与从目标值中提取纪元时的8.3.6的差异

时间:2015-01-07 23:53:38

标签: timestamp postgresql-9.3

我们有一个包含时间戳作为字符串的表,并且已经使用 extract 在PostgreSQL 8.3.6服务器上检索其纪元:

select '2015/01/07 14:00:00' as the_timestamp, 
extract(epoch from cast('2015/01/07 14:00:00' as timestamp)) as the_epoch;

    the_timestamp    | the_epoch
---------------------+------------
 2015/01/07 14:00:00 | 1420668000
(1 row)

我们终于升级了,并且有一台运行PostgreSQL 9.3.5的服务器,现在得到了不同的结果:

select '2015/01/07 14:00:00' as the_timestamp, 
extract(epoch from cast('2015/01/07 14:00:00' as timestamp)) as the_epoch;

    the_timestamp    | the_epoch
---------------------+------------
 2015/01/07 14:00:00 | 1420639200         <<=== this is 8 hours earlier
(1 row)

这两个示例都使用 psql 作为客户端,两者都使用相同的时区:

show timezone;

      TimeZone
---------------------
 America/Los_Angeles
(1 row)

在PostgreSQL 9.3 documentation中,我发现了这个:

  

注意:SQL标准要求只写 timestamp   相当于没有时区的时间戳,而PostgreSQL也是如此   行为。 (7.3之前的版本将其视为时间戳随时间变化   区域。) timestamptz 被接受为时间戳的缩写   时区;这是一个PostgreSQL扩展。

我发现如果我在9.3服务器上更改查询以使用 timestamptz ,它会得到与8.3相同的结果:

select '2015/01/07 14:00:00' as the_timestamp, 
extract(epoch from cast('2015/01/07 14:00:00' as timestamp)) as the_epoch;

    the_timestamp    | the_epoch
---------------------+------------
 2015/01/07 14:00:00 | 1420668000
(1 row)

请注意, timestamp timestamptz 在8.3上提供相同的结果:

select extract(epoch from cast('2015/01/07 14:00:00' as timestamptz));
 date_part
------------
 1420668000
(1 row)

select extract(epoch from cast('2015/01/07 14:00:00' as timestamp));
 date_part
------------
 1420668000
(1 row)

好像我们在9.3中发现了一个错误?似乎提取这种方式错误地假设带有时区,当它不应该时。

1 个答案:

答案 0 :(得分:2)

TIMESTAMP WITH TIME ZONE并不代表您认为的含义。不幸的是,它并不意味着&#34;取这个时间戳并将其与相关的时区一起存储为字段中的两个单独的值&#34;。相反,它被PostgreSQL视为&#34;采用此时间戳,您应该假设它是在本地时间,除非它有时区说明符,并将其转换为UTC,然后将其存储为UTC。将其转换回当地时间进行显示。&#34;

实际使用时区信息,然后在导入时丢弃,使TIMESTAMP WITH TIME ZONE成为一个可怕的误称。

您遇到的问题是timestamp with time zone的纪元是 UTC ,而非本地时间,纪元。由于时间戳假定为本地时间,除非您指定时区说明符,这意味着TimeZone会影响输入的解释。

详细

当你写:

cast('2015/01/07 14:00:00' as timestamp)

或文字的等价物:

TIMESTAMP '2015/01/07 14:00:00'

你说&#34;时间戳&#39; 2015/01/07 14:00:00&#39;作为挂钟时间的一个点,没有定义时区。&#34;本地TimeZone不会影响它。假设时间段与时间戳在同一时区,无论是什么。这就是设置TimeZone对它没有影响的原因:

regress=# SET TimeZone = 'Australia/Perth';
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp));
 date_part  
------------
 1420639200
(1 row)

regress=# SET TimeZone = UTC;
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp));
 date_part  
------------
 1420639200
(1 row)

现在,当您改为使用timestamp with time zone时,您会说时间戳是本地时间,除非另有说明。它将被导入并转换为UTC以进行内部存储。然后它按照TimeZone的定义转换回本地时间,用于显示/输出。

时代是UTC,而不是当地时间。

这就是为什么会发生这种情况:

regress=# SET TimeZone = 'Australia/Perth';
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp with time zone));
 date_part  
------------
 1420610400
(1 row)

regress=# SET TimeZone = UTC;
SET
regress=# SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp with time zone));
 date_part  
------------
 1420639200
(1 row)

extract结果不同的原因是输入时间戳值不同。它的值相同,但在读取和加载值时会考虑TimeZone。如果你在表格中查看它会更有意义:

CREATE TABLE myts (ts timestamp without time zone, tstz timestamp with time zone);

SET TimeZone = UTC;
INSERT INTO myts(ts,tstz) VALUES ('2015/01/07 14:00:00','2015/01/07 14:00:00');
SET TimeZone = 'Australia/Perth';
INSERT INTO myts(ts,tstz) VALUES ('2015/01/07 14:00:00','2015/01/07 14:00:00');

现在看看内容:

regress=# Set TimeZone = UTC;
SET
regress=# SELECT * FROM myts;
         ts          |          tstz          
---------------------+------------------------
 2015-01-07 14:00:00 | 2015-01-07 14:00:00+00
 2015-01-07 14:00:00 | 2015-01-07 06:00:00+00
(2 rows)

和时代:

regress=# SELECT extract(epoch from ts) as ets, extract(epoch from tstz) as etstz FROM myts;
    ets     |   etstz    
------------+------------
 1420639200 | 1420639200
 1420639200 | 1420610400
(2 rows)

正如您所看到的那样输入会影响事物,而不是输出。

使用显式时区怎么样?

现在,如果我们在输入中设置显式时区怎么办?

SET TimeZone = UTC;

INSERT INTO myts(ts,tstz) VALUES ('2015/01/07 14:00:00 +8','2015/01/07 14:00:00 +8');

您会看到效果与TimeZone设置为Australia/Perth的效果相同,即输入会忽略本地TimeZone设置,因为时间戳包含显式时区。

虽然有时区和没有时区,但仍会产生不同的时间戳。时区限定符从timestamp字段中被丢弃,之前用于转换 timstamptz字段。

(是的,timestamp上的时区被丢弃的事实很可怕。有很多关于SQL时间的可怕事情。)

那么你如何得到理想的结果?

如果您想要当地时间,请使用timestamp,而不是通用时间点。

或者,告诉PostgreSQL你想要timestamptz的纪元,而不是转换回本地时间,即UTC,通过使用AT TIME ZONE运算符将其重新解释为UTC的时间戳:

SELECT extract(epoch from cast('2015/01/07 14:00:00' as timestamp) AT TIME ZONE 'UTC');

或者只需在TimeZone设置为UTC的情况下运行您的服务器。坦率地说,这是大多数人所做的事情,因为TimeZonetimestamp vs timestamptz的语义在大多数情况下都不是很有用。

为什么8.3不同?

不知道,我不得不深入挖掘更多的发行说明和提交日志,而不是我有时间。看起来timstamptz输入已更改为尊重TimeZone,但我不知道当时的具体理由何时或是什么。