您可以获取 systimestamp 时区并使用它:
FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE to_char(systimestamp, 'TZR')
使用您的测试数据(但将布达佩斯更改为伦敦,因为那是我的本地区域):
SELECT
original_date,
stored_utc_date,
cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE 'Europe/London' as date) as reverted_good,
cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE DBTIMEZONE as date) as reverted_wrong,
cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE TO_CHAR(systimestamp, 'TZR') as date) as reverted_right
from (
select original_date, cast( SYS_EXTRACT_UTC(cast(original_date as timestamp )) as date) stored_utc_date
from (select to_date('2016-05-19 13:45:12','YYYY-MM-DD HH24:MI:SS') original_date from dual)
)
/
ORIGINAL_DATE STORED_UTC_DATE REVERTED_GOOD REVERTED_WRONG REVERTED_RIGHT
------------------- ------------------- ------------------- ------------------- -------------------
2016-05-19 13:45:12 2016-05-19 12:45:12 2016-05-19 13:45:12 2016-05-19 12:45:12 2016-05-19 13:45:12
除了...这并不总是有效,因为 TZR 被报告为偏移量(因为它基于操作系统 TZ),并且您无法从偏移量中猜测区域。如果原始日期是在冬天,而您在夏天运行,反之亦然,那么恢复的日期将是一个小时后。因此,有效的一半恢复日期总是错误的 - 但哪一半取决于您运行查询的时间。
看起来您可以通过使用 DBTIMEZONE 作为本地时区来解决这个问题:
cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE DBTIMEZONE
as timestamp with local time zone
你的测试查询再次:
SELECT
original_date,
stored_utc_date,
cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE 'Europe/London' as date) as reverted_good,
cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE DBTIMEZONE as date) as reverted_wrong,
cast(cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE DBTIMEZONE as timestamp with local time zone) as date) as reverted_right
from (
select original_date, cast( SYS_EXTRACT_UTC(cast(original_date as timestamp )) as date) stored_utc_date
from (select to_date('2016-05-19 13:45:12','YYYY-MM-DD HH24:MI:SS') original_date from dual)
)
/
ORIGINAL_DATE STORED_UTC_DATE REVERTED_GOOD REVERTED_WRONG REVERTED_RIGHT
------------------- ------------------- ------------------- ------------------- -------------------
2016-05-19 13:45:12 2016-05-19 12:45:12 2016-05-19 13:45:12 2016-05-19 12:45:12 2016-05-19 13:45:12
包含全年日期的更广泛的测试查询:
with t as (
select from_tz(cast(add_months(trunc(sysdate, 'MM'), 1-level) as timestamp), 'Europe/London')
as original_systimestamp
from dual
connect by level <= 12
)
select original_systimestamp,
cast(cast(from_tz(sys_extract_utc(original_systimestamp), 'UTC')
at time zone dbtimezone as timestamp with local time zone) as date) as good_date,
sys_extract_utc(original_systimestamp) as utc_timestamp,
from_tz(sys_extract_utc(original_systimestamp), 'UTC')
at time zone to_char(systimestamp, 'TZR') as at_systimezone,
from_tz(sys_extract_utc(original_systimestamp), 'UTC')
at time zone dbtimezone as at_dbtimezone,
cast(from_tz(sys_extract_utc(original_systimestamp), 'UTC')
at time zone dbtimezone as timestamp with local time zone) as at_local_dbtimezone
from t
order by original_systimestamp;
ORIGINAL_SYSTIMESTAMP GOOD_DATE UTC_TIMESTAMP AT_SYSTIMEZONE AT_DBTIMEZONE AT_LOCAL_DBTIMEZONE
----------------------------------- ------------------- --------------------- ---------------------------- ---------------------------- ----------------------------
2015-06-01 00:00:00.0 Europe/London 2015-06-01 00:00:00 2015-05-31 23:00:00.0 2015-06-01 00:00:00.0 +01:00 2015-05-31 23:00:00.0 +00:00 2015-06-01 00:00:00.0
2015-07-01 00:00:00.0 Europe/London 2015-07-01 00:00:00 2015-06-30 23:00:00.0 2015-07-01 00:00:00.0 +01:00 2015-06-30 23:00:00.0 +00:00 2015-07-01 00:00:00.0
2015-08-01 00:00:00.0 Europe/London 2015-08-01 00:00:00 2015-07-31 23:00:00.0 2015-08-01 00:00:00.0 +01:00 2015-07-31 23:00:00.0 +00:00 2015-08-01 00:00:00.0
2015-09-01 00:00:00.0 Europe/London 2015-09-01 00:00:00 2015-08-31 23:00:00.0 2015-09-01 00:00:00.0 +01:00 2015-08-31 23:00:00.0 +00:00 2015-09-01 00:00:00.0
2015-10-01 00:00:00.0 Europe/London 2015-10-01 00:00:00 2015-09-30 23:00:00.0 2015-10-01 00:00:00.0 +01:00 2015-09-30 23:00:00.0 +00:00 2015-10-01 00:00:00.0
2015-11-01 00:00:00.0 Europe/London 2015-11-01 00:00:00 2015-11-01 00:00:00.0 2015-11-01 01:00:00.0 +01:00 2015-11-01 00:00:00.0 +00:00 2015-11-01 00:00:00.0
2015-12-01 00:00:00.0 Europe/London 2015-12-01 00:00:00 2015-12-01 00:00:00.0 2015-12-01 01:00:00.0 +01:00 2015-12-01 00:00:00.0 +00:00 2015-12-01 00:00:00.0
2016-01-01 00:00:00.0 Europe/London 2016-01-01 00:00:00 2016-01-01 00:00:00.0 2016-01-01 01:00:00.0 +01:00 2016-01-01 00:00:00.0 +00:00 2016-01-01 00:00:00.0
2016-02-01 00:00:00.0 Europe/London 2016-02-01 00:00:00 2016-02-01 00:00:00.0 2016-02-01 01:00:00.0 +01:00 2016-02-01 00:00:00.0 +00:00 2016-02-01 00:00:00.0
2016-03-01 00:00:00.0 Europe/London 2016-03-01 00:00:00 2016-03-01 00:00:00.0 2016-03-01 01:00:00.0 +01:00 2016-03-01 00:00:00.0 +00:00 2016-03-01 00:00:00.0
2016-04-01 00:00:00.0 Europe/London 2016-04-01 00:00:00 2016-03-31 23:00:00.0 2016-04-01 00:00:00.0 +01:00 2016-03-31 23:00:00.0 +00:00 2016-04-01 00:00:00.0
2016-05-01 00:00:00.0 Europe/London 2016-05-01 00:00:00 2016-04-30 23:00:00.0 2016-05-01 00:00:00.0 +01:00 2016-04-30 23:00:00.0 +00:00 2016-05-01 00:00:00.0
但即使这样也只有在会话时区与数据库服务器的区域匹配时才有效;如果我将会话时区设置为欧洲/伦敦以外的其他时间,那就太远了。如果您依赖于能够设置会话时区,那么问题中的第一个带有硬编码区域的查询并不会更糟......
同样值得注意的是,DBTIMEZONE 不一定会告诉您任何有用的信息; Oracle recommend setting it to UTC。因此,如果您不能使用它,并且不能真正使用从systimestamp 提取的 TZR(因为它实际上是一个偏移量,并且您不能将其转换为一个区域,因此无法知道 DST 信息),我不知道'认为没有办法从数据库中保存的任何内容中找出原始系统时间。您似乎需要在某个时候提供服务器时区区域 - 通过设置会话时区或在您的原始查询中。