我有一个 Postgres 时钟闹钟表(不是真的,但这是类似的,更容易解释)。用户以1小时的分辨率设置警报,用户可以来自许多不同的时区。警报每天重复。我想要可靠地获取应该在一天中的特定时刻发出的警报,并且我遇到夏令时问题。我该如何以最好的方式做到这一点?
Alfred和Lotta都住在斯德哥尔摩(距UTC约+1小时,但+ 2小时 当它是DST)。
Sharon住在新加坡(距UTC约8小时,没有 DST)在冬季,阿尔弗雷德在凌晨4点发出警报。警报应该响起 当地时间凌晨4点,全年。
夏季,洛塔发出警报 凌晨5点。它应该全年都在凌晨5点出发 与此同时,沙龙已经在上午11点发出警报。所有这些都可以03:00 UTC存储在数据库中。
如果我在冬天查询数据库,发现应该发出的警报 03:00 UTC,我想要阿尔弗雷德和莎朗的警报。新加坡现在+ 7小时 来自瑞典,所以新加坡上午11点在瑞典凌晨4点。洛塔的警报 不应该再去一个小时。
相反,如果我在夏天查询数据库以获取警报 应该在UTC时间03:00开始,我想要Lotta和Sharon的警报。 新加坡距离瑞典仅6小时,因此新加坡上午11点是凌晨5点 瑞典现在。斯文的闹钟在一小时前响起。
如何存储,查询数据库?
如有必要,我可以更改数据库架构。目前,我们根本没有调整DST,事实上只有一个小时""整数字段(看似愚蠢,时间字段会更好)。
似乎我需要存储UTC时间和时区信息,但我不知道如何在Postgres中实现这一目标。我发现Postgres有一些时区概念,但据我所知,没有时区字段类型。此外,我想我需要在SQL中进行一些计算,以确定如何根据时区数据和创建日期来偏移select中的UTC时间。我对SQL不太好......
我确实希望在Postgres中解决这个问题,因为可能有很多"警报"并且我想避免将所有这些问题都提取到Ruby并在那里过滤所带来的性能问题。 (是的,这是一个Rails应用程序。)
答案 0 :(得分:12)
使用timestamp with time zone
(timestamptz
)进行计算
警报的时间可以是time [without time zone]
但是你必须为每一行明确保存时区。
从不使用time with time zone
这是一个逻辑上破坏的类型,PostgreSQL不鼓励使用它。 The manual:
类型
time with time zone
由SQL标准定义,但是 定义表现出导致可疑用途的特性。 在大多数情况下,date
,time
,timestamp without timezone
的组合,timestamp with time zone
应提供完整的范围 任何应用程序所需的日期/时间功能。
演示设置:
CREATE TABLE alarm(name text, t time, tz text);
INSERT INTO alarm VALUES
('Alfred', '04:00', 'Europe/Stockholm') -- Alfred sets an alarm for 4 AM.
, ('Lotta', '05:00', 'Europe/Stockholm') -- Lotta sets an alarm for 5 AM.
, ('Sharon', '11:00', 'Asia/Singapore'); -- Sharon has set an alarm for 11 AM.
必须是时区名称(而不是缩写)来计算DST。相关:
获取“今天”的匹配警报:
SELECT *
FROM alarm
WHERE (('2012-07-01'::date + t) AT TIME ZONE tz AT TIME ZONE 'UTC')::time
= '03:00'::time
('2012-7-1'::date + t)
...汇编timestamp [without time zone]
对于“今天”也可以 now()::date + t
。AT WITH TIME ZONE tz
...将时间戳放在保存的时区,结果为timestamptz
。AT WITH TIME ZONE 'UTC'
...按照timestamp
::time
...提取时间组件的最简单方法。您可以在此处查找time zone names:
SELECT *
FROM pg_timezone_names
WHERE name ~~* '%sing%'
LIMIT 10
SQL Fiddle展示了夏季/冬季。
答案 1 :(得分:6)
您可以使用全时区名称来完成此操作,例如: America / New_York而不是EDT / EST,并且在该时区存储小时而不是UTC。然后,您可以对夏令时的偏移变化保持无知。
以下内容应该有效:
-- CREATE TABLE time_test (
-- user_to_alert CHARACTER VARYING (30),
-- alarm_hour TIME,
-- user_timezone CHARACTER VARYING (30)
-- );
SELECT user_to_alert,
CASE
WHEN EXTRACT(HOUR FROM CURRENT_TIME AT TIME ZONE user_timezone) = EXTRACT(HOUR FROM alarm_hour) THEN TRUE
ELSE FALSE
END AS raise_alarm
FROM time_test;
或者:
SELECT user_to_alert
FROM time_test
WHERE EXTRACT(HOUR FROM CURRENT_TIME AT TIME ZONE user_timezone) = EXTRACT(HOUR FROM alarm_hour);
答案 2 :(得分:2)
假设:
SET timezone = 'UTC';
CREATE TABLE tzdemo (
username text not null,
alarm_time_utc time not null,
alarm_tz_abbrev text not null,
alarm_tz text not null
);
INSERT INTO tzdemo (username, alarm_time_utc, alarm_tz_abbrev, alarm_tz) VALUES
('Alfred', TIME '04:00' AT TIME ZONE '+01:00', 'CET', 'Europe/Stockholm'),
('Lotta', TIME '05:00' AT TIME ZONE '+02:00', 'CEST', 'Europe/Stockholm'),
('Sharon', TIME '11:00' AT TIME ZONE '+08:00', 'SGT', 'Singapore');
尝试:
SELECT username
FROM tzdemo
WHERE alarm_time_utc AT TIME ZONE alarm_tz_abbrev = TIME '03:00' AT TIME ZONE alarm_tz;
结果:
username
----------
Alfred
Sharon
(2 rows)
原理:
这也可以让您应对用户更改位置的情况,从而更改时区。
当您想要进行预测性查询时,可以通过日期限定时间戳来扩展此方法,例如“在当地时间将在位置发出警报”。
我对这个解决方案并不完全有信心,建议仔细测试。