在选择预定项目时,在Postgres中计算DST

Mar*_*lin 12 ruby sql postgresql timezone dst

我有一个Postgres 时钟闹钟表(不是真的,但这是类似的,更容易解释).用户以1小时的分辨率设置警报,用户可以来自许多不同的时区.警报每天重复.我想要可靠地获取在一天中的特定时刻应该关闭的警报,并且我遇到夏令问题.我该如何以最好的方式做到这一点?

阿尔弗雷德和洛塔都住在斯德哥尔摩(从UTC开始+1小时,但在DST时为+ 2小时).
Sharon住在新加坡(距离UTC +8小时,没有夏令时)

在冬季,阿尔弗雷德在凌晨4点发出警报.警报应该在当地时间凌晨4点全年开始.
在夏季,Lotta在凌晨5点发出警报.它应该全年都在凌晨5点出发.
与此同时,沙龙已经在上午11点发出警报.

所有这些都可以03:00 UTC的形式存储在数据库中.

如果我在冬天查询数据库以获取应在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应用程序.)

Erw*_*ter 13

使用timestamp with time zone(timestamptz)进行计算.
报警的时间可以是time [without time zone].
但是你必须为每一行明确保存时区.

永远不要使用time with time zone它是一个逻辑上破碎的类型,PostgreSQL不鼓励使用它.手册:

该类型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.
Run Code Online (Sandbox Code Playgroud)

必须是时区名称(而不是缩写)才能说明DST.有关:

获取"今天"的匹配警报:

SELECT *
FROM   alarm
WHERE  (('2012-07-01'::date + t) AT TIME ZONE tz AT TIME ZONE 'UTC')::time
       = '03:00'::time
Run Code Online (Sandbox Code Playgroud)
  • ('2012-7-1'::date + t)...汇编timestamp [without time zone] 也可能只是now()::date + t为了"今天".
  • AT WITH TIME ZONE tz...将时间戳放在保存的时区,从而产生timestamptz.
  • AT WITH TIME ZONE 'UTC' ......按UTC计算 timestamp
  • ::time ...提取时间组件的最简单方法.

在这里,您可以查找时区名称:

SELECT *
FROM   pg_timezone_names
WHERE  name ~~* '%sing%'
LIMIT  10
Run Code Online (Sandbox Code Playgroud)

SQL Fiddle演示夏季/冬季.


tit*_*old 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;
Run Code Online (Sandbox Code Playgroud)

要么:

SELECT user_to_alert
FROM time_test
WHERE EXTRACT(HOUR FROM CURRENT_TIME AT TIME ZONE user_timezone) = EXTRACT(HOUR FROM alarm_hour);
Run Code Online (Sandbox Code Playgroud)