#oracle #hibernate #timestamp #ojdbc #offsetdatetime
#Oracle #спящий режим #временная метка #ojdbc #offsetdatetime
Вопрос:
Я пытаюсь сохранить две даты в Oracle с помощью гибернации. обе даты имеют одинаковые временные метки в московском часовом поясе: 2005-10-30T02:00 03:00 [Европа / Москва] и 2005-10-30T02:00 04:00 [Европа / Москва] («Вс Окт. 30 02:00:00 MSK 2005» и «Вс окт. 30 02:00:00 MSD 2005»). даты разделены по времени на один час и связаны с переходом на зимнее / летнее время.
Я создал таблицу в Oracle:
create table TMP
(
ID LONG,
TS TIMESTAMP,
TSLTZ TIMESTAMP WITH LOCAL TIME ZONE,
TSTZ TIMESTAMP WITH TIME ZONE
);
и объект в моем модуле:
@Entity
@Table(name = "tmp")
public class DateTimeOracle {
private Long id;
private ZonedDateTime ts;
private ZonedDateTime tsltz;
private ZonedDateTime tstz;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public ZonedDateTime getTs() {
return ts;
}
public ZonedDateTime setTs(ZonedDateTime ts) {
this.ts = ts;
}
public ZonedDateTime getTsltz() {
return tsltz;
}
public ZonedDateTime setTsltz(ZonedDateTime tsltz) {
this.tsltz = tsltz;
}
public ZonedDateTime getTstz() {
return tstz;
}
public ZonedDateTime setTstz1(ZonedDateTime tstz) {
this.tstz = tstz;
}
}
В entity все поля инициализируются одной датой.
После сохранения обе даты в Oracle имеют одинаковые значения и выглядят как:
ts = 2005-10-30 02:00:00.000000
TSLTZ = 2005-10-29 23:00:00.000000
TSTZ = 2005-10-30 02:00:00.000000 04:00
Почему oracle сохраняет одно и то же значение для разных дат (включая смещение 04:00)? Есть ли способ это исправить?
P.S. Postgres сохраняет дату правильно. Один со смещением 03:00, другой со смещением 04:00 (2005-10-29 23:00:00.000000 и 2005-10-29 22:00:00.000000, соответственно).
Обновить
Вот как я создаю даты:
Date dt2 = new Date(1130623200000L); //2005-10-29 23:00:00 04:00
Date dt3 = new Date(1130626800000L); //2005-10-29 23:00:00 03:00
ZonedDateTime zdt2 = ZonedDateTime.ofInstant(dt2.toInstant(), ZoneId.systemDefault()); // My zone is MSK
ZonedDateTime zdt3 = ZonedDateTime.ofInstant(dt3.toInstant(), ZoneId.systemDefault()); // My zone is MSK
OffsetDateTime odt2 = zdt2.toOffsetDateTime();
OffsetDateTime odt3 = zdt3.toOffsetDateTime();
Если я не использую спящий режим и использую jdbc напрямую, ситуация не изменится.
Connection conn = DriverManager.getConnection("<oracle_url>",
"<username>", "<password>");
PreparedStatement pstmt = conn.prepareStatement("insert into tmp (id, TSTZ1, TSTZ2) values (200, ?, ?)", Statement.RETURN_GENERATED_KEYS);
pstmt.setDate(1, new java.sql.Date(dt2.getTime()));
pstmt.setDate(2, new java.sql.Date(dt3.getTime()));
int z1 = pstmt.executeUpdate();
pstmt.close();
conn.close();
Обновление 2
Если я сохраню OffsetDateTime через драйвер jdbc в метке времени с локальным часовым поясом или в метке времени с часовым поясом, тогда все в порядке.
PreparedStatement pstmt = conn.prepareStatement("insert into tmp (TSLTZ1, TSLTZ2, TSTZ1, TSTZ2) values (?, ?, ?, ?)");
pstmt.setObject(1, odt2);
pstmt.setObject(2, odt3);
pstmt.setObject(3, odt2);
pstmt.setObject(4, odt3);
Я вижу в БД:
2005-10-29 22:00:00.000000 2005-10-29 23:00:00.000000 2005-10-30 02:00:00.000000 04:00 2005-10-30 02:00:00.000000 03:00
Но если я сохраню ZonedDateTime, то значения в timestamp с локальным часовым поясом будут правильными, но в timestamp с часовым поясом будут неверными.
PreparedStatement pstmt = conn.prepareStatement("insert into tmp (TSLTZ1, TSLTZ2, TSTZ1, TSTZ2) values (?, ?, ?, ?)");
pstmt.setObject(1, zdt2);
pstmt.setObject(2, zdt3);
pstmt.setObject(3, zdt2);
pstmt.setObject(4, zdt3);
В БД я вижу:
2005-10-29 22:00:00.000000 2005-10-29 23:00:00.000000 2005-10-30 02:00:00.000000 04:00 2005-10-30 02:00:00.000000 04:00
Последние два значения неверны.
Комментарии:
1. Почему вы используете тип данных
LONG
? Он устарел целую вечность.2. Это тестовый пример, идентификатор используется для различения разных вставок
3. Я не думаю
java.sql.Date
, что это правильный класс.java.sql.Timestamp
может быть лучше.4. Ваш вопрос действительно сбивает с толку. Пожалуйста, сделайте четкое заявление для одной записи : что вы вставили? Каков результат (с использованием правильного выходного формата)? И чего вы ожидаете вместо этого? Затем, если необходимо, сделайте то же самое для второго значения.
5.
But I don't understand why this works wrong: pstmt.setDate(2, new java.sql.Date(dt2.getTime()));
— Вы не должны тратить свое время на анализ этого. API даты и времениjava.util
и их API форматированияSimpleDateFormat
устарели и подвержены ошибкам. Рекомендуется полностью прекратить их использование и переключиться на современный API даты и времени . Вы уже упоминали:If I save the date as pstmt.setObject(1, odt2); then all is ok!
. Обратите внимание, чтоjava.util.Date
в нем нет информации о часовом поясе.
Ответ №1:
Некоторые пояснения о типах данных временных меток Oracle:
TIMESTAMP
: Не сохраняет никакой информации о часовом поясе. Если вы вводите временную метку с указанием часового пояса, то информация о часовом поясе просто усекается и теряется.TIMESTAMP WITH TIME ZONE
: Сохраняет временную метку с информацией о часовом поясе (т.Е. Либо Как именованный регион, либо как смещение UTC) при вставке временной метки в базу данных.TIMESTAMP WITH LOCAL TIME ZONE
: Временная метка сохраняется какDBTIMEZONE
(рекомендуется и обычноUTC
). Временная метка отображается всегда и только в текущем сеансе пользователяSESSIONTIMEZONE
. Таким образом, он не отображает никакой информации о часовом поясе, потому что по определению это всегда ваш местный часовой пояс.
Какой из них я должен использовать?
Конечно, это зависит от ваших требований.
Поскольку TIMESTAMP WITH LOCAL TIME ZONE
вам не нужно заботиться о каких-либо настройках вашего клиента, время всегда отображается по местному времени. Время сохраняется в DBTIMEZONE
памяти, таким образом, вы теряете исходный вставленный часовой пояс.
Имейте в виду, когда вы создаете индекс на TIMESTAMP WITH TIME ZONE
. Невозможно создать индекс непосредственно по такому столбцу. Вместо этого Oracle создает виртуальный столбец для SYS_EXTRACT_UTC(TSTZ)
и создает индекс для этого виртуального столбца. Вам следует обратить на это внимание при разработке своих запросов.
Обновить
У вас совершенно особая ситуация. Когда вы вставляете TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow'
, то это время неоднозначно, это может означать 2005-10-30 02:00:00 03:00
или 2005-10-30 02:00:00 04:00
Возьмем этот пример:
SELECT TO_CHAR(TIMESTAMP '2005-10-30 00:00:00 Europe/Moscow' LEVEL * INTERVAL '1' HOUR,
'YYYY-MM-DD hh24:mi:ss TZH:TZM TZD tzr') AS ts
FROM dual
CONNECT BY LEVEL <= 4;
--------------------------------------------
|TS |
--------------------------------------------
|2005-10-30 01:00:00 04:00 MSD Europe/Moscow|
|2005-10-30 02:00:00 04:00 MSD Europe/Moscow|
|2005-10-30 02:00:00 03:00 MSK Europe/Moscow|
|2005-10-30 03:00:00 03:00 MSK Europe/Moscow|
--------------------------------------------
Взгляните на ВРЕМЕННУЮ МЕТКУ С типом данных ЧАСОВОГО ПОЯСА
Чтобы устранить неоднозначность граничных случаев, когда время переключается со стандартного времени на летнее, используйте как элемент формата TZR, так и соответствующий элемент формата TZD. Элемент формата TZD представляет собой аббревиатуру региона часового пояса с включенной информацией о переходе на летнее время. Примерами могут служить PST для тихоокеанского стандартного времени США и PDT для тихоокеанского дневного времени США. Следующая спецификация гарантирует, что будет возвращено значение летнего времени:
TIMESTAMP '1999-10-29 01:30:00 America/Los_Angeles PDT'
Если вы не добавляете элемент формата TZD, а значение datetime неоднозначно, база данных Oracle возвращает ошибку, если
ERROR_ON_OVERLAP_TIME
для параметра сеанса установлено значениеTRUE
. ЕслиERROR_ON_OVERLAP_TIME
установлено значениеFALSE
(значение по умолчанию), то база данных Oracle интерпретирует неоднозначную дату и время как стандартное время.
Обратите внимание, часовой пояс 04:00
или 03:00
не равен Europe/Moscow
. Часовой Europe/Moscow
пояс учитывает переход на летнее время (когда он все еще использовался в России около 10 лет назад), но 04:00
/ 03:00
этого не делает.
Извините, я никогда не использовал hibernate, поэтому я не знаю, как этот фреймворк обрабатывает такие данные. Я тоже не знаком с Java. Возможно, информация о переходе на летнее время не поддерживается.
Я могу только догадываться, класс java.sql.Date
и метод setDate
относятся к DATE
типу данных в Oracle. Как уже говорилось, лучше использовать java.sql.Timestamp
и setTimestamp
.
Тип DATE
данных не поддерживает никакой информации о часовом поясе. Если вы попытаетесь вставить DATE
значение в TIMESTAMP WITH [LOCAL] TIME ZONE
столбец, то Oracle действительно это сделает
FROM_TZ(CAST(<your DATE value> AS TIMESTAMP), SESSIONTIMEZONE)
Комментарии:
1. Спасибо за информацию, но вопрос был другим. Почему Oracle сохраняет одно и то же значение для двух разных дат?
2. Что такое «две разные даты» и результат? Я совершенно уверен, что когда вы вставляете
2005-10-30T02:00 03:00
, то TSTZ, конечно, нет2005-10-30 02:00:00.000000 04:00
3. Я подумал то же самое, но когда я вставляю 2005-10-30T02:00 03:00, то TSTZ я вижу в базе данных 2005-10-30 02:00:00.000000 04:00. Вы можете попробовать сохранить указанные даты в базе данных самостоятельно.
4. Редактировать: я сохраняю не 2005-10-30T02:00 03:00, а zdt3 и odt3 (см. Раздел вверху)
5. Вы правы, но когда я сохраняю даты из Java, у меня в базе данных неправильные значения, даже если я использую jdbc напрямую без спящего режима.
Ответ №2:
Ваши условия очень особенные, давайте попробуем немного облегчить их.
Время Unix 1130623200 равно 2005-10-29 22:00:00 UTC
- По московскому времени это так
2005-10-30 02:00:00 Europe/Moscow
, однако это неоднозначно. Это может быть - либо
2005-10-30 02:00:00 04:00 Europe/Moscow MSD
- или
2005-10-30 02:00:00 03:00 Europe/Moscow MSK
Время Unix 1130626800 равно 2005-10-29 23:00:00 UTC
- По московскому времени это так
2005-10-30 02:00:00 Europe/Moscow
, однако это неоднозначно. Это может быть - либо
2005-10-30 02:00:00 04:00 Europe/Moscow MSD
- или
2005-10-30 02:00:00 03:00 Europe/Moscow MSK
Согласно документации Oracle, неоднозначное TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow'
значение соответствует стандартному времени, т.Е. 2005-10-30 02:00:00 Europe/Moscow MSK 03:00
(как это было в 2005 году!)
Имейте в виду, в 2005 году московское стандартное время было MSK => 03:00
. В 2011 году российское правительство объявило, что переход на летнее время в будущем будет соблюдаться круглый год, что фактически заменит стандартное время. Т.Е. Сегодня московское стандартное время MSK => 04:00
называется MSD
до 2011 года.
Проверьте с помощью
SELECT
TO_CHAR(TIMESTAMP '2005-10-29 22:00:00 UTC' AT TIME ZONE 'Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_1_UTC,
TO_CHAR(TIMESTAMP '2005-10-29 23:00:00 UTC' AT TIME ZONE 'Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_2_UTC,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSK', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_MSK,
TO_CHAR(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSD', 'YYYY-MM-DD HH24:MI:SS TZH:TZM tzr TZD') AS TS_MSD
FROM dual
TS_1_UTC | TS_2_UTC | TS | TS_MSK | TS_MSD |
---|---|---|---|---|
2005-10-30 02:00:00 04:00 Европа /Moscow MSD | 2005-10-30 02:00:00 03:00 Европа/Москва MSK | 2005-10-30 02:00:00 03:00 Европа/Москва MSK | 2005-10-30 02:00:00 03:00 Европа/Москва MSK | 2005-10-30 02:00:00 04:00 Европа /Moscow MSD |
ALTER SESSION SET TIME_ZONE = 'Europe/Moscow';
SELECT
TO_CHAR(CAST(TIMESTAMP '2005-10-29 22:00:00 UTC' AT TIME ZONE 'Europe/Moscow' AS TIMESTAMP WITH LOCAL TIME ZONE), 'YYYY-MM-DD HH24:MI:SS TZD') AS TS_1_UTC,
TO_CHAR(CAST(TIMESTAMP '2005-10-29 23:00:00 UTC' AT TIME ZONE 'Europe/Moscow' AS TIMESTAMP WITH LOCAL TIME ZONE), 'YYYY-MM-DD HH24:MI:SS TZD') AS TS_2_UTC,
TO_CHAR(CAST(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow' AS TIMESTAMP WITH LOCAL TIME ZONE), 'YYYY-MM-DD HH24:MI:SS TZD') AS TS,
TO_CHAR(CAST(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSK' AS TIMESTAMP WITH LOCAL TIME ZONE), 'YYYY-MM-DD HH24:MI:SS TZD') AS TS_MSK,
TO_CHAR(CAST(TIMESTAMP '2005-10-30 02:00:00 Europe/Moscow MSD' AS TIMESTAMP WITH LOCAL TIME ZONE), 'YYYY-MM-DD HH24:MI:SS TZD') AS TS_MSD
FROM dual
TS_1_UTC | TS_2_UTC | TS | TS_MSK | TS_MSD |
---|---|---|---|---|
2005-10-30 02:00:00 MSD | 2005-10-30 02:00:00 MSK | 2005-10-30 02:00:00 MSK | 2005-10-30 02:00:00 MSK | 2005-10-30 02:00:00 MSD |
Я думаю, что Oracle делает это абсолютно правильно, независимо от того, есть ли у вас TIMESTAMP WITH TIME ZONE
или TIMESTAMP WITH LOCAL TIME ZONE
. Но вы должны тщательно проверить, что на самом деле вставлено в таблицу и как вы ее отображаете.