【问题标题】:How to get the numeric difference between DBTIMEZONE and SESSIONTIMEZONE?如何获得 DBTIMEZONE 和 SESSIONTIMEZONE 之间的数值差异?
【发布时间】:2013-08-07 00:13:28
【问题描述】:

Oracle 中是否有一种平滑的方法可以在当前时刻(当我执行调用时)获取 SESSIONTIMEZONE 和 DBTIMEZONE 之间的数值差异?

例如:

SELECT SESSIONTIMEZONE, DBTIMEZONE FROM DUAL;

返回:

+04:00  +07:00

所以,我需要某种函数,通过使用给定参数调用它,我得到这两个值之间的差异。

对于上面的例子:

SELECT get_numeric_offset(SESSIONTIMEZONE, DBTIMEZONE) FROM DUAL;

将返回 -3(符号很重要)。

当然,我可以自己编写这个函数,方法是处理字符串并解析它们,然后进行一些算术运算或做类似的事情(我仍然不认为这是一个非常顺利的解决方案:

SELECT (
        CAST(SYSTIMESTAMP AT TIME ZONE SESSIONTIMEZONE AS DATE) - 
        CAST(SYSTIMESTAMP AT TIME ZONE DBTIMEZONE AS DATE)
       )*24 
 FROM DUAL;

也许我错过了什么,而 Oracle 实际上提供了一种计算两个给定时区之间差异的方法?

【问题讨论】:

    标签: oracle oracle11g oracle10g timezone timezone-offset


    【解决方案1】:

    你想要什么是不可能的,因为时区不是固定的,而是随着时间的推移而变化。因此,您无法计算两个时区之间的差异,而只能计算在某个时间点有效的差异。

    时区随时间变化是很常见的。在TZ Database 中,时区每年都有很多变化,例如改变时区的地方或夏令时的开始、结束或差异,或在“随机”时间插入闰秒。

    因此,要计算偏移量,您还必须给出一个特定的时间点,您希望针对该时间点计算偏移量(即应该应用哪个版本的时区定义)。

    考虑到这一点,就很清楚了,如何在某个时间点计算两个时区之间的差异:

    select 
      (TIMESTAMP '2012-06-30 22:00:00 US/Pacific') - (TIMESTAMP '2012-06-30 22:00:00 Europe/Berlin') as diff
    from
        dual
    ;
    
    DIFF
    ------------------------------
    +000000000 09:00:00.000000000
    

    当您获得这些信息后,您必须决定要达到哪种精度的差异。我特意选择了上面的日期,因为那天晚上插入了闰秒。因此,在某个时间点,您可能会得到九小时零一秒(或者是九小时减去一秒)的差异。

    同样重要的是,您插入的是准确的位置,而不是其他一些时间戳信息。位置可以更改为不同的时区。因此,您必须指定拖车位置和时间点才能获得正确的时区差异。

    如果您想四舍五入,可以使用EXTRACT(HOUR FROM ...)EXTRACT(MINUTE FROM ...) 访问其组件。如果想在第二级进行四舍五入,可以使用以下技巧:

    select 
        (to_number(to_char(sys_extract_utc(TIMESTAMP '2012-07-01 01:00:00 US/Pacific'), 'SSSSSFF3')) 
        - to_number(to_char(sys_extract_utc(TIMESTAMP '2012-07-01 01:00:00 Europe/Berlin'), 'SSSSSFF3')))/1000 as diff_s
    from 
        dual
    ;
    
        DIFF_S
    ----------
        -54000
    

    【讨论】:

    • 是的,我确实牢记时区会随着时间而变化。我需要从一个时区到另一个时区的当前偏移量(在当前时刻),我不需要非常严格的精度(几个小时就足够了)。
    【解决方案2】:

    关于使用 SESSIONTIMEZONE 的一点警告

    您的第一个示例不是防弹的,因为会话时区可以设置为以下任一:

    • 操作系统本地时区 ('OS_TZ')
    • 数据库时区 ('DB_TZ')
    • 与 UTC 的绝对偏移量(例如,'-05:00'
    • 时区区域名称(例如,'Europe/London'

    看看这里发生了什么:

    SELECT SESSIONTIMEZONE, DBTIMEZONE FROM DUAL;
    
    SESSIONTIMEZONE  |  DBTIMEZONE
             +04:00  |      +07:00
    
    ALTER SESSION SET TIME_ZONE='Europe/Paris';
    
    SELECT SESSIONTIMEZONE, DBTIMEZONE FROM DUAL;
    
    SESSIONTIMEZONE  |  DBTIMEZONE
       Europe/Paris  |      +07:00
    

    在您的函数中解析“Europe/Paris”字符串将变得更加困难......您应该使用TZ_OFFSET 函数,以确保您始终获得相同的±HH:MM 格式。

    ALTER SESSION SET TIME_ZONE='Europe/Paris';
    
    SELECT DBTIMEZONE, SESSIONTIMEZONE, TZ_OFFSET(SESSIONTIMEZONE) FROM DUAL;
    
     DBTIMEZONE | SESSIONTIMEZONE | TZ_OFFSET(SESSIONTIMEZONE)
         +07:00 |    Europe/Paris |                     +02:00
    

    关于您的第二个解决方案

    我想我更喜欢您的第二种解决方案。您可以使用CURRENT_DATE 而不是CAST(SYSTIMESTAMP AT TIME ZONE SESSIONTIMEZONE AS DATE) 来缩短它,它们是等价的:

    SELECT (
            CURRENT_DATE - 
            CAST(SYSTIMESTAMP AT TIME ZONE DBTIMEZONE AS DATE)
           )*24 
     FROM DUAL;
    

    从技术上讲,你甚至可以做到这一点!

    SELECT (CURRENT_DATE - SYSDATE) * 24 
     FROM DUAL;
    

    但实际上SYSDATE 不能保证与DBTIMEZONE 在同一时区。 SYSDATE 始终绑定到底层操作系统的时区,而 DBTIMEZONE 可以在服务器启动后更改。

    在我扯皮的同时,我还应该提醒您,某些国家/地区使用的偏移量不是整数,例如伊朗标准时间是UTC+03:30,缅甸时间是UTC+06:30...我不知道不知道您是否会在生产环境中遇到这种情况,但我希望您可以接受查询返回类似1.5...的可能性...

    【讨论】:

    • 感谢您为我提供此信息。我喜欢你回答的连贯性,尽管我已经知道你所说的所有事情。我知道我一般需要使用tz_offset,并且知道有些时区不是整数。我投票赞成你的答案,但它仍然不是这个问题的答案。
    猜你喜欢
    • 1970-01-01
    • 2014-10-04
    • 1970-01-01
    • 2020-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多