Oracle SQL:处理夏令时
Oracle SQL: Handle with Daylight Saving Time
我有以下系统信息:
我使用 Oracle 数据库 10g
SysTimeStamp
是 UTC
SessionTimeZone
是 Europe/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')
当 March
和 October
的小时发生变化时,我的问题出现了,因为在 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;
我有以下系统信息:
我使用 Oracle 数据库 10g
SysTimeStamp
是 UTC
SessionTimeZone
是 Europe/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')
当 March
和 October
的小时发生变化时,我的问题出现了,因为在 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;