如何使用Postgres将行级TZ转换为UTC?

时间:2019-06-18 00:14:22

标签: postgresql datetime timezone

首先,这不是将 UTC转换为TZ 的重复问题。那不是我要的答案。我也不在寻找有关时间 偏移量 的答案。

  • TZ到UTC
  • UTC到TZ
  • 偏移量

用例:国际“ Cron Jobs”

有一堆按照用户指定的时间表运行的作业,应该尊重夏时制,夏令时,冬令时等。

基本上,我有一个这样的表:

 name |      timezone       | usertime
------+---------------------+----------
 Joe  | America/New_York    | 02:00:00
 Jane | America/Chicago     | 02:00:00
 John | America/Denver      | 02:00:00
 Jess | America/Phoenix     | 02:00:00
 Jack | America/Los_Angeles | 02:00:00
 Ping | Asia/Shanghai       | 02:00:00

我想问的问题是:

  

如果我从世界标准时间now()开始算起,那么usertime的UTC时间是(或将是)什么时间?

0。 WITH TIME ZONE =>错误!

time类型是……毫无价值。 postgres文档甚至对此警告,指责SQL标准,但其实现是为了完整性。

timestamp更好,但是...

  • WITHOUT TIME ZONE实际上确实应用了本地系统tz
  • WITH TIME ZONE需要一个时区,但只保存偏移量

似乎最好只使用文本类型。

1。 AT TIME ZONE =>错误!

此转换完全倒退。首先,无论时区设置如何,它都会显示与本地系统时间相关的所有内容。它还会向后(到本地时间)而不是向前(到设置的时区)计数。

SELECT
  name,
  timezone,
  usertime,
  usertime::time AT TIME ZONE timezone AS time
FROM demo;
 name |      timezone       | usertime |    time
------+---------------------+----------+-------------
 Joe  | America/New_York    | 02:00:00 | 04:00:00-04
 Jane | America/Chicago     | 02:00:00 | 03:00:00-05
 John | America/Denver      | 02:00:00 | 02:00:00-06
 Jess | America/Phoenix     | 02:00:00 | 01:00:00-07
 Jack | America/Los_Angeles | 02:00:00 | 01:00:00-07
 Ping | Asia/Shanghai       | 02:00:00 | 16:00:00+08
(5 rows)

2。时间+时区=>错误!

正如您在此处看到的,time类型实际上根本不允许时区,只是时间偏移量

SELECT
  name,
  timezone,
  (usertime || ' ' || timezone)::time AT TIME ZONE 'UTC'
    AS realtime
FROM demo;
ERROR:  invalid input syntax for type time: "02:00:00 America/New_York"

3。时间戳+时区=>错误!

您会认为,如果您使用时区(例如1985-10-26 09:00:00 America/Los_Angeles)显式构造了字符串,则将获得正确的时间。不。它完全忽略了它。

SELECT
  name,
  timezone,
  (current_date || ' ' || usertime || ' ' || timezone)::timestamp,
  usertime
FROM demo;
 name |      timezone       |      timestamp      | usertime
------+---------------------+---------------------+----------
 Joe  | America/New_York    | 2019-06-17 02:00:00 | 02:00:00
 Jane | America/Chicago     | 2019-06-17 02:00:00 | 02:00:00
 John | America/Denver      | 2019-06-17 02:00:00 | 02:00:00
 Jess | America/Phoenix     | 2019-06-17 02:00:00 | 02:00:00
 Jack | America/Los_Angeles | 2019-06-17 02:00:00 | 02:00:00
 Ping | Asia/Shanghai       | 2019-06-17 02:00:00 | 02:00:00
(5 rows)

1 个答案:

答案 0 :(得分:0)

丑陋的解决方案:双重投射

这是低效率的全表扫描,但我认为它可以正常工作。

您必须

  1. 将当前时间戳投射到目标时区
  2. 将其添加到目标时间(必须在子表达式中)
  3. 将完整结果投射到目标时区
  4. 然后再次将 转换为UTC

这是极其精致。最细微的修改(日期/时间戳,括号,顺序)可能会导致某些操作无效。

SELECT
  name,
  timezone,
  usertime,
  ((current_timestamp AT TIME ZONE timezone)::date + usertime)
    AT TIME ZONE timezone
    AT TIME ZONE 'UTC'
    AS realtime
FROM demo;
 name |      timezone       | usertime |      realtime
------+---------------------+----------+---------------------
 Ping | Asia/Shanghai       | 02:00:00 | 2019-06-17 18:00:00
 Joe  | America/New_York    | 02:00:00 | 2019-06-18 06:00:00
 Jane | America/Chicago     | 02:00:00 | 2019-06-18 07:00:00
 John | America/Denver      | 02:00:00 | 2019-06-18 08:00:00
 Jess | America/Phoenix     | 02:00:00 | 2019-06-17 09:00:00
 Jack | America/Los_Angeles | 02:00:00 | 2019-06-17 09:00:00

您必须使用current_timestamp,而不是current_date,并且必须将其强制转换为目标时区,并将其全部包装在括号中,否则您将有时间倒退或侧身:

 Jack | America/Los_Angeles | 02:00:00 | 2019-06-17 09:00:00
 Ping | Asia/Shanghai       | 02:00:00 | 2019-06-16 18:00:00

如何解决时间溢出

已更新:我不确定上一个示例中是否确实涵盖了100%的时区。不管您采用哪种方式,似乎都发生“向后滚动”,只是在不同区域的不同时间。

另一种方法是删除日期信息并滚动 current 时间以获取正确的事件查询窗口:

SELECT *
FROM (
  SELECT
    name,
    timezone,
    usertime,
    (((current_timestamp AT TIME ZONE timezone)::date + usertime)
      AT TIME ZONE timezone
      AT TIME ZONE 'UTC')::time
      AS utc_time,
    (((current_timestamp AT TIME ZONE timezone)::date + usertime)
      AT TIME ZONE timezone
      AT TIME ZONE 'Indian/Chagos')::time
      AS ctu_time
  FROM demo
  ) as d
WHERE utc_time
  BETWEEN (current_timestamp AT TIME ZONE 'UTC')::time
    AND (current_timestamp AT TIME ZONE 'UTC')::time + INTERVAL '15 MINUTES'
  OR ctu_time
  BETWEEN (current_timestamp AT TIME ZONE 'Indian/Chagos')::time
    AND (current_timestamp AT TIME ZONE 'Indian/Chagos')::time + INTERVAL '15 MINUTES'
;

这里的问题是23:5800:13之间没有数字,因此您需要选择两个时区。

更准确,优雅和/或更有效的解决方案?

我希望看到一种解决方案,该解决方案始终显示事件的未来日期/时间,而不是过去的日期/时间。

我也想知道是否有一种方法可以查询WHERE realtime >= now() AND realtime < near_future之类的内容而不会引起全表扫描。