注意:这个答案是关于 PostgreSQL 的。大多数解决方案也应该能够应用于 redshift,因为它基于旧版本的 PostgreSQL。但是,您可能需要为此解决方案的某些部分寻找替代方案,因为我无法在 redshift 上对此进行测试(例如,使用 CONVERT_TIMEZONE(tz, ts) 函数而不是 ts AT TIME ZONE tz 表达式)。
首先,您需要了解时区有多种“类型”。前任Europe/London 是一个时区名称,数据库有关于其夏令时规则的信息。但是,时区偏移量(例如UTC、UTC+2 或任何时间间隔)是静态的,永远不会被视为夏令时 (neither in python)。还有时区缩写,它们只是时区偏移的别名,但它们的 DST 变体有一个备用名称(例如 CET 在夏令时是 CEST),所以它们永远不会(或总是)被认为是夏令时(请注意,PostgreSQL 接受(并调整)虚假的日期时间输入,例如 2016-01-12 10:00 CEST,实际上是 2016-01-12 09:00 CET)。此外,还有 POSIX 风格的时区,例如 EST5EDT,它们可以有自己的夏令时规则。
对于纯SQL检测,需要查询pg_timezone_abbrevs和pg_timezone_names系统视图:
create or replace function tstz_isdst(ts timestamp without time zone, tz text)
returns boolean
immutable
language sql
as $func$
with tz_info as (
select utc_offset, true fix_dst, is_dst
from pg_timezone_abbrevs
where lower(abbrev) = lower(tz)
union all
select utc_offset, false, is_dst
from pg_timezone_names
where lower(name) = lower(tz)
union all
select -coalesce(substring(tz from '([\+\-]?\d+(:\d+){1,2}(.\d+)?)')::interval,
substring(tz from '[\+\-]?\d+')::integer * interval '1 hour'),
false, false
)
select case
when fix_dst then is_dst
when ts = (ts at time zone tz at time zone 'UTC' + utc_offset) then is_dst
else not is_dst
end
from tz_info
limit 1
$func$;
select tstz_isdst('2016-01-12 10:00', 'GMT'),
tstz_isdst('2016-04-12 10:00', 'BST'),
tstz_isdst('2016-03-27 01:30', 'GMT0BST'), -- not exists
tstz_isdst('2016-10-30 01:30', 'Europe/London'); -- ambiguous
请注意,对于不存在或不明确的日期时间 + 时区组合(当前正在进行夏令时更改),此函数将返回 false。
但是这个函数可能仍然不是你所需要的,因为在 PostgreSQL 中 pg_timezone_names 视图的计算速度非常慢(在我的测试中为 300-600 毫秒),因此查询它的每一行可能不是最佳的桌子。但是您可以改用连接:
select t.ts, t.tz, case
when tz_abbr.is_dst is not null
then tz_abbr.is_dst
when tz_name.utc_offset is not null
then case
when t.ts = (t.ts at time zone t.tz at time zone 'UTC' + tz_name.utc_offset)
then tz_name.is_dst
else not tz_name.is_dst
end
else t.ts <> (t.ts at time zone t.tz at time zone 'UTC' -
coalesce(substring(t.tz from '([\+\-]?\d+(:\d+){1,2}(.\d+)?)')::interval,
substring(t.tz from '[\+\-]?\d+')::integer * interval '1 hour'))
end is_dst
from (values(timestamp '2016-01-12 10:00', 'GMT'),
(timestamp '2016-04-12 10:00', 'BST'),
(timestamp '2016-03-27 01:30', 'GMT0BST'),
(timestamp '2016-10-30 01:30', 'Europe/London')) t(ts, tz)
left join pg_timezone_abbrevs tz_abbr on lower(tz_abbr.abbrev) = lower(t.tz)
left join pg_timezone_names tz_name on lower(tz_name.name) = lower(t.tz);