获得时区时间"来自"没有时区的时间"和时区名称

时间:2011-11-22 20:26:07

标签: sql postgresql time timezone

首先,我发现不建议使用time with time zone。我将使用它,因为我将多个time with time zone值与我当前的系统时间进行比较,而不管是哪一天。即用户说每天从08:00开始,在他们的时区12点结束,而不是系统时区。所以,我在一个表中有一个time without time zone列,我们称之为SCHEDULES.time,我在另一个表中有一个 UNIX时区名称列,我们称之为{{1} }。

我的系统时区为USERS.tz,不使用DST,因此偏移量始终为'America/Regina'

给定时间为'12:00:00'并且tz为'America / Vancouver'我想将数据选择到-06类型的列中,但我不想将时间转换为我的时区因为用户已经有效地说是从温哥华的12:00开始,而不是在里贾纳。

因此,做:

time with time zone

结果(目前):

SELECT SCHEDULES.time AT TIME ZONE USERS.tz
FROM SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID;

但我真的想要:

'10:00:00-08'

除了'12:00:00-08' 之外,我找不到任何与时区相关的文档。有没有办法在没有角色操纵或其他黑客的情况下实现这一目标?

更新 这可以通过使用字符串连接,强制转换和Postgres时区视图来实现:

AT TIME ZONE

然而,这是相当慢的。必须有更好的方法,不是吗?

更新2: 我为这种困惑道歉。 select ('12:00:00'::text || utc_offset::text)::timetz from pg_timezone_names where name = 'America/Vancouver'; 表格不使用SCHEDULES,我尝试通过合并time with time zonetime with time zone时区名称中的值来选择time without time zone

更新3: 感谢所有参与他们(热烈)讨论的人。 :)我一直坚信放弃我的计划使用text作为我的输出,而是使用time with time zone,因为它表现良好,更具可读性,并解决了我将遇到的另一个问题,滚动到新日期的时区。 IE浏览器。 “美国/温哥华”中的“2011-11-21 23:59”是“美国/里贾纳”中的“2011-11-22”。

更新4: 正如我在上次更新中所说,我选择了@ MichaelKrelin-hacker首先提出的答案和@JonSkeet最终确定的答案。也就是说,timestamp with time zone作为我的最终输出是更好的解决方案。我最终使用了一个类似的查询:

timestamp with time zone

在我将SELECT timezone(USERS.tz, now()::date + SCHEDULES.time) FROM SCHEDULES JOIN USERS ON USERS.ID = SCHEDULES.USERID; 输入我的视图后,Postgres重写了timezone()格式。

2 个答案:

答案 0 :(得分:12)

警告:PostgreSQL新手(请参阅有关该问题的评论!)。我对时区有点了解,所以我知道是什么让 sense 问。

在我看来,AT TIME ZONE这基本上是一种不受支持的情况(不幸的是)。查看AT TIME ZONE文档,它提供了一个表,其中“输入”值类型仅为:

  • 没有时区的时间戳
  • 带时区的时间戳
  • 时区与时区

我们错过了你想要的那个:时间没有时区。你要问的是有些逻辑,虽然它确实取决于日期......因为不同的时区可以根据日期有不同的偏移量。例如,12:00:00欧洲/伦敦可能意味着12:00:00 UTC,或者它可能意味着11:00:00 UTC,具体取决于它是冬天还是夏天。

在我的系统上,将系统时区设置为America / Regina,查询

SELECT ('2011-11-22T12:00:00'::TIMESTAMP WITHOUT TIME ZONE) 
                               AT TIME ZONE 'America/Vancouver'

结果给了我2011-11-22 14:00:00-06。这不是理想,但它确实至少给出了即时时间点(我认为)。我相信如果您使用客户端库获取它 - 或者将其与另一个TIMESTAMP WITH TIME ZONE进行比较 - 您将获得正确的结果。只是文本转换然后使用系统时区进行输出。

这对你来说足够好吗?您是否可以将SCHEDULES.time字段更改为TIMESTAMP WITHOUT TIME ZONE字段,或者(在查询时)将字段中的时间与日期相结合以创建没有时区的时间戳?

编辑:如果您对“当前日期”感到满意,看起来就像您可以将查询更改为:

SELECT (current_date + SCHEDULES.time) AT TIME ZONE USERS.tz
from SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID

当然,当前系统日期可能与当地时区中的当前日期不同。我认为这将解决这个问题......

SELECT ((current_timestamp AT TIME ZONE USERS.tz)::DATE + schedules.time)
       AT TIME ZONE USERS.tz
from SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID

换句话说:

  • 抓住当前时机
  • 在用户的时区中计算本地日期/时间
  • 取日期
  • 将计划时间添加到该日期以获得TIMESTAMP WITHOUT TIME ZONE
  • 使用AT TIME ZONE将时区应用于本地日期/时间

我确信有更好的方法,但我认为这是有道理的。

你应该知道,在某些情况下,这可能会失败:

  • 当时钟从01:00跳到02:00时,你想要的结果是01:30的时间,所以01:30根本不会发生?
  • 当时钟从02:00返回到01:00时,您希望结果是01:30,那么01:30会发生两次?

答案 1 :(得分:3)

以下是如何计算未投射到文本的时间的演示:

CREATE TEMP TABLE schedule(t time, tz text);
INSERT INTO schedule values
 ('12:00:00', 'America/Vancouver')
,('12:00:00', 'US/Mountain')
,('12:00:00', 'America/Regina');

SELECT s.t AT TIME ZONE s.tz
        - p.utc_offset
        + EXTRACT (timezone from now()) * interval '1s'
FROM   schedule s
JOIN   pg_timezone_names p ON s.tz = p.name;

基本上你必须减去UTC偏移并添加当地时区的偏移量以到达给定的时区。

您可以通过硬编码本地偏移来加快计算速度。在你的情况下(美国/里贾纳)应该是:

SELECT s.t AT TIME ZONE s.tz
        - p.utc_offset
        - interval '6h'
FROM   schedule s
JOIN   pg_timezone_names p ON s.tz = p.name;

由于pg_timezone_names是一个视图,实际上并不是一个系统表,因此速度相当慢 - 就像演示的变体一样,可以转换为文本表示并返回。

我会存储时区缩写并通过text进行双重演员,而无需加入pg_timezone_names以获得最佳效果。


快速解决方案

使你减速的罪魁祸首pg_timezone_names。经过一些测试后,我发现pg_timezone_abbrevs远远优于PST。当然,您必须保存正确的 时区缩写而不是时区名称才能实现此目的。时区名称自动考虑DST,时区缩写基本上只是时间偏移的代码。 The documentation:

  

时区缩写,例如SELECT * FROM pg_timezone_names; 。这样的说明仅仅是   与全时区域名称相比,定义了与UTC的特定偏移量   这也意味着一套夏令时过渡日期规则。

查看这些测试结果或尝试自己:

SELECT * FROM pg_timezone_abbrevs;

总运行时间: 541.007 ms

text

总运行时间: 0.523 ms

因素1000 。是否按照您的想法转换为timetz并返回pg_timezone_names或使用我的方法来计算时间并不重要。两种方法都非常快。只是不要使用CREATE TEMP TABLE schedule(t time, abbrev text); INSERT INTO schedule values ('12:00:00', 'PST') -- 'America/Vancouver' ,('12:00:00', 'MST') -- 'US/Mountain' ,('12:00:00', 'CST'); -- 'America/Regina' -- calculating SELECT s.t AT TIME ZONE s.abbrev - a.utc_offset + EXTRACT (timezone from now()) * interval '1s' FROM schedule s JOIN pg_timezone_abbrevs a USING (abbrev); -- casting (even faster!) SELECT (t::text || abbrev)::timetz FROM schedule s;

实际上,只要您保存时区缩写,就可以在没有任何其他连接的情况下采用投射路线。使用缩写而不是utc_offset。根据您的定义,结果是准确的。

{{1}}