Почему моя временная шкала смещается во временной зоне?

У меня есть эта дата в базе данных 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), и что не мешает существующему пользовательскому интерфейсу.