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

时间:2012-11-05 20:06:53

标签: sql ruby postgresql timezone dst

我有一个 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应用程序。)

3 个答案:

答案 0 :(得分:12)

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

从不使用time with time zone这是一个逻辑上破坏的类型,PostgreSQL不鼓励使用它。 The manual:

  

类型time with time zone由SQL标准定义,但是   定义表现出导致可疑用途的特性。   在大多数情况下,datetimetimestamp 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)

原理:

  • 存储创建警报的时区偏移量,包括当时是否为DST
  • 还存储转换为UTC的时钟时间
  • 查询时,请使用全时区名称遵循当前UTC规则的时间,以生成该区域当前时区内的时间。与存储的时间戳进行比较,该时间戳表示创建警报时的时区。

这也可以让您应对用户更改位置的情况,从而更改时区。

当您想要进行预测性查询时,可以通过日期限定时间戳来扩展此方法,例如“在当地时间将在位置发出警报”。

我对这个解决方案并不完全有信心,建议仔细测试。