Question

Is there in Oracle a smooth way to get the numeric difference between SESSIONTIMEZONE and DBTIMEZONE at the current moment (when I perform the call)?

For instance:

SELECT SESSIONTIMEZONE, DBTIMEZONE FROM DUAL;

Returns:

+04:00  +07:00

So, i need some kind of function, by calling which with given parameters, I get the difference between these two values.

For the instance above:

SELECT get_numeric_offset(SESSIONTIMEZONE, DBTIMEZONE) FROM DUAL;

Would return -3 (the sign is crucial).

Of course, it's possible for me to write this function myself by working with strings and parsing them and then proceeding some arithmetical operations or do something like this (which I still don't consider as a pretty smooth solution:

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

Maybe I missed something and Oracle actually provides a way to calculate difference between two given timezones?

Was it helpful?

Solution

A little warning on using SESSIONTIMEZONE

Your first example is not bullet-proof, as the session time zone can be set to either of the following :

  • Operating system local time zone ('OS_TZ')
  • Database time zone ('DB_TZ')
  • Absolute offset from UTC (for example, '-05:00')
  • Time zone region name (for example, 'Europe/London')

Just look at what happens here :

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

Parsing that 'Europe/Paris' string in your function is going to be a lot harder... You should use the TZ_OFFSET function, to ensure that you always get the same ±HH:MM format.

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

About your second solution

I think I like your second solution better. You can shorten it by using CURRENT_DATE instead of CAST(SYSTIMESTAMP AT TIME ZONE SESSIONTIMEZONE AS DATE), they are equivalent :

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

You could technically even do this !

SELECT (CURRENT_DATE - SYSDATE) * 24 
 FROM DUAL;

BUT actually, SYSDATE is not guaranteed to be on the same timezone as DBTIMEZONE. SYSDATE is always bound to the underlying OS' timezone, while DBTIMEZONE can be altered once the server has been started.

And while I'm splitting hairs, I should also remind you that certain countries / regions use offsets that are not whole numbers, for example Iran Standard Time is UTC+03:30, Myanmar Time is UTC+06:30... I don't know if you'll ever encounter this in your production environment, but I hope you're okay with the possibility that your query returns something like 1.5...

OTHER TIPS

What you want is not possible, because timezones are not fixed, but change over time. Hence you cannot calculate THE difference between two timezones, but only the difference that is valid at a certain point in time.

It is quite common, that timezones change over time. In the TZ Database there are each year many changes for the time zones, e.g. places changing their time zone or changes on the start, end or difference of the day light saving time, or leap seconds getting inserted at "random" times.

So to calculate the offset, you must also give a particular point in time, for which you want the offset calculated (i.e. which version of the time zone definitions should be applied).

With this in mind, it becomes clear, how to calculate the difference between two time zones at a certain point in time:

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

When you have this information, you have to decide, up to which precision you want the difference. I have chosen the date above on purpose, because on that night a leap second was inserted. So there might be some point in time, where you will get a difference of not exactly nine hours but of nine hours and one second (or is it nine hours minus one second).

It is also important, that you insert the exact locations and not some other timestamp information. Locations can change to different time zones. Hence you have to specify tow locations and a point in time to get the correct time zone difference.

If you want to round the difference, you can access its components with EXTRACT(HOUR FROM ...) and EXTRACT(MINUTE FROM ...). If want to round at the second level, you can use the following trick:

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top