Oracle SQL:处理夏令时

时间:2017-02-22 09:50:48

标签: sql oracle10g

我有以下系统信息:

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

所以,我有date_1表格中的tbl_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个小时,而{{1}的最后一个星期天我们一天有25个小时。

因此我必须每年4次更改我的查询(夏季时间,冬季时间,3月23小时,10月25小时)

您可以在此October中推荐一个解决此问题的查询吗?

3 个答案:

答案 0 :(得分:1)

如果您有一个没有嵌入时区信息的简单日期或时间戳,您可以告诉Oracle将其视为具有the from_tz() function的特定时区。然后,您可以将该值 - 现在具有数据类型'带区域的时间戳'而不是简单的'时间戳' - 转换为具有at time zone datetime expression syntax的另一个区域,或者使用会话时区作为“本地”或使用具体命名的时区:

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值的数据类型很重要,它应该表示的标称时区也是如此。如果是a;准备好'带时区的时间戳'或'带有本地时区的时间戳',那么它已经嵌入了时区信息,因此您根本不需要from_tz()部分。如果是日期,则需要将其转换为时间戳。

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

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

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

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

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

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日时钟变化后会自动添加额外的小时。但是您的样本二月数据的结果是一小时 - 因此您的数据实际上并未存储为UTC(但是-01:00,您可以更改from_tz()调用以反映该数据),或者你的预期结果是错误的。

答案 1 :(得分:0)

您可以将案例应用于选择:

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

答案 2 :(得分:0)

对于美国时区

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;