Accounting for DST in Postgres, when selecting scheduled items
我有一个Postgres时钟闹钟表(不是真的,但这是类似的,更容易解释)。用户以1小时的分辨率设置警报,用户可以来自许多不同的时区。警报每天重复。我想要可靠地获取在一天中的特定时刻应该关闭的警报,并且我遇到夏令时问题。我该如何以最好的方式做到这一点?
例
Alfred and Lotta both live in Stockholm (+1 hour from UTC, but +2h
when it's DST). Sharon lives in Singapore (+8 hours from UTC, no
DST)During winter, Alfred sets an alarm for 4 AM. The alarm should go off
at 4 AM local time, all year. During summer, Lotta sets an alarm
for 5 AM. Again, it should go off at 5 AM all year round.
Meanwhile, Sharon has set an alarm for 11 AM.All of these can be stored in the database as 03:00 UTC.
If I query the database in the winter for alarms that should go off at
03:00 UTC, I want Alfred's and Sharon's alarms. Singapore is now +7h
from Sweden, so 11 AM in Singapore is 4 AM in Sweden. Lotta's alarm
should not go off for another hour.Conversely, if I query the database in the summer for alarms that
should go off at 03:00 UTC, I want Lotta's and Sharon's alarms.
Singapore is +6h from Sweden now, so 11 AM in Singapore is 5 AM in
Sweden now. Sven's alarm went off an hour ago.
我如何存储它,并查询数据库?
如有必要,我可以更改数据库架构。目前,我们根本没有调整DST,事实上只有一个"小时"整数字段(看似愚蠢,时间字段会更好)。
我似乎需要存储UTC时间和时区信息,但我不知道如何在Postgres中最好地实现这一点。我发现Postgres有一些时区概念,但据我所知,没有时区字段类型。此外,我想我需要在SQL中进行一些计算,以确定如何根据时区数据和创建日期来偏移select中的UTC时间。我对SQL不太满意......
我确实希望在Postgres中解决这个问题,因为可能存在很多"警报",我想避免将所有这些问题都引入Ruby并在那里过滤所带来的性能问题。 (是的,这是一个Rails应用程序。)
使用
警报的时间可以是
但是你必须为每一行明确保存时区。
永远不要使用
The type
time with time zone is defined by the SQL standard, but the
definition exhibits properties which lead to questionable usefulness.
In most cases, a combination ofdate ,time ,timestamp without timezone ,
andtimestamp with time zone should provide a complete range of
date/time functionality required by any application.
演示设置:
1 2 3 4 5 | 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。有关:
- 应用于时间戳时,具有相同属性的时区名称会产生不同的结果
获取"今天"的匹配警报:
1 2 3 4 | 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' ...按UTCtimestamp 获取 -
::time ...提取时间组件的最简单方法。
在这里,您可以查找时区名称:
1 2 3 4 | SELECT * FROM pg_timezone_names WHERE name ~~* '%sing%' LIMIT 10 |
SQL Fiddle演示夏季/冬季。
您可以使用全时区名称来完成此操作,例如: America / New_York而不是EDT / EST,并且在该时区存储小时而不是UTC。然后,您可以对夏令时的偏移变化保持无知。
像下面这样的东西应该工作:
1 2 3 4 5 6 7 8 9 10 11 12 | -- 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; |
要么:
1 2 3 | SELECT user_to_alert FROM time_test WHERE EXTRACT(HOUR FROM CURRENT_TIME AT TIME ZONE user_timezone) = EXTRACT(HOUR FROM alarm_hour); |
鉴于:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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'); |
尝试:
1 2 3 | SELECT username FROM tzdemo WHERE alarm_time_utc AT TIME ZONE alarm_tz_abbrev = TIME '03:00' AT TIME ZONE alarm_tz; |
结果:
1 2 3 4 5 | username ---------- Alfred Sharon (2 ROWS) |
原理:
- 存储创建警报的时区偏移量,包括当时是否为DST
- 还存储转换为UTC的时钟时间
- 查询时,请使用全时区域名称遵循当前UTC规则的时间,以生成该区域当前时区的时间。与创建警报时的时区内存储的时间戳进行比较。
这也允许您应对用户更改位置的情况,从而更改时区。
当您想要进行预测性查询时,可以通过日期限定时间戳来扩展此方法,例如"在当地时间将在位置发出警报声"。
我对这个解决方案并不完全有信心,建议仔细测试。