Почему моя временная шкала смещается во временной зоне?
У меня есть эта дата в базе данных PostgreSQL 9.1 в столбце timestamp without time zone
:
2012-11-17 13:00:00
Это означает, что он находится в формате UTC, и это я проверял, выбрав его как временную метку UNIX (EXTRACT epoch
).
int epoch = 1353157200; // from database
Date date = new Date((long)epoch * 1000);
System.out.println(date.toGMTString()); // output 17 Nov 2012 13:00:00 GMT
Однако, когда я читал эту дату с использованием JPA/Hibernate, все идет не так. Это мое отображение:
@Column(nullable=true,updatable=true,name="startDate")
@Temporal(TemporalType.TIMESTAMP)
private Date start;
Date
Я получаю, однако, следующее:
17 Nov 2012 12:00:00 GMT
Почему это происходит, и что более важно, как я могу остановить его?
Обратите внимание, что я просто хочу хранить точки во времени, повсеместно (как это делает java.util.Date
), и мне было все равно, о часовых поясах, за исключением того, что я, очевидно, не хочу, чтобы они повреждали мои данные.
Как вы, вероятно, вывели, клиентское приложение, которое подключается к базе данных, находится в UTC + 1 (Нидерланды).
Кроме того, выбор типа столбца timestamp without time zone
был сделан Hibernate, когда он автоматически сгенерировал схему. Может быть, это может быть timestamp with time zone
вместо?
Ответы
Ответ 1
Если база данных не предоставляет информацию о часовом поясе, тогда драйвер JDBC должен обрабатывать ее, как если бы она находилась в локальном часовом поясе JVM (см. PreparedStatement.setDate(int, Date)):
Устанавливает назначенный параметр для данного значения java.sql.Date, используя часовой пояс по умолчанию для виртуальной машины, на которой запущено приложение.
Спецификация Javadoc и JDBC явно не говорят ничего о ResultSet
и т.д., но для обеспечения согласованности большинство драйверов также применяют это правило к датам, полученным из базы данных. Если вам нужен явный контроль за используемым часовым поясом, вам нужно будет использовать различные методы set/getDate/Time/Timestamp
, которые также принимают объект Calendar
в нужном часовом поясе.
Некоторые драйверы также предоставляют свойство соединения, позволяющее указать часовой пояс для использования при преобразовании в/из базы данных.
Ответ 2
Я обнаружил, что изменение типа столбца на timestamp with time zone
устраняет проблему. Мне также придется преобразовать все мои другие столбцы метки времени.
Из этого я делаю вывод, что Hibernate не читает столбцы timestamp without time zone
как в UTC, а как в локальном часовом поясе. Если есть способ заставить его интерпретировать их как UTC, пожалуйста, дайте мне знать.
Ответ 3
Существует несколько решений для решения проблем:
1) Самый простой способ - установить часовой пояс в строке подключения JDBC - пока база данных поддерживает это.
Для MySQL вы можете использовать useGmtMillisForDatetimes=true
, чтобы принудительно использовать UTC в базе данных. Насколько я знаю, Postgres не поддерживает такой вариант, но я могу ошибаться, потому что я не использую Postgres.
2) Установите часовой пояс по умолчанию в вашей клиентской программе Java с помощью
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
Недостаток: там вы также изменяете часовой пояс, где вы не хотите, например, если ваша программа имеет часть пользовательского интерфейса.
3) Используйте сопоставление со специальным геттером только для Hibernate:
В файле сопоставления
<property name="myTimeWithTzConversion" type="timestamp" access="property">
<column name="..." />
</property>
и в вашей программе у вас есть get/setMyTimeWithTzConversion()
для доступа только с помощью hibernate к переменной-члену Timestamp myTime
, а в этом получателе/установщике вы выполняете преобразование в часовом поясе.
Наконец, мы решили 3) (это немного больше работы по программированию), потому что там нам не нужно было менять существующую базу данных, наша база данных находилась в UTC + 1 (что запрещает решение строки подключения JDBC), и что не мешает существующему пользовательскому интерфейсу.