【问题标题】:What is the inverse of SYS_EXTRACT_UTC() in Oracle?Oracle 中 SYS_EXTRACT_UTC() 的倒数是什么?
【发布时间】:2016-09-16 08:41:48
【问题描述】:

问题

original_date 成为date。我如何才能从 SYS_EXTRACT_UTC(cast(original_date as timestamp )) 的结果中取回它的值,完全由内部 Oracle 函数,即不管数据库的任何时区设置如何。

背景

一些愚蠢的程序将日期值写入UTC格式的表之一,即

cast(SYS_EXTRACT_UTC(SYSTIMESTAMP) as date) 

存储在此表的日期类型列中,而不是 SYSDATE。

在所有其他表中,SYSDATE 仅存储在此类列中。我的任务是一起使用这些值,所以我想恢复 SYS_EXTRACT_UTC() 函数的效果。只有当我手动指定我的时区时,我才能解决这个问题,即通过

cast( FROM_TZ(cast(my_utc_date as TIMESTAMP), 'UTC')  AT TIME ZONE 'Europe/Budapest' as date)

但如果我使用DBTIMEZONE 而不是'Europe/Budapest',则会得到错误的结果,可能是因为DBTIMEZONE 忽略了夏季夏令时

例如当to_char(SYSDATE,'YYYY-MM-DD HH24:MI:SS') = '2016-05-19 13:45:12'时,则程序存储

 cast(SYS_EXTRACT_UTC(cast(SYSDATE as timestamp)) as date) 

我的测试查询是:

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)
    )

其结果是:

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 

【问题讨论】:

  • 你试过SESSIONTIMEZONE而不是DBTIMEZONE吗?
  • @MartinSchapendonk - 假设运行查询的会话始终与数据库服务器位于同一时区,或者至少声明它是。这可能不是一个安全的假设。
  • @AlexPoole 如果这不是一个安全的假设,那么我认为不可能重建原始时间戳,因为记录可能来自许多不同的时区(因为 sys_extract_utc 使用会话时区提取物也是如此)。
  • @MartinSchapendonk - 不,问题中的sys_extract_utc() 调用使用的是systimestamp,它使用数据库服务器时区(但不是DBTIMEZONE)。如果它使用了基于会话的current_timestamp,那么您将无法重建它,因为该会话的时区是未知的。 (测试查询使用固定日期和隐式到时区转换,这稍微混淆了)
  • @AlexPoole Oracle 文档状态“如果未指定时区,则日期时间与会话时区相关联。”我在这里错过了什么?

标签: oracle date timezone utc date-conversion


【解决方案1】:

您可以获取 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 信息),我不知道'认为没有办法从数据库中保存的任何内容中找出原始系统时间。您似乎需要在某个时候提供服务器时区区域 - 通过设置会话时区或在您的原始查询中。

【讨论】:

  • 不,这不对...to_char(systimestamp, 'TZR') 将显示“区域”作为偏移量,在我的情况下为 +01:00,因此结果会根据它是否夏令。嗯。
  • 我认为没有恐慌,这是正确的。现在它向我显示 +02:00,因为现在是夏天。在冬天它会显示 +01:00,这正是我所需要的。
  • @mma - 但是 +01:00 或 +02:00 应用于每个存储的日期,无论存储时是夏季还是冬季。所以一半的恢复日期将是一个小时;在夏天,原来的冬季日期会出错,在冬天,原来的夏季日期会出错。
  • 哦,是的,你是对的。但是,幸运的是,这只会在夏季/冬季切换后的前 5 分钟内对我们造成暂时错误,因为此转换将每 5 分钟执行一次。当然,如果可能的话,最好有一个完美的解决方案。
  • @mma - 我还在考虑。现在我认为我刚刚所做的编辑也可能是错误的......需要使用不同的会话 TZ 进行测试。同时,回到您的原始查询,为什么手动将 TZR 设置为布达佩斯是错误的——这是否需要在不同时区的不同数据库上运行?如果您提供的值与您已经拥有的实际服务器时区匹配,则可能没问题。
猜你喜欢
  • 2021-05-16
  • 2018-09-18
  • 1970-01-01
  • 2014-02-20
  • 2020-11-21
  • 2015-03-14
  • 1970-01-01
  • 2022-10-04
  • 1970-01-01
相关资源
最近更新 更多