从“没有时区的时间”和时区名称中获取“有时区的时间”
Posted
技术标签:
【中文标题】从“没有时区的时间”和时区名称中获取“有时区的时间”【英文标题】:Get "time with time zone" from "time without time zone" and the time zone name 【发布时间】:2012-01-04 05:21:38 【问题描述】:首先,我意识到不推荐使用time with time zone
。我将使用它,因为我正在将多个 time with time zone
值与我当前的系统时间进行比较,无论是哪一天。 IE。用户说每天从 08:00 开始,并在 12:00 结束时使用他们的时区,而不是系统时区。所以,我在一个表中有一个time without time zone
列,我们称之为SCHEDULES.time
,我在另一个表中有一个UNIX 时区名称 列,我们称之为USERS.tz
。
我的系统时区是'America/Regina'
,不使用夏令时,所以偏移量总是-06
。
鉴于“12:00:00”的时间和“美国/温哥华”的 tz,我想将数据选择到 time with time zone
类型的列中,但我不想将时间转换为我的时间zone,因为用户实际上已经说过在温哥华的 12:00 开始,而不是在里贾纳。
因此,做:
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'
除了AT TIME ZONE
之外,我找不到任何有关将时区应用于时间的文档。有没有办法在没有字符操作或其他 hack 的情况下实现这一点?
更新: 这可以通过使用字符串连接、强制转换和 Postgres 时区视图来完成:
select ('12:00:00'::text || utc_offset::text)::timetz
from pg_timezone_names
where name = 'America/Vancouver';
但是,这相当慢。一定有更好的办法吧?
更新 2:
我为混乱道歉。 SCHEDULES
表不使用 time with time zone
,我正在尝试通过组合 time without time zone
和 text
时区名称中的值来选择 time with time zone
。
更新 3:
感谢所有参与(激烈)讨论的人。 :) 我已被说服放弃使用time with time zone
作为输出的计划,而是使用timestamp with time zone
,因为它性能良好、更具可读性,并解决了我将要遇到的另一个问题,即时区滚动到新的日期。 IE。 'America/Vancouver' 中的 '2011-11-21 23:59' 是 'America/Regina' 中的 '2011-11-22'。
更新 4:
正如我在上次更新中所说,我选择了@MichaelKrelin-hacker 首次提出并@JonSkeet 最终确定的答案。也就是说,timestamp with time zone
作为我的最终输出是一个更好的解决方案。我最终使用了如下查询:
SELECT timezone(USERS.tz, now()::date + SCHEDULES.time)
FROM SCHEDULES
JOIN USERS ON USERS.ID = SCHEDULES.USERID;
timezone()
格式在我将(current_date + SCHEDULES.time) AT TIME ZONE USERS.tz
输入视图后被 Postgres 重写。
【问题讨论】:
你真正的SQL里面有join吗?目前我看不到你如何将一个映射到另一个。如果是这样,请显示一个简短但完整的查询。 是的,连接是隐含的。 SELECT SCHEDULES.time AT TIME ZONE USERS.tz from SCHEDULES JOIN USERS on USERS.ID=SCHEDULES.USERID @JonSkeet:我不明白为什么有必要这样做,因为他给了我们具体的数据示例。如果有帮助,您可以假装完整的查询是SELECT '12:00:00'::TIME AT TIME ZONE 'America/Vancouver';
,它在 OP 的 GMT-0600 系统上给出 10:00:00-08
。
@ruakh:连接对于实际查询绝对是必要的 - 很明显,如果这被搞砸了,它会影响其他一切。
这让我很感兴趣。听起来它应该工作。我正在下载并安装 PostgreSQL 来尝试一下。准备好愚蠢的问题...
【参考方案1】:
警告:PostgreSQL 新手(请参阅有关问题的 cmets!)。不过我对时区有点了解,所以我知道问什么有意义。
在我看来,对于AT TIME ZONE
,这基本上是一种不受支持的情况(不幸的是)。查看AT TIME ZONE 文档,它提供了一个表格,其中只有“输入”值类型:
我们缺少您想要的:时间没有时区。您要问的是有点合乎逻辑的,尽管它确实取决于日期......因为不同的时区可能有不同的偏移量,具体取决于日期。例如,12:00:00 Europe/London 可能表示 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 出现两次?【讨论】:
你最后没有得到我开始回答的内容吗? :) @MichaelKrelin-hacker:不是真的——我最后建议SCHEDULES.time
应该是TIMESTAMP WITHOUT TIME ZONE
字段;我在您的(已删除)答案中看不到这一点。
是的,因为,正如你现在所知,我(错误地)假设现在是没有时区的时间,从结果中可以看出,它的工作原理是一样的。
我的意思是我们或多或少地让 OP 得到相同的结果,这并不是他想要的。
@MichaelKrelin-hacker:不,这些结果更好:他们有错误的小时字段和错误的时区,也就是说,他们有正确的时间-小时字段的区域(因此他们正确识别正确的时间点)。而 OP 的原始结果给出了错误的小时字段但 正确 时区,这意味着他们的小时字段的时区错误(因此他们确定了错误的时间-天)。【参考方案2】:
这是一个如何在不转换为文本的情况下计算时间的演示:
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
要好得多。当然,您必须保存正确 时区缩写而不是时区名称来实现这一点。时区名称会自动考虑夏令时,时区缩写基本上只是时间偏移的代码。 The documentation:
时区缩写,例如
PST
。这样的规范仅仅是 与完整时区名称相比,定义了与 UTC 的特定偏移量 这也可以暗示一组夏令时转换日期规则。
看看这些测试结果或自己试试吧:
SELECT * FROM pg_timezone_names;
总运行时间:541.007 毫秒
SELECT * FROM pg_timezone_abbrevs;
总运行时间:0.523 ms
因子 1000。无论您是按照您的想法转换为text
并返回到timetz
,还是使用我的方法来计算时间并不重要。这两种方法都非常快。只是不要使用pg_timezone_names
。
实际上,只要您保存时区缩写,您就可以在没有任何额外连接的情况下采用投射路线。使用缩写而不是 utc_offset。根据您的定义,结果是准确的。
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;
【讨论】:
我认为 OP 实际上想要避免加入而不是字符操作,他抱怨他的查询速度很慢,我认为这主要是因为加入。 很好的答案!不幸的是,与使用带有时区的时间戳而不加入该视图的选项相比,加入 pg_timezone_names 视图仍然太慢。不过非常感谢! 这是真的,@ErwinBrandstetter。顺便说一句,这些构造如何解释夏令时?还是他们? 尚不清楚pg_timezone_names
是否总是给出相对于UTC 的当前 偏移量,还是相对于UTC 的标准时间 偏移量。基本上,我们需要知道 OP 希望将哪个日期应用于时间,因为这会显着影响结果。
@MichaelKrelin-hacker:时间偏移或时区的信息必须来自某个地方。以上是关于从“没有时区的时间”和时区名称中获取“有时区的时间”的主要内容,如果未能解决你的问题,请参考以下文章