在Postgres中将时间戳转换为特定时区的时间戳

Not*_* ID 3 postgresql timezone

我有一个表,其中有一列的数据类型为timestamp without time zone,我试图将其转换为给定时区的没有时区的时间戳。

这些是我的 Postgres 9.3 设置

select current_setting('TIMEZONE');
 current_setting
-----------------
 Hongkong

select * from  pg_timezone_names where name = 'Hongkong';
   name   | abbrev | utc_offset | is_dst
----------+--------+------------+--------
 Hongkong | HKT    | 08:00:00   | f
Run Code Online (Sandbox Code Playgroud)

这是我将其转换为 HKT 的方法:

-- this SQL gives me what I expected
select '2015-01-05 12:00:00'::timestamp without time zone
at time zone 'UTC'
at time zone 'HKT';
---------------------
 2015-01-05 20:00:00

-- Shouldn't this produce the same result with the above one?
-- How do I make this work?
-- Don't tell me to change it to UTC-08:00 ...
select '2015-01-05 12:00:00'::timestamp without time zone
at time zone 'UTC'
at time zone 'UTC+08:00';
---------------------
 2015-01-05 04:00:00 -- WHY?
Run Code Online (Sandbox Code Playgroud)

poz*_*ozs 5

这背后的原因是为什么你真的不应该在 PostgreSQL 中使用时区偏移(除非你确切地知道你在做什么)。

时区'UTC+08:00'是 POSIX 样式的时区规范,'Hongkong'是准确的时区名称,并且'HKT'是其缩写(之一)。

系统pg_timezone_names视图的utc_offset定义为距UTC 的偏移量(正数表示格林威治以东) 。

但在 POSIX 风格的时区规范中,偏移量部分是不同的

...另一个需要记住的问题是,在 POSIX 时区名称中,正偏移量用于格林威治以西的位置。在其他地方,PostgreSQL 遵循 ISO-8601 约定,即正时区偏移位于格林威治以东

因此,您不应使用偏移量(作为间隔)或 POSIX 风格的时区规范,而应使用:

  • 时区缩写,如果您想自己处理夏令时规则,
  • 准确的时区名称,其他地方(首选)。

简而言之,这就是缩写和全名之间的区别:缩写代表相对于 UTC 的特定偏移量,而许多全名则暗示当地夏令时规则,因此有两个可能的 UTC 偏移量。

更复杂的是,一些司法管辖区使用相同的时区缩写来表示不同时间的不同 UTC 偏移量;例如,在莫斯科,MSK 在某些年份表示 UTC+3,在其他年份表示 UTC+4。PostgreSQL 根据这些缩写在指定日期的含义(或最近的含义)来解释它们;但是,与上面的 EST 示例一样,这不一定与该日期的当地民用时间相同。

但最简单的解决方案是使用timestamp with time zone:您已经将设置设置TimeZone'Hongkong',因此timestamp with time zone值将在该时区显示给您的(PostgreSQL)客户端。

set time zone 'Hongkong';

select current_setting('TimeZone') TimeZone,
       dt original,
       dt AT TIME ZONE 'UTC' AT TIME ZONE 'UTC+08:00' "UTC+08:00",
       dt AT TIME ZONE 'UTC' AT TIME ZONE 'UTC+8' "UTC+8",
       dt AT TIME ZONE 'UTC' AT TIME ZONE 'UTC-8' "UTC-8",
       dt AT TIME ZONE 'UTC' AT TIME ZONE INTERVAL '+08:00' "INTERVAL +08:00",
       dt AT TIME ZONE 'UTC' AT TIME ZONE 'HKT' "HKT",
       dt AT TIME ZONE 'UTC' AT TIME ZONE 'Hongkong' "Hongkong",
       dt AT TIME ZONE 'UTC' "with time zone"
from (values (timestamp '2015-01-05 12:00:00')) v(dt);

-- TIMEZONE | ORIGINAL            | UTC+08:00
-- ---------+---------------------+--------------------
-- Hongkong | 2015-01-05 12:00:00 | 2015-01-05 04:00:00

-- UTC+8               | UTC-8               | INTERVAL +08:00
-- --------------------+---------------------+--------------------
-- 2015-01-05 04:00:00 | 2015-01-05 20:00:00 | 2015-01-05 20:00:00

-- HKT                 | HONGKONG            | WITH TIME ZONE
-- --------------------+---------------------+-----------------------
-- 2015-01-05 20:00:00 | 2015-01-05 20:00:00 | 2015-01-05 20:00:00+08
Run Code Online (Sandbox Code Playgroud)

SQLFiddle