Как получить числовую разницу между DBTIMEZONE и SESSIONTIMEZONE?

есть ли в 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 фактически позволяет вычислить разницу между двумя часовыми поясами?

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

разбор строки "Европа / Париж" в вашей функции будет намного сложнее... Вы должны использовать TZ_OFFSET функция, чтобы убедиться, что вы всегда получаете то же самое .

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 каждый год много изменений для часовых поясов, например мест изменяя их часовой пояс или изменения на начале, конце или разнице времени сбережений света дня, или високосные секунды вставляются в "случайные" времена.

таким образом, чтобы вычислить смещение, вы также должны указать конкретный момент времени, для которого вы хотите вычислить смещение (т. е. какую версию определений часовых поясов следует применять).

имея это в виду, становится ясно, как рассчитать разницу между двумя часовыми поясами в определенный момент времени:

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