从“没有时区的时间”和时区名称中获取“有时区的时间”

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 zonetext 时区名称中的值来选择 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:时间偏移或时区的信息必须来自某个地方

以上是关于从“没有时区的时间”和时区名称中获取“有时区的时间”的主要内容,如果未能解决你的问题,请参考以下文章

使用 node-postgres 在 utc 中获取 Postgres“没有时区的时间戳”

postgreSQL 将列数据类型更改为没有时区的时间戳

修复错误:“运算符不存在:没有时区的时间戳>整数”错误

PostgreSQL在没有时区的时间戳类型的表中搜索

从ios中的facebook登录获取国家名称和时区

没有时区的时间戳中的夏令时时间偏移