在选择预定项目时,在Postgres中计算DST
我有一个Postgres 时钟闹钟表(不是真的,但这是类似的,更容易解释)。 用户以1小时的分辨率设置警报,用户可以来自许多不同的时区。 警报每天重复。 我想要可靠地获取应该在一天中的特定时刻发生的警报,并且我遇到了夏令时问题 。 我该如何以最好的方式做到这一点?
例
阿尔弗雷德和洛塔都住在斯德哥尔摩(从UTC开始+1小时,但是在夏令时+ 2小时)。
Sharon住在新加坡(距离UTC +8小时,没有夏令时)在冬季,阿尔弗雷德在凌晨4点发出警报。 警报应该在当地时间凌晨4点全年开始。
夏季,洛塔在凌晨5点发出警报。 它应该全年都在凌晨5点出发。
与此同时,沙龙已经在上午11点发出警报。所有这些都可以03:00 UTC的forms存储在数据库中。
如果我在冬天查询数据库以获取应在03:00 UTC发出的警报,我想要Alfred和Sharon的警报。 新加坡现在离瑞典只有7小时,所以新加坡上午11点在瑞典凌晨4点。 洛塔的警报不应再持续一小时。
相反,如果我在夏天查询数据库以获取应在03:00 UTC发出的警报,我想要Lotta和Sharon的警报。 新加坡现在离瑞典只有6小时,所以新加坡上午11点在瑞典上午5点。 斯文的闹钟在一小时前响起。
如何存储,查询数据库?
如有必要,我可以更改数据库架构。 目前,我们根本没有调整DST,事实上只有一个“小时”整数字段(看起来很愚蠢,时间字段会更好)。
我似乎需要存储UTC时间和时区信息,但我不知道如何在Postgres中最好地实现这一点。 我发现Postgres有一些时区概念,但据我所知,没有时区字段类型。 另外,我想我需要在SQL中进行一些计算,以确定如何根据时区数据和创建日期来偏移select中的UTC时间。 我对SQL不太满意……
我确实想在Postgres中解决这个问题,因为可能存在很多“警报”,我想避免将所有这些问题都引入Ruby并在那里过滤所带来的性能问题。 (是的,这是一个Rails应用程序。)
使用timestamp with time zone
( timestamptz
)的timestamptz
进行计算。
报警的time [without time zone]
可以是time [without time zone]
。
但是你必须为每一行明确保存时区 。
永远不要 time with time zone
使用time with time zone
这是一个逻辑上破碎的类型,PostgreSQL不鼓励使用它。 手册:
time with time zone
的类型time with time zone
由SQL标准定义,但定义显示的属性导致可疑的有用性。 在大多数情况下,date
,time
,timestamp without timezone
timestamp with time zone
timestamp without timezone
的组合应该提供任何应用程序所需的完整范围的日期/时间function。
演示设置:
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'
…按照UTCtimestamp
获取 -
::time
…提取时间组件的最简单方法。
在这里,您可以查找时区名称 :
SELECT * FROM pg_timezone_names WHERE name ~~* '%sing%' LIMIT 10
SQL Fiddle展示夏季/冬季。
你可以使用全时区名称,例如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);
鉴于:
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)
原理:
- 存储创建警报的时区偏移量,包括当时是否为DST
- 还存储转换为UTC的时钟时间
- 查询时,请使用全时区域名称遵循当前UTC规则的时间,以生成该区域当前时区的时间。 与创建警报时的时区内存储的时间戳进行比较。
这也允许您应对用户更改位置的情况,从而更改时区。
当您想要进行预测性查询时,可以通过日期限定时间戳来扩展此方法,例如“在当地时间将在位置发出警报”。
我对这个解决方案并不完全有信心,建议仔细测试。