lit*_*erg 3 postgresql timezone postgresql-9.4
我们使用的是 Postgresql 9.4,在使用 date_trunc 时我注意到一个奇怪的行为。结果中的时区移动了 1 小时:
select date_trunc('year','2016-08-05 04:01:58.372486-05'::timestamp with time zone);
date_trunc
------------------------
2016-01-01 00:00:00-06
Run Code Online (Sandbox Code Playgroud)
截断到例如天时没有这种行为:
select date_trunc('day','2016-08-05 04:01:58.372486-05'::timestamp with time zone);
date_trunc
------------------------
2016-08-05 00:00:00-05
Run Code Online (Sandbox Code Playgroud)
这是预期的行为吗?如果是这样,这背后的逻辑是什么?
该date_trunc(text, timestamptz)
变体似乎记录不足,所以这是我的发现:
1) 在day
精度(第一个参数)之下,结果的时区偏移量始终与第二个参数的偏移量相同。
2) 达到或超过day
精度时,根据当前配置参数(可以用或设置)重新计算时区偏移。重新计算的偏移量始终与在相同配置参数生效的确切时刻相同。所以,例如 当参数包含 DST信息时,则相应地对齐偏移量。但是,当实际参数不包含 DST 信息(例如固定偏移量)时,结果的时区偏移量不变。TimeZone
set time zone '...'
set TimeZone to '...'
TimeZone
TimeZone
TimeZone
总而言之,date_trunc(text, timestamptz)
可以使用date_trunc(text, timestamp)
变体和at time zone
运算符s来模拟该函数:
date_trunc('month', tstz)
Run Code Online (Sandbox Code Playgroud)
应该相当于:
date_trunc('month', tstz at time zone current_setting('TimeZone')) at time zone current_setting('TimeZone'))
Run Code Online (Sandbox Code Playgroud)
至少,我是这么想的。事实证明,有一些TimeZone
配置设置是有问题的。因为:
PostgreSQL 允许您以三种不同的形式指定时区:
一个完整的时区名,例如
America/New_York
。已识别的时区名称列在pg_timezone_names
视图中(请参阅第 50.80 节)。PostgreSQL 为此使用了广泛使用的 IANA 时区数据,因此许多其他软件也可以识别相同的时区名称。一个时区的缩写,例如
PST
。这样的规范仅仅定义了一个特定的偏移量UTC
,与完整的时区名称形成对比,后者也可以暗示一组夏令时转换日期规则。已识别的缩写列在pg_timezone_abbrevs
视图中(参见第 50.79 节)。您不能设置配置参数TimeZone
或log_timezone
时区缩写,但您可以在日期/时间输入值中使用缩写并与 AT TIME ZONE 运算符一起使用。
(第三个是修复偏移量,或者它的 POSIX 形式,但这在这里并不重要)。
如您所见,缩写不能设置为TimeZone
。但是有一些缩写,也被认为是完整的时区名称,f.ex。CET
. 因此,set time zone 'CET'
会成功,但实际上会CEST
在夏季使用。但at time zone 'CET'
将始终引用缩写,它是一个固定偏移量UTC
(并且永远不会CEST
,因为可以使用at time zone 'CEST'
; 但set time zone 'CEST'
无效)。
这是时区设置的完整列表,当它们用于时set time zone
与用于时具有不兼容的含义at time zone
(从 9.6 开始):
CET
EET
MET
WET
Run Code Online (Sandbox Code Playgroud)
使用以下脚本,您可以检查您的版本:
create or replace function incompatible_tz_settings()
returns setof text
language plpgsql
as $func$
declare
cur cursor for select name from pg_timezone_names;
begin
for rec IN cur loop
declare
r pg_timezone_names;
begin
r := rec;
execute format('set time zone %L', (r).name);
if exists(select 1
from generate_series(current_timestamp - interval '12 months', current_timestamp + interval '12 months', interval '1 month') tstz
where date_trunc('month', tstz) <> date_trunc('month', tstz at time zone (r).name) at time zone (r).name) then
return next (r).name;
end if;
end;
end loop;
end
$func$;
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
2774 次 |
最近记录: |