Специфичен ли часовой пояс 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
Мой сервер БД также работает в том же часовом поясе "Азия / Калькутта"
Это дает мне следующие результаты
Date.getTime()
отсутствует в UTC- Или временная метка влияет на часовой пояс при сохранении в БД, что я здесь делаю не так?
Еще один вопрос:
Будет 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
объект для создания SQLTIMESTAMP
значения, которое затем драйвер отправляет в базу данных. С помощью объекта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.LocalDateTime
(и java.time.LocalTime
) для TIMESTAMP
(и TIME
) через 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
.