What is the inverse of SYS_EXTRACT_UTC() in Oracle?
设
背景
一些愚蠢的程序将日期值写入UTC中的一个表中,即
1 | cast(SYS_EXTRACT_UTC(SYSTIMESTAMP) as date) |
在此表的日期类型列中存储而不是SYSDATE。
在所有其他表中,简单地将SYSDATE存储在此类列中。 我的任务是将这些值一起使用,所以我想要回到SYS_EXTRACT_UTC()函数的效果。 只有当我手动指定我的时区时,我才能解决这个问题
1 | cast( FROM_TZ(cast(my_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE 'Europe/Budapest' as date) |
但是,如果我使用
例如,当to_char(SYSDATE,'YYYY-MM-DD HH24:MI:SS')='2016-05-19 13:45:12'时,程序存储
1 | cast(SYS_EXTRACT_UTC(cast(SYSDATE as timestamp)) as date) |
我的测试查询是:
1 2 3 4 5 6 7 8 9 | SELECT original_date, stored_utc_date, cast( FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE 'Europe/Budapest' as date) as reverted_good, cast( FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE DBTIMEZONE as date) as reverted_wrong 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) ) |
其结果是:
1 2 3 | ORIGINAL_DATE STORED_UTC_DATE REVERTED_GOOD REVERTED_WRONG ------------------- ------------------- ------------------- ------------------- 2016-05-19 13:45:12 2016-05-19 11:45:12 2016-05-19 13:45:12 2016-05-19 12:45:12 |
您可以获取systimestamp时区区域并使用它:
1 | FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE to_char(systimestamp, 'TZR') |
使用您的测试数据(但将布达佩斯改为伦敦,因为这是我的本地区域):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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作为本地时区来解决这个问题:
1 2 | cast(FROM_TZ(cast(stored_utc_date as TIMESTAMP), 'UTC') AT TIME ZONE DBTIMEZONE as timestamp with local time zone |
您的测试查询再次:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
更广泛的测试查询,包含全年的日期:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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建议将其设置为UTC。因此,如果您不能使用它,并且无法真正使用从