JodaTime Hibernate поддерживает сохранение DateTime с неправильным часовым поясом

Я знаю, что есть похожие вопросы, но я хотел прояснить, в чем именно проблема.

В основном я пытаюсь сохранить дату в своей базе данных Oracle. Я хочу, чтобы он хранился по всемирному координированному времени. Столбец в базе данных - это TIMESTAMP, я не сохраняю информацию о часовом поясе.

Глядя на класс поддержки JodaTime Hibernate PersistentDateTime, кажется, что метод nullSafeGet () просто вызывает DateTime.toDate (), который возвращает java.util.Date. Это дата, сохраненная в базе данных.

Я не понимаю ... почему getDate () не сохраняет часовой пояс существующего DateTime? Я отправляю ему DateTime (скажем, 17:00) в UTC, и после вызова getDate () он переключается на мой часовой пояс, CET (18:00). Я хочу сохранить 17:00, а не 18:00.

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

Моя текущая идея состоит в том, чтобы реализовать класс, который расширяет PersistentDateTime и переопределяет метод nullSafeGet (), чтобы он поддерживал часовой пояс передаваемого ему DateTime. Не знаете точно, как это сделать ... возможно, в API JodaTime есть метод, который делает это элегантно?

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

ИЗМЕНИТЬ

Ради потомков и чтобы ответить полезным комментаторам, я хотел бы прояснить, в чем конкретно заключалась моя проблема.

Я думал, что проблема в том, что мой сервер приложений и мой сервер db имеют разные часовые пояса. Оказывается, проблема не в этом. Проблема скорее в том, что сервер приложений или JVM имеет другой часовой пояс, чем тот, который я использую в своем приложении. Позволь мне объяснить.

Мое веб-приложение управляет событиями. У событий есть дата начала и окончания. Это столбцы TIMESTAMP (без часового пояса) в базе данных Oracle. Кроме того, в приложении вы должны указать, какой часовой пояс вы используете. Это не зависит от часового пояса, используемого сервером приложений (а затем JVM) или сервером базы данных.

Но, конечно, у сервера приложений есть есть часовой пояс. В моем случае это CST, центральное стандартное время (GMT-6). Проблема возникает, когда часовой пояс, используемый в приложении, не совпадает с часовым поясом, используемым на уровне JVM. В моем случае часовой пояс, определенный в приложении, - EST, Eastern Standard Time (GMT-5).

Так что же происходит? Что происходит, я создаю событие из панели веб-администратора, например:

  • Дата начала: 15 января 2014 г., 09:00
  • Дата окончания: 20 января 2014 г., 23:00

Эти даты создаются с использованием Joda DateTime и им назначается правильный часовой пояс (с помощью DateTime.withZoneRetainFields ()), а именно тот, который я использую в приложении, EST. Итак, мое событие выглядит так:

  • Название события: Событие 1
  • Дата начала: 15.01.2014 09:00 EST
  • Дата окончания: 20.01.2014 23:00 EST

Все идет нормально. Однако, когда я сохраняю его, даты сохраняются следующим образом:

  • Дата начала: 15.01.2014 08:00
  • Дата окончания: 20.01.2014 22:00

Что случилось? Произошло то, что, поскольку сервер приложений / JVM работает в CST, из обеих дат был вычтен час. Я думал, что это ошибка, но, как полагают комментаторы, это особенность. Драйверы JodaTime Hibernate просто вызывают DateTime.toDate (), который создает java.util.Date, который сам по себе не имеет часового пояса, но автоматически меняет час в соответствии с часовым поясом, используемым JVM.

Как мне этого избежать? Что ж, я мог бы запустить JVM с параметром -Duser.timezone и убедиться, что он всегда совпадает с часовым поясом, используемым в приложении. Это один из вариантов.

Еще я мог бы сделать, вместо того, чтобы избежать проблемы, поработать с ней. Другими словами, на уровне Hibernate / базы данных всякий раз, когда я загружаю событие, убедитесь, что полям даты начала и окончания назначен часовой пояс, определенный приложением, и, таким образом, часы будут преобразованы обратно в часовой пояс, используемый в приложении. Таким образом, даже если они сохранены как 08:00 и 22:00, в приложении они будут отображаться как 09:00 и 23:00. Это, очевидно, лучшее и более точное решение. Но я просто не уверен, стоит ли это всей работы. Я видел различные способы сделать это с помощью Hibernate в Интернете - с использованием типа доступа к свойству или настраиваемого типа пользователя Hibernate. Не уверен, работают ли они должным образом или нет, и кажется, что это много работы и очень навязчиво. Мне нужно перейти от использования DateTime к Calendar (я думаю, не уверен) и / или сделать специальные сопоставления в моем POJO.

По правде говоря, у нас есть java.util.Dates по всему приложению, и во многих случаях просто выполняется new Date () для их создания без учета часового пояса. Все они в конечном итоге должны быть изменены на DateTime и явно созданы с использованием часового пояса, определенного приложением.

Но это большая работа. На данный момент я решил просто использовать параметр -Duser.timezone и посмотреть, как это получится. Опять же, не лучшее решение, но я думаю, что пока оно отвечает нашим потребностям.

Большое спасибо всем комментаторам за то, что они «открыли мне глаза».


person Robert Bowen    schedule 26.01.2014    source источник
comment
Код. Нам нужен код. (И ответ почти наверняка заключается в том, что дата хранится внутри как смещение от эпохи и просто интерпретируется при загрузке или отображается в вашем местном часовом поясе.)   -  person chrylis -cautiouslyoptimistic-    schedule 26.01.2014


Ответы (2)


j.u.Date Is Tricky

Комментарий chrylis, скорее всего, правильный: класс java.util.Date вводит вас в заблуждение. Date не имеет часового пояса, но его toString реализация применяет часовой пояс JVM по умолчанию. Поэтому неопытному Java-программисту кажется, что объект Date имеет часовой пояс, но на самом деле его не.

Если мы с Крилисом правы, то у вас нет проблемы с базой данных, у вас нет проблемы с JDBC, у вас нет проблемы с гибернацией и у вас нет Joda-Time. Проблема заключается в плохо спроектированных и реализованных классах java.util.Date/Calendar.

Другое возможное объяснение похоже ... Joda-Time DateTime экземпляр действительно знает назначенный ему часовой пояс. Если вы не укажете часовой пояс, будет назначен часовой пояс по умолчанию JVM. Или укажите объект часового пояса (DateTimeZone class), например предопределенная константа _2 _ объект.

Вывод: у вас, вероятно, вообще нет проблем или ошибок. Просто неправильно понятые особенности.

Домашнее задание…

Объединенные классы Java…

java.util.Date date = new java.util.Date();
long dateMillis = date.getTime();

Joda-Time… (преобразовать этот экземпляр java.util.Date)

DateTime dateTime = new DateTime( date, DateTimeZone.UTC );     
long dateTimeMillis = dateTime.getMillis();

Выгрузить в консоль…

System.out.println( "date: " + date + " = " + dateMillis );
System.out.println( "dateTime: " + dateTime + " = " + dateTimeMillis );

При запуске…

date: Sat Jan 25 17:18:02 PST 2014 = 1390699082010
dateTime: 2014-01-26T01:18:02.010Z = 1390699082010
person Basil Bourque    schedule 26.01.2014
comment
Вы оба правы. Единственное, чего мне не хватало, так это сравнения результатов getTime () из моего java.util.Date с getMillis () из моего Joda DateTime. Если бы я сделал это, я бы увидел, что они были в одно и то же мгновение. Проблема, с которой я столкнулся, по-видимому, заключается в том, что настройки часового пояса как для веб-сервера, так и для сервера базы данных различаются для моей тестовой среды, среды контроля качества, предварительной и производственной среды. Это беспорядок. Но похоже, что проблема на уровне JDBC; если я могу всегда предполагать, что веб-сервер работает в одной и той же TZ (будь то GMT, CET и т. д.), он будет постоянно сохранять. - person Robert Bowen; 27.01.2014
comment
@RobertBowen Напротив, я рекомендую вам всегда указывать часовой пояс, а не полагаться на значения по умолчанию. Если вы укажете, то не имеет значения, в каком месте / часовом поясе установлен ваш сервер. Вам нужно только предположить, что часы сервера установлены на правильное время для назначенного ему часового пояса. И даже этого я не предполагаю; в своих процедурах запуска и обслуживания я вызываю надежный локальный сервер времени или через Интернет и сравниваю к часам сервера. См. класс DateTimeZone. - person Basil Bourque; 28.01.2014
comment
Под «всегда использовать часовой пояс» вы имеете в виду сохранение на диске с данными даты / времени? У нас нет полей TZ в базе данных, и, к сожалению, мы не можем их добавить. Итак, если в моем Linux-боксе / Java есть TZ CST, но в приложении пользователь также выбирает, какой TZ они хотят использовать (например, CET), когда я сохраняю новый DateTime, скажем, 11:00 CET, он сохраняется в 4:00. Поэтому, когда я загружаю его из базы данных, по умолчанию это будет 4:00 по центральноевропейскому времени. Чтобы отобразить его правильно и выполнить вычисления, мне нужно назначить приложение TZ (CET), чтобы снова было 11:00. Итак, хотя полагаться на TZ по умолчанию - это плохо, необходимость вручную изменять TZ для каждой загрузки БД тоже не очень хорошо, нет? - person Robert Bowen; 28.01.2014
comment
Я только что понял, что в своем первом посте я не упомянул концепцию TZ на уровне приложения. Прости за это. Я был убежден, что моя проблема заключалась в разных TZ между веб-сервером и сервером db. Но теперь я знаю, что проблема в том, что Linux / JVM TZ отличается от приложения TZ. Если они совпадают, дата сохраняется в том виде, в котором она отображается в приложении. В противном случае дата пересчитывается с использованием JVM TZ и сохраняется. В любом случае, я полагаю, что мог бы создать какой-то класс Hibernate, который автоматически изменяет дату приложения TZ при загрузке, но я не знаю, стоит ли оно того ... В любом случае, спасибо за вашу помощь. - person Robert Bowen; 28.01.2014
comment
Я парень Postgres, а не Oracle, поэтому я не уверен, но согласно этот документ, если вы действительно используете код типа TIMESTAMP 180, то это нормально, если вы на 100% уверены, что все точки входа в Oracle (все части всех приложений, все команды- использование строки и т. д.) преобразуют значения даты и времени в значения UTC / GMT при вставках и обновлениях. Ваш драйвер JDBC может вам в этом помочь, прочтите документацию по драйверу. То же самое для Hibernate (который я не использую). - person Basil Bourque; 29.01.2014
comment
Я не понимаю, что вы имеете в виду под приложением TZ. Но если у вас или ваших системных администраторов есть надлежащий контроль над вашими системами, вы сможете установить реальный часовой пояс своей ОС без каких-либо проблем. Обычно tz ОС сервера настроен на UTC / GMT (или Atlantic / Reykjavik), но как разработчик приложений я никогда не хочу зависеть от того, что системный администратор делает правильные вещи. Вы должны знать и контролировать, что ваше приложение, драйвер JDBC, Hibernate и база данных делают с вашими данными. В вашем приложении Java вы всегда должны явно устанавливать часовой пояс для объектов Joda-Time (или java.time. *). - person Basil Bourque; 29.01.2014

Проблема в основном находится внутри драйвера JDBC, который пытается преобразовать даты в / из текущего часового пояса JVM, как сказал @RobertBowen. Есть несколько способов найти обходной путь для этой проблемы, как уже было прокомментировано выше (с использованием UTC). Однако я оставляю вам два возможных немедленных решения (хотя и не лучшие практики).

1.- При запуске JVM установите значение:

-Duser.timezone параметр

2.- Когда ваше приложение запускается, установите желаемый часовой пояс: TimeZone.setDefault(TimeZone.getTimeZone("Mexico/General"));

person darkconeja    schedule 29.11.2016