Oracle SQL:处理夏令时

Oracle SQL: Handle with Daylight Saving Time

我有以下系统信息:

我使用 Oracle 数据库 10g
SysTimeStampUTC
SessionTimeZoneEurope/Athens
dbTimeZone+03:00

所以,我有来自 tbl_1 table 的列 date_1,具有以下日期时间:

date_1
-----------------
08.02.2017 10:00
08.02.2017 11:00
08.02.2017 12:00
-----------------

我想要的结果是这样的:

date_2
-----------------
08.02.2017 13:00
08.02.2017 14:00
08.02.2017 15:00

为此我使用:

SELECT TO_CHAR(date_1 + INTERVAL '3' HOUR, 'DD.MM.YYYY HH24:MI') as date_2 
FROM tbl_1
WHERE date_1 >= TO_DATE('08.02.2017 10:00','DD.MM.YYYY HH24:MI')
  AND date_1 <= TO_DATE('08.02.2017 12:00','DD.MM.YYYY HH24:MI')

MarchOctober 的小时发生变化时,我的问题出现了,因为在 March 的最后一个星期日,我们一天有 23 个小时,而在最后一个星期日,从 October 我们一天有25个小时。

因此,我必须更改查询 4 ​​times/year(夏令时、冬令时、三月有 23 小时和十月有 25 小时)

你能在这个 select 中推荐一个解决这个问题的查询吗?

您可以申请案例 select:

select date_1 + case
                  when to_char(date_1 ,'MM') <= 3 then 2/24 -- Jan/Feb/Mar
                  when to_char(date_1,'MM') <= 10 then 3/24 -- Apr to Oct
                  else 2/24 -- Nov/Dec
                end as date_2
from tbl_1

如果您有一个没有嵌入时区信息的纯日期或时间戳,您可以告诉 Oracle 将其视为处于具有 the from_tz() function. You can then convert that value - which now has data type 'timestamp with zone zone' rather than a plain 'timestamp' - to another zone with the at time zone datetime expression syntax 的特定时区,或者使用会话时区作为 'local' 或特定的命名时区:

alter session set nls_date_format='YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_format='YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_tz_format='YYYY-MM-DD HH24:MI:SS TZR';
alter session set time_zone = 'America/New_York';

with cte (ts) as (
  select timestamp '2017-02-08 12:00:00' from dual
)
select ts,
  from_tz(ts, 'UTC') as ts_utc,
  from_tz(ts, 'UTC') at local as ts_local,
  from_tz(ts, 'UTC') at time zone 'Europe/Athens' as ts_athens
from cte;

TS                  TS_UTC                  TS_LOCAL                             TS_ATHENS                        
------------------- ----------------------- ------------------------------------ ---------------------------------
2017-02-08 12:00:00 2017-02-08 12:00:00 UTC 2017-02-08 07:00:00 AMERICA/NEW_YORK 2017-02-08 14:00:00 EUROPE/ATHENS

如果您从日期开始,那么您必须在调用 from_tz():

之前将其转换为时间戳
with cte (dt) as (
  select cast( timestamp '2017-02-08 12:00:00' as date) from dual
)
select dt,
  from_tz(cast(dt as timestamp), 'UTC') as ts_utc,
  from_tz(cast(dt as timestamp), 'UTC') at local as ts_local,
  from_tz(cast(dt as timestamp), 'UTC') at time zone 'Europe/Athens' as ts_athens
from cte;

DT                  TS_UTC                  TS_LOCAL                             TS_ATHENS                        
------------------- ----------------------- ------------------------------------ ---------------------------------
2017-02-08 12:00:00 2017-02-08 12:00:00 UTC 2017-02-08 07:00:00 AMERICA/NEW_YORK 2017-02-08 14:00:00 EUROPE/ATHENS

所以原始 date_1 值的数据类型很重要,它应该代表的标称时区也很重要。如果它是;准备好 'timestamp with time zone' 或 'timestamp with local time zone' 那么它已经嵌入了时区信息,因此您根本不需要 from_tz() 部分。如果是日期,则需要将其转换为时间戳。

假设 date_1 存储为纯时间戳(可能由您的间隔添加暗示,但不是由您使用的列名和过滤器暗示)并且它名义上是 UTC,您可以这样做:

from_tz(date_1, 'UTC') at time zone 'Europe/Athens'

... 这会给你一个 'timestamp with time zone' 结果;或者您可以使用 local 来依赖您的会话时区。如果 `date_1 存储为日期,您将转换添加到 timestamp:

from_tz(cast(date_1 as timestamp), 'UTC') at time zone 'Europe/Athens'

作为演示,在 CTE 中生成时间戳(不是日期),包括一些围绕今年夏令时变化的时间:

with tbl_1(date_1) as (
  select timestamp '2017-02-08 10:00:00' from dual
  union all select timestamp '2017-02-08 11:00:00' from dual
  union all select timestamp '2017-02-08 12:00:00' from dual
  union all select timestamp '2017-03-23 12:00:00' + numtodsinterval(level, 'day')
    from dual connect by level <= 4
)
select date_1,
--  cast(from_tz(date_1, 'UTC') at time zone 'Europe/Athens' as timestamp) as date_2
  to_char(from_tz(date_1, 'UTC') at time zone 'Europe/Athens',
    'DD.MM.YYYY HH24:MI') as date_2
from tbl_1
order by date_1;

DATE_1              DATE_2          
------------------- ----------------
2017-02-08 10:00:00 08.02.2017 12:00
2017-02-08 11:00:00 08.02.2017 13:00
2017-02-08 12:00:00 08.02.2017 14:00
2017-03-24 12:00:00 24.03.2017 14:00
2017-03-25 12:00:00 25.03.2017 14:00
2017-03-26 12:00:00 26.03.2017 15:00
2017-03-27 12:00:00 27.03.2017 15:00

您可以看到在 3 月 26 日时钟更改后自动增加了一个小时。但是您的 2 月示例数据的结果在一个小时之前就出来了 - 因此您的数据实际上并未存储为 UTC(而是 -01:00,您可以更改 from_tz() 调用以反映这一点),或者你的预期结果是错误的。

美国时区

SELECT SYSDATE, 
NEXT_DAY ( TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7 dst_start, 
NEXT_DAY ( TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') dst_end, 
CASE WHEN SYSDATE >= NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7 AND SYSDATE < NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') THEN 'Y' ELSE 'N' END AS dst_check_usa, 
NEW_TIME ( SYSDATE, CASE WHEN SYSDATE >= NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') + 7 AND SYSDATE < NEXT_DAY ( TO_DATE ( TO_CHAR (SYSDATE, 'YYYY') || '/11/01 02:00 AM', 'YYYY/MM/DD HH:MI AM') - 1, 'SUN') THEN 'CDT' ELSE 'CST' END, 'GMT') AS current_time_gmt 
FROM DUAL;

欧洲时区

SELECT SYSDATE, 
NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') dst_start_uk,
NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/10/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') dst_end_uk,
CASE WHEN SYSDATE >= NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/03/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') AND SYSDATE < NEXT_DAY(LAST_DAY(TO_DATE (TO_CHAR (SYSDATE, 'YYYY') || '/10/01 02:00 AM', 'YYYY/MM/DD HH:MI AM'))-7, 'SUN') THEN 'Y' ELSE 'N' END AS dst_check_uk
FROM DUAL;