在PostgreSQL中正确处理TIME WITH TIME ZONE

时间:2018-05-10 16:10:43

标签: postgresql timezone dst date-arithmetic

我们有一个表格,其中包含来自另一个系统的旧报告的数据。该表的列反映了报告的相同结构。

以下是表格的缩写结构:

CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
  REPORT_DATE DATE NOT NULL,
  EVENT_ID BIGINT PRIMARY KEY NOT NULL,
  START_HOUR TIMESTAMP WITHOUT TIME ZONE,
  END_HOUR TIME WITHOUT TIME ZONE,
  EXPECTED_HOUR TIME WITHOUT TIME ZONE
);

我们正在重构此表以处理不同客户的不同时区。新结构将类似于:

CREATE TABLE IF NOT EXISTS LEGACY_TABLE (
  REPORT_DATE DATE NOT NULL,
  EVENT_ID BIGINT PRIMARY KEY NOT NULL,
  START_HOUR TIMESTAMP WITH TIME ZONE,
  END_HOUR TIME WITH TIME ZONE,
  EXPECTED_HOUR TIME WITH TIME ZONE
);

这些小时字段表示REPORT_DATE列所代表的当天的特定时间点。我的意思是每个TIME列代表REPORT_DATE指定的那一天。

其他一些要考虑的要点:

  • 我们不知道为什么START_HOUR在我们从旧系统收到的报告中采用TIMESTAMP格式。但我们按照它的方式导入数据。
  • 报告中的字段根据客户端的时区进行格式化,因此要重构此表,我们需要将客户端的时区(我们有此信息)组合在一起,以UTC格式正确插入时间戳/时间。

但现在问题。这些列的值用于在我们的系统中多次计算另一个值,如下所示:

START_HOUR - END_HOUR (the result of this operation is currently being casted to TIME WITHOUT TIME ZONE)
START_HOUR < END_HOUR
START_HOUR + EXPECTED_HOUR
EXPECTED_HOUR - END_HOUR
EXPECTED_HOUR < '05:00' 

经过一些研究后我发现不建议使用类型TIME WITH TIME ZONEPostgres time with time zone equality),现在我有点困惑的是重构这个表来处理不同的最佳方法是什么时区并处理我们需要的不同列操作。

除此之外,我已经知道可以安全地减去两列TIMESTAMP WITH TIME ZONE类型的列。这个减法操作考虑了DST的变化(Subtracting two columns of type timestamp with time zone),但其他的呢?那个从TIMESTAMP中减去一个TIME的那个?。

关于表格重构,我们应该使用TIME WITH TIME ZONE吗?我们应该继续使用TIME WITHOUT TIME ZONE吗?或者最好完全忘记类型TIME并将DATE与TIME结合并将列更改为TIMESTAMP WITH TIME ZONE

我认为这些问题是相关的,因为我们选择使用的新列类型将定义我们如何使用列进行操作。

1 个答案:

答案 0 :(得分:2)

你声称:

  

每个TIME列代表REPORT_DATE中指定的一天中的时刻。

所以你从不越过同一行内的日期行。我建议保存1x date 3x time时区(作为text或FK列):

CREATE TABLE legacy_table (
   event_id      bigint PRIMARY KEY NOT NULL
 , report_date   date NOT NULL
 , start_hour    time
 , end_hour      time
 , expected_hour time
 , tz            text  -- time zone
);

就像你已经找到的那样,timetz (time with time zone) should generally be avoided。它不能处理DST规则( d aylight s aving t ime)。

所以基本上就是你之前已经拥有的。只需从start_hour中删除日期组件,这就是死货。简单地将时间戳转换为时间来切断日期。喜欢:(timestamp '2018-03-25 1:00')::time

tz可以是AT TIME ZONE构造接受的任何字符串,但为了可靠地处理不同的时区,最好只使用时区名称。您在system catalog pg_timezone_names中找到的任何name

要优化存储,您可以在小型查找表中收集允许的时区名称,并将tz text替换为tz_id int REFERENCES my_tz_table

有和没有DST的两个示例行:

INSERT INTO legacy_table VALUES
   (1, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Vienna')  -- sadly, with DST
 , (2, '2018-03-25', '1:00', '3:00', '2:00', 'Europe/Moscow'); -- Russians got rid of DST

出于表示目的或计算,您可以执行以下操作:

SELECT (report_date + start_hour)    AT TIME ZONE tz AT TIME ZONE 'UTC' AS start_utc
     , (report_date + end_hour)      AT TIME ZONE tz AT TIME ZONE 'UTC' AS end_utc
     , (report_date + expected_hour) AT TIME ZONE tz AT TIME ZONE 'UTC' AS expected_utc
-- START_HOUR - END_HOUR
     , (report_date + start_hour) AT TIME ZONE tz
     - (report_date + end_hour)   AT TIME ZONE tz AS start_minus_end
FROM   legacy_table;

您可以根据需要创建一个或多个views以便随时显示字符串。该表用于存储您所需的信息

请注意括号!否则,由于operator precedence,运算符+将在AT TIME ZONE之前绑定。

看到结果:

db&lt;&gt;小提琴here

由于时间在维也纳被操纵(以及任何其他适用愚蠢的DST规则的地方),你会得到“令人惊讶”的结果。

相关: