Oracle to_date с форматом не показывает время

У меня простой расчет, я вычитаю интервал из даты со временем:

 select TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS')  - to_dsinterval('00 0:05:00') from dual;

Работает нормально, результат: 2016-12-05 22:59:59

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

select TO_DATE(
TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS') - to_dsinterval('00 0:05:00')) from dual;

но теперь он превращает время в нули. Результат должен быть: 2016-12-05 22:59:59, но фактический: 2016-12-05 00:00:00

Если я добавлю формат к внешнему to_date следующим образом:

select to_date( TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS') - to_dsinterval('00 0:05:00'), 'YYYY-MM-DD HH24:MI:SS') from dual;

Результат стал очень странным: 0005-12-16 00:00:00 Что я делаю не так?


person Nikolas    schedule 05.12.2016    source источник
comment
Результат вычитания уже является датой. Вам не нужен еще один to_date(), и это не решает проблему с часовыми поясами. Если вам нужна какая-то операция с часовыми поясами, используйте timestamp at time zone   -  person Kacper    schedule 05.12.2016


Ответы (2)


Тип данных DATE не поддерживает функции часового пояса, для этого необходимо использовать TIMESTAMP WITH TIME ZONE.

Ваш запрос

SELECT TO_DATE( TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS') - TO_DSINTERVAL('00 0:05:00'), 'YYYY-MM-DD HH24:MI:SS') 
FROM dual;

делает следующее:

  1. Создайте DATE '2016-12-05 23:04:59'
  2. Вычесть интервал '00 0:05:00'
  3. Приведение к VARCHAR2 (в формате NLS_DATE_FORMAT)
  4. Приведение к DATE с использованием формата YYYY-MM-DD HH24:MI:SS

В случае, если ваш NLS_DATE_FORMAT будет равен YYYY-MM-DD HH24:MI:SS, этот запрос возвращает правильный результат.

Используйте этот:

SELECT TO_TIMESTAMP('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS') - TO_DSINTERVAL('00 0:05:00')
FROM dual;

TO_DATE(... тоже работает. Если вам нужна поддержка часового пояса, вы должны сделать:

SELECT TO_TIMESTAMP_TZ('2016-12-05 23:04:59 Europe/Berlin', 'YYYY-MM-DD HH24:MI:SS TZR') - TO_DSINTERVAL('00 0:05:00')
FROM dual;
person Wernfried Domscheit    schedule 05.12.2016
comment
Над DATE типами данных может выполняться INTERVAL DAY TO SECOND арифметика. Если вы это сделаете, SELECT DUMP( TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS') - TO_DSINTERVAL('00 0:05:00') ) FROM DUAL вернет Typ=13, который является типом данных DATE, поэтому нет неявного приведения к TIMESTAMP. - person MT0; 05.12.2016

TO_DATE( char, fmt, nls ) принимает VARCHAR2 аргументов.

Выполнение TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS') - to_dsinterval('00 0:05:00') возвращает тип данных DATE, который, когда вы передаете его в TO_DATE(), оракул преобразует его в тип данных VARCHAR2, чтобы он соответствовал ожидаемому типу данных аргумента (неявно вызывая TO_CHAR( value, NLS_DATE_FORMAT ) для выполнения этого преобразования), а затем преобразует его обратно в тип данных DATE.

Вам просто нужно сделать:

SELECT TO_DATE('2016-12-05 23:04:59', 'YYYY-MM-DD HH24:MI:SS')
         - to_dsinterval('00 0:05:00')
FROM   DUAL;

Если вы хотите обрабатывать часовые пояса, используйте TIMESTAMP AT TIME ZONE и просто преобразуйте его в любой часовой пояс, в котором вы хотите сохранить дату:

SELECT TIMESTAMP '2016-12-05 23:04:59 Europe/Paris' AT TIME ZONE 'UTC'
FROM   DUAL;

(Создаст вашу временную метку в часовом поясе Парижа и преобразует ее в правильное время в часовом поясе UTC).

person MT0    schedule 05.12.2016