Postgresql:如何从时间戳,时区字段正确创建带时区的时间戳

Postgresql: how to correctly create timestamp with timezone from timestamp, timezone fields

我有一个没有时区的时间戳表。
YYYY-MM-DD HH:MM:SS

和一个字段"时区",太平洋为"P",山为"M"。

我需要创建一个"带时区的时间戳"字段

鉴于我有两个字段,有没有办法正确地解释夏令时?

特别:
时间戳:2013-11-03 01:00:00
时区:"P"
将成为:2013-11-03 01:00:00-07


时间戳:2013-11-03 03:00:00
时区:"P"
将成为:2013-11-03 03:00:00-08


如果你考虑他们的名字,timestamp without time zonetimestamp with time zone(TIMESTAMPTZ)之间的区别可能非常棘手。 (事实上??,规范似乎足够混乱,因此各种RDBMS以不同的方式实现它。)

在PostgreSQL中,两种类型都不存储值存储时的时区,但TIMESTAMPTZ基于UTC引用将值存储为精确的时刻,而timestamp without time zone始终是相对的。

  • 当查询时,将调整TIMESTAMPTZ以表示与最初存储的时间相同的时刻(在世界的任何一个部分中),作为客户端配置的当前时区中的瞬间。
  • A timestamp without time zone将始终与客户端配置的时区相同,即使您查询它的时区不同:2013-11-03 03:00:00表示的时刻将不明确并取决于客户端设置。

据推测,您使用timestamp without time zone的"时区"列(PM)来补偿输入值的模糊性。

原则上,如果您与存储时间戳的时区位于相同的相对时区,则应该返回相同的值,因此如果您已将客户端设置为US/Pacific时区并且已存储在P时区2013-11-03 03:00:00,你应该得到2013-11-03 03:00:00。但是,这仅在相对值没有歧义时才有效。

你的第一个例子中的问题是已经存在一些歧义:

timestamp: 2013-11-03 01:00:00 timezone:"P" would become: 2013-11-03
01:00:00-07

2013-11-03 01:00:00可以代表US/Pacific时区内的两个不同时刻,因此只需2013-11-03 01:00:00"P",您就已经丢失了无法恢复的信息。

如果您只是希望它在'-08'和'-07'之间切换,具体取决于该时刻的DST设置,这将自动为您完成,但您应该首先使用TIMESTAMPTZ ,确切地说,你代表的那个时刻。

这是一个保留初始时区的示例,因此您可以看到'-08'和'-07'之间的变化:

1
2
3
4
5
6
7
8
9
SET TIME zone 'US/Pacific';

SELECT t AS"Date/Time for US/Pacific",
       t AT TIME zone 'UTC'"Date/Time in UTC"
FROM (VALUES
    ('2013-11-03 00:00:00-07'::timestamptz),
    ('2013-11-03 01:00:00-07'::timestamptz),
    ('2013-11-03 02:00:00-07'::timestamptz),
    ('2013-11-03 03:00:00-07'::timestamptz)) AS v(t);

结果:

1
2
3
4
5
6
| DATE/TIME FOR US/PACIFIC | DATE/TIME IN UTC    |
|--------------------------|---------------------|
| 2013-11-03 00:00:00-07   | 2013-11-03 07:00:00 |
| 2013-11-03 01:00:00-07   | 2013-11-03 08:00:00 |
| 2013-11-03 01:00:00-08   | 2013-11-03 09:00:00 |
| 2013-11-03 02:00:00-08   | 2013-11-03 10:00:00 |

不幸的是,只有你的两个字段无法处理DST更改。

当然值得阅读PostgreSQL手册的日期/时间类型部分,并注意AT TIME ZONE文档中表格的"返回类型"列,以便更好地理解这些问题。


首先,当说结果将成为例如2013-11-03 01:00:00-07时,应该补充说这实际上取决于SQL客户端的时区设置。例如,欧洲时间的会话永远不会读取2013-11-03 01:00:00-07作为timestamp with time zone的值,因为没有欧洲国家GMT-07

也就是说,转换可以通过应用于timestamp without time zone的AT TIME ZONE构造来完成。

假设我们从US/Pacific时区运行:

1
2
3
4
5
6
7
8
SET TIME zone 'US/Pacific';

SELECT t AT TIME ZONE
     CASE z WHEN 'P' THEN 'US/Pacific' WHEN 'M' THEN 'US/Mountain' END  
  FROM (VALUES
    ('2013-11-03 01:00:00'::TIMESTAMP, 'P'),
    ('2013-11-03 03:00:00'::TIMESTAMP, 'P')
  ) AS v(t,z);

结果是:

1
2
3
4
        timezone        
------------------------
 2013-11-03 01:00:00-08
 2013-11-03 03:00:00-08

2013-11-03 01:00:00 AT time zone 'US/Pacific'具有歧义,因为它属于在-07时区中首先发生的小时跨度,然后在DST切换之后在-08时区中第二次发生。 postgres的解释是在-08时区看到它。如果我们考虑前一分钟,它会落入-07时区。


检查这是否对您有意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SET timezone TO 'PST8PDT';

SELECT now();
              now              
-------------------------------
 2013-09-28 03:24:20.169189-07

SELECT ts,
    ts at TIME zone 'PST' AS"PST",
    ts at TIME zone 'PDT' AS"PDT"
FROM (VALUES
    ('2013-11-03 01:00:00'::TIMESTAMP),
    ('2013-11-03 02:00:00'),
    ('2013-11-03 03:00:00')
) s (ts)
;
         ts          |          PST           |          PDT          
---------------------+------------------------+------------------------
 2013-11-03 01:00:00 | 2013-11-03 01:00:00-08 | 2013-11-03 01:00:00-07
 2013-11-03 02:00:00 | 2013-11-03 02:00:00-08 | 2013-11-03 01:00:00-08
 2013-11-03 03:00:00 | 2013-11-03 03:00:00-08 | 2013-11-03 02:00:00-08