在 PostgreSQL 中正确处理 TIME WITH TIME ZONE
Properly handle TIME WITH TIME ZONE in PostgreSQL
我们有一个 table,其中填充了来自另一个系统的遗留报告的数据。该 table 的列反映了报告的相同结构。
这里是 table 的缩写结构:
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
);
我们正在重构此 table 以处理不同客户的不同时区。新结构类似于:
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 格式。但是我们按照数据来的方式导入数据。
- 报告中的字段根据客户端的时区进行格式化,因此要重构此 table 我们需要结合客户端的时区(我们有此信息)以正确插入 timestamps/times 在 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 ZONE
(Postgres time with time zone equality) 类型,现在我对重构这个 [=51 的最佳方法是什么感到有点困惑=] 来处理不同的时区和处理我们需要的不同的列操作。
除此之外,我已经知道减去 TIMESTAMP WITH TIME ZONE
类型的两列是安全的。这个减法运算考虑了夏令时的变化(),但其他的呢?还有从 TIMESTAMP 中减去 TIME 的那个?
关于 table 重构,我们是否应该使用 TIME WITH TIME ZONE
?我们应该继续使用 TIME WITHOUT TIME ZONE
吗?或者最好完全忘记类型 TIME
并将 DATE 与 TIME 结合起来并将列更改为 TIMESTAMP WITH TIME ZONE
?
我认为这些问题是相关的,因为我们选择使用的新列类型将定义我们如何操作这些列。
您断言:
every TIME column represents a moment during the day specified in 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 规则 (daylight saving time).
所以基本上你已经拥有的东西。只需删除 start_hour
中的日期部分,即空运费。将 timestamp
转换为 time
以截断日期。喜欢:(timestamp '2018-03-25 1:00:00')::time
tz
可以是 AT TIME ZONE
construct, but to deal with different time zones reliably, it's best to use time zone names exclusively. Any name
you find in the system catalog pg_timezone_names
.
接受的任何字符串
要优化存储,您可以在小型查找中收集允许的时区名称 table 并将 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 以便根据需要随时显示字符串。 table 用于存储您 需要 的信息。
注意括号!否则运算符 +
将在 AT TIME ZONE
之前绑定,因为 operator precedence.
看看结果:
db<>fiddle here
由于时间在维也纳被操纵(就像任何应用愚蠢的 DST 规则的地方一样),您会得到“令人惊讶”的结果。
相关:
- Accounting for DST in Postgres, when selecting scheduled items
- Ignoring time zones altogether in Rails and PostgreSQL
我们有一个 table,其中填充了来自另一个系统的遗留报告的数据。该 table 的列反映了报告的相同结构。
这里是 table 的缩写结构:
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
);
我们正在重构此 table 以处理不同客户的不同时区。新结构类似于:
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 格式。但是我们按照数据来的方式导入数据。
- 报告中的字段根据客户端的时区进行格式化,因此要重构此 table 我们需要结合客户端的时区(我们有此信息)以正确插入 timestamps/times 在 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 ZONE
(Postgres time with time zone equality) 类型,现在我对重构这个 [=51 的最佳方法是什么感到有点困惑=] 来处理不同的时区和处理我们需要的不同的列操作。
除此之外,我已经知道减去 TIMESTAMP WITH TIME ZONE
类型的两列是安全的。这个减法运算考虑了夏令时的变化(
关于 table 重构,我们是否应该使用 TIME WITH TIME ZONE
?我们应该继续使用 TIME WITHOUT TIME ZONE
吗?或者最好完全忘记类型 TIME
并将 DATE 与 TIME 结合起来并将列更改为 TIMESTAMP WITH TIME ZONE
?
我认为这些问题是相关的,因为我们选择使用的新列类型将定义我们如何操作这些列。
您断言:
every TIME column represents a moment during the day specified in
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 规则 (daylight saving time).
所以基本上你已经拥有的东西。只需删除 start_hour
中的日期部分,即空运费。将 timestamp
转换为 time
以截断日期。喜欢:(timestamp '2018-03-25 1:00:00')::time
tz
可以是 AT TIME ZONE
construct, but to deal with different time zones reliably, it's best to use time zone names exclusively. Any name
you find in the system catalog pg_timezone_names
.
要优化存储,您可以在小型查找中收集允许的时区名称 table 并将 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 以便根据需要随时显示字符串。 table 用于存储您 需要 的信息。
注意括号!否则运算符 +
将在 AT TIME ZONE
之前绑定,因为 operator precedence.
看看结果:
db<>fiddle here
由于时间在维也纳被操纵(就像任何应用愚蠢的 DST 规则的地方一样),您会得到“令人惊讶”的结果。
相关:
- Accounting for DST in Postgres, when selecting scheduled items
- Ignoring time zones altogether in Rails and PostgreSQL