PostgreSQL:如何正确地从时间戳和时区字段创建带时区的时间戳

4

我有一个没有时区的时间戳表格,格式为 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


您的编辑后问题更加明确,但根本问题仍然存在。 "2013-11-03 01:00:00" 仍然是不明确的:它可以是在 "2013-11-03 00:59:00-07" 后的一分钟,也可以是在 "2013-11-03 01:59:00-07" 后的一分钟。 - Bruno
3个回答

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

话虽如此,可以使用应用于timestamp without time zoneAT TIME ZONE构造进行转换。

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

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);

结果如下:

结果是:

        时区        
------------------------
 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'存在歧义,因为它属于在DST切换后首先发生的-07时区中的小时跨度,然后在第二次发生在-08时区。Postgres的解释是将其视为-08时区。如果我们考虑之前的一分钟,则会落入-07时区。


1
在编程中,TIMESTAMP WITHOUT TIME ZONETIMESTAMP WITH TIME ZONE (TIMESTAMPTZ)之间的区别可能很棘手,如果考虑它们的名称。实际上,规范似乎足够混乱,以至于各种关系型数据库管理系统以不同的方式实现它。
在PostgreSQL中,两种类型都不存储值存储时的时区,但是TIMESTAMPTZ将该值存储为基于UTC参考的精确时间点,而TIMESTAMP WITHOUT TIME ZONE始终是相对的。
当查询时,TIMESTAMPTZ将被调整为表示与最初存储的相同的时间点(无论这是世界上的哪个部分),因为它在客户端配置的当前时区中的时间点。 TIMESTAMP WITHOUT TIME ZONE将始终相对于客户端配置的时区是相同的值,即使您从中查询它的时区不同:由2013-11-03 03:00:00所表示的时间点将是模糊的,并取决于客户端设置。
假设您使用了“时区”列(PM)和TIMESTAMP WITHOUT TIME ZONE来补偿输入值中的歧义。原则上,如果您与存储时间戳的相对时区相同,则应该得到相同的值,因此,如果您将客户端设置为US/Pacific时区,并且在P时区中存储了2013-11-03 03:00:00,则应该返回2013-11-03 03:00:00。然而,只有在相对值没有歧义时才有效。您第一个示例中的问题是已经存在一些歧义:时间戳为2013-11-03 01:00:00,时区为“P”,将变为2013-11-03 01:00:00-07。

2013-11-03 01:00:00US/Pacific 时区可以表示两个不同的时间点,因此仅有 2013-11-03 01:00:00"P",您已经丢失了无法恢复的信息。

如果您只想在那个特定时间切换为“-08”和“-07”,这将自动完成,但您应该一开始就使用 TIMESTAMPTZ,以精确表示您所代表的时间点。

以下是一个示例,保留了初始时区,因此您可以看到“-08”和“-07”的更改:

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);

结果:

| 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文档表格中的“返回类型”列,可以更好地理解这些问题。


0

请确认您是否理解此内容

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接