Вопрос-ответ

Is java.sql.Timestamp timezone specific?

Специфичен ли часовой пояс java.sql.Timestamp?

Я должен хранить дату и время UTC в базе данных.
Я преобразовал дату и время, указанные в определенном часовом поясе, в UTC. для этого я следовал приведенному ниже коду.
Моя входная дата-время "20121225 10:00:00 Z", часовой пояс "Азия / Калькутта"
Мой сервер / база данных (oracle) работает в том же часовом поясе (IST) "Азия / Калькутта"

Получаем объект Date в этом конкретном часовом поясе

        String date = "20121225 10:00:00 Z";
String timeZoneId = "Asia/Calcutta";
TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
//This date object is given time and given timezone
java.util.Date parsedDate = dateFormatLocal.parse(date + " "
+ timeZone.getDisplayName(false, TimeZone.SHORT));

if (timeZone.inDaylightTime(parsedDate)) {
// We need to re-parse because we don't know if the date
// is DST until it is parsed...
parsedDate = dateFormatLocal.parse(date + " "
+ timeZone.getDisplayName(true, TimeZone.SHORT));
}

//assigning to the java.sql.TimeStamp instace variable
obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));

Сохранить в БД

        if (tsSchedStartTime != null) {
stmt.setTimestamp(11, tsSchedStartTime);
} else {
stmt.setNull(11, java.sql.Types.DATE);
}

ВЫВОД

База данных (oracle) сохранила то же самое значение dateTime: "20121225 10:00:00 не в UTC.

Я подтвердил из приведенного ниже sql.

     select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable

Мой сервер БД также работает в том же часовом поясе "Азия / Калькутта"

Это дает мне следующие результаты


  1. Date.getTime() отсутствует в UTC

  2. Или временная метка влияет на часовой пояс при сохранении в БД, что я здесь делаю не так?

Еще один вопрос:

Будет timeStamp.toString() печататься в локальном часовом поясе, как это делает java.util.date? Не UTC?

Переведено автоматически
Ответ 1

Хотя это явно не указано для setTimestamp(int parameterIndex, Timestamp x) драйверы должны следовать правилам, установленным setTimestamp(int parameterIndex, Timestamp x, Calendar cal) javadoc:


Присваивает указанному параметру заданное java.sql.Timestamp значение, используя заданный Calendar объект. Драйвер использует Calendar объект для создания SQL TIMESTAMP значения, которое затем драйвер отправляет в базу данных. С помощью объекта Calendar драйвер может вычислить временную метку с учетом пользовательского часового пояса. Если не указан объект Calendar, драйвер использует часовой пояс по умолчанию, который соответствует виртуальной машине, на которой запущено приложение.


При вызове с помощью setTimestamp(int parameterIndex, Timestamp x) драйвер JDBC использует часовой пояс виртуальной машины для вычисления даты и времени временной метки в этом часовом поясе. Эти дата и время хранятся в базе данных, и если в столбце базы данных не хранится информация о часовом поясе, то любая информация о зоне теряется (что означает, что приложения, использующие базу данных, должны последовательно использовать один и тот же часовой пояс или придумать другую схему для определения часового пояса (т. е. Сохранить в отдельном столбце).

Например, ваш местный часовой пояс равен GMT + 2. Вы сохраняете "2012-12-25 10:00:00 UTC". Фактическое значение, хранящееся в базе данных, равно "2012-12-25 12:00:00". Вы извлекаете его снова: вы получаете его снова как "2012-12-25 10:00:00 UTC" (но только если вы извлекаете его с помощью getTimestamp(..)), но когда другое приложение обращается к базе данных в часовом поясе GMT + 0, оно извлекает временную метку как "2012-12-25 12:00:00 UTC".

Если вы хотите сохранить его в другом часовом поясе, то вам нужно использовать setTimestamp(int parameterIndex, Timestamp x, Calendar cal) с экземпляром календаря в требуемом часовом поясе. Просто убедитесь, что вы также используете эквивалентный метод получения с тем же часовым поясом при получении значений (если вы используете TIMESTAMP без информации о часовом поясе в вашей базе данных).

Итак, предполагая, что вы хотите сохранить фактический часовой пояс GMT, вам нужно использовать:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);

С JDBC 4.2 совместимый драйвер должен поддерживать java.time.LocalDateTimejava.time.LocalTime) для TIMESTAMPTIME) через get/set/updateObject. В java.time.Local* классах нет часовых поясов, поэтому преобразование применять не нужно (хотя это могло бы вызвать новый набор проблем, если бы ваш код предполагал определенный часовой пояс).

То есть:


  • заменить getDate(..) на getObject(.., LocalDate.class)

  • заменить setDate(.., dateValue) на setObject(.., localDateValue)

  • заменить getTime(..) на getObject(.., LocalTime.class)

  • заменить setTime(.., timeValue) на setObject(.., localTimeValue)

  • заменить getTimestamp(..) на getObject(.., LocalDateTime.class)

  • заменить setTimestamp(.., timestampValue) на setObject(.., localDateTimeValue)

Ответ 2

Я думаю, правильный ответ должен быть java.sql.Timestamp НЕ зависит от часового пояса. Timestamp состоит из java.util.Date и отдельного значения в наносекундах. В этом классе нет информации о часовом поясе. Таким образом, в качестве даты этот класс просто содержит количество миллисекунд с 1 января 1970 года, 00:00:00 GMT + nanos.

В PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) драйвер использует Calendar для изменения часового пояса по умолчанию. Но временная метка по-прежнему содержит миллисекунды в GMT.

API неясно, как именно драйвер JDBC должен использовать Calendar. Провайдеры, похоже, не стесняются в том, как это интерпретировать, например, в прошлый раз, когда я работал с MySQL 5.5 Calendar, драйвер просто игнорировал Calendar как в PreparedStatement.setTimestamp, так и в ResultSet.getTimestamp .

Ответ 3

Ответ заключается в том, что java.sql.Timestamp это беспорядок, и его следует избегать. Используйте java.time.LocalDateTime вместо этого.

Итак, почему это беспорядок? Из java.sql.Timestamp JavaDoc a java.sql.Timestamp является "тонкой оболочкой вокруг java.util.Date, которая позволяет JDBC API идентифицировать это как значение временной метки SQL". Из java.util.Date JavaDoc: "класс Date предназначен для отражения универсального координированного времени (UTC)". Из спецификации ISO SQL ВРЕМЕННАЯ МЕТКА БЕЗ ЧАСОВОГО ПОЯСА "является типом данных datetime без часового пояса". TIMESTAMP - это краткое название временной МЕТКИ БЕЗ ЧАСОВОГО ПОЯСА. Таким образом, a java.sql.Timestamp "отражает" UTC, в то время как временная МЕТКА SQL "без часового пояса".

Поскольку java.sql.Timestamp отражает UTC, в его методах применяются преобразования. Это приводит к бесконечной путанице. С точки зрения SQL нет смысла преобразовывать значение временной метки SQL в какой-либо другой часовой пояс, поскольку у временной метки нет часового пояса для преобразования. Что значит преобразовать 42 в градус Фаренгейта? Это ничего не значит, потому что в 42 нет единиц измерения температуры. Это просто простое число. Аналогичным образом вы не можете преобразовать временную МЕТКУ 2020-07-22T10:38:00 в Americas / Los Angeles, потому что 2020-07-22T10:30:00 не находится ни в одном часовом поясе. Это не UTC, GMT или что-то еще. Это точное время по дате.

java.time.LocalDateTime также содержит только дату и время. У него нет часового пояса, точно так же, как у SQL TIMESTAMP. Ни один из его методов не применяет какое-либо преобразование часовых поясов, что значительно упрощает прогнозирование и понимание его поведения. Поэтому не используйте java.sql.Timestamp. Используйте java.time.LocalDateTime.

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
Ответ 4

Вы можете использовать приведенный ниже метод для сохранения временной метки в базе данных, специфичной для вашей желаемой зоны / zone Id.

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

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

Пожалуйста, обратите внимание, что Timestamp относится к классу java.sql.Timestamp.

java date jdbc