Является ли java.sql.Timestamp часовым поясом конкретным?
Я должен хранить UTC dateTime в БД.
Я конвертировал дату и время в определенном часовом поясе в UTC. для этого я следовал приведенному ниже коду.
Моя дата вводаВремя: "20121225 10:00:00 Z", часовой пояс "Азия / Калькутта"
Мой сервер / БД (оракул) работает в том же часовом поясе (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);
}
ВЫХОД
БД (оракул) сохранила то же самое 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 имеет влияние часового пояса при сохранении в БД Что я здесь делаю не так?
Еще один вопрос:
Будет timeStamp.toString()
печать в местном часовом поясе, как java.util.date
делает? Не UTC?
7 ответов
Хотя это явно не указано для 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*
классы не имеют часовых поясов, поэтому преобразование не требуется (хотя это может открыть новый набор проблем, если ваш код действительно предполагал определенный часовой пояс).
Я думаю, что правильный ответ должен быть java.sql. Timestamp НЕ зависит от часового пояса. Временная метка является составной частью java.util.Date и отдельного значения наносекунд. В этом классе нет информации о часовых поясах. Таким образом, как и Date, этот класс просто содержит количество миллисекунд с 1 января 1970 года, 00:00:00 по Гринвичу + nanos.
В PreparedStatement.setTimestamp(int parameterIndex, Timestamp x, Calendar cal) Календарь используется драйвером для изменения часового пояса по умолчанию. Но временная метка все еще держит миллисекунды в GMT.
API неясно, как именно драйвер JDBC должен использовать календарь. Поставщики, кажется, не стесняются интерпретировать это, например, когда я в последний раз работал с MySQL 5.5 Calendar, драйвер просто игнорировал Calendar в PreparedStatement.setTimestamp и ResultSet.getTimestamp.
Ответ в том, что java.sql.Timestamp
это беспорядок, и его следует избегать. Использоватьjava.time.LocalDateTime
вместо.
Так почему это беспорядок? Изjava.sql.Timestamp
JavaDoc, a java.sql.Timestamp
это "тонкая обертка вокруг java.util.Date
который позволяет JDBC API идентифицировать это как значение SQL TIMESTAMP ". Из java.util.Date
JavaDoc " Date
класс предназначен для отражения скоординированного всемирного времени (UTC)". В соответствии со спецификацией ISO SQL TIMESTAMP WITHOUT TIME ZONE " - это тип данных datetime без часового пояса ". TIMESTAMP - это краткое имя для TIMESTAMP WITHOUT TIME ZONE. Таким образом, java.sql.Timestamp
"отражает" UTC, а SQL TIMESTAMP - "без часового пояса".
Потому как java.sql.Timestamp
отражает UTC, его методы применяют преобразования. Это вызывает бесконечную путаницу. С точки зрения SQL нет смысла преобразовывать значение SQL TIMESTAMP в какой-либо другой часовой пояс, поскольку TIMESTAMP не имеет часового пояса для преобразования. Что значит преобразовать 42 в градусы Фаренгейта? Это ничего не значит, потому что 42 не имеет единиц измерения температуры. Это просто голое число. Точно так же вы не можете преобразовать TIMESTAMP 2020-07-22T10:38:00 в Америку / Лос-Анджелес, потому что 2020-07-22T10:30:00 находится вне какого-либо часового пояса. Это не в UTC, не по Гринвичу или что-то еще. Это голое свидание.
java.time.LocalDateTime
также является голым временем даты. У него нет часового пояса, как у SQL TIMESTAMP. Ни в одном из его методов не применяется какое-либо преобразование часового пояса, что значительно упрощает прогнозирование и понимание его поведения. Так что не используйтеjava.sql.Timestamp
. Использоватьjava.time.LocalDateTime
.
LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);
Вы можете использовать приведенный ниже метод для сохранения метки времени в базе данных, соответствующей желаемой зоне / идентификатору зоны.
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());
Распространенная ошибка людей - использование LocaleDateTime
чтобы получить метку времени того момента, при котором вся информация, указанная для вашей зоны, отбрасывается, даже если вы попытаетесь преобразовать ее позже. Зона не понимает.
пожалуйста, обратите внимание Timestamp
из класса java.sql.Timestamp
.
Для Mysql у нас есть ограничение. В драйвере Mysql doc у нас есть:
Ниже приведены некоторые известные проблемы и ограничения для MySQL Connector/J: Когда Connector / J получает временные метки для дня перехода на летнее время (DST), используя метод getTimeStamp() для набора результатов, некоторые из возвращаемых значений могут быть неправильными. Ошибок можно избежать, используя следующие параметры подключения при подключении к базе данных:
useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC
Итак, когда мы не используем эти параметры и мы вызываем setTimestamp or getTimestamp
с календарем или без календаря у нас есть метка времени в часовом поясе jvm.
Пример:
Часовой пояс jvm: GMT+2. В базе данных у нас есть временная метка: 1461100256 = 19/04/16 21: 10: 56,000000000 по Гринвичу
Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone
Первый метод возвращает: 1461100256000 = 19/04/2016 - 21:10:56 GMT
Второй метод возвращает: 1461100256000 = 19/04/2016 - 21:10:56 GMT
Третий метод возвращает: 1461085856000 = 19/04/2016 - 17:10:56 GMT
Вместо Oracle, когда мы используем одни и те же вызовы, мы имеем:
Первый метод возвращает: 1461093056000 = 19/04/2016 - 19:10:56 GMT
Второй метод возвращает: 1461100256000 = 19/04/2016 - 21:10:56 GMT
Третий метод возвращает: 1461085856000 = 19/04/2016 - 17:10:56 GMT
NB. Нет необходимости указывать параметры для Oracle.
Это зависит от вашего водителя. Вам нужно указать параметр в вашей Java-программе, чтобы указать часовой пояс, который вы хотите использовать.
java -Duser.timezone="America/New_York" GetCurrentDateTimeZone
Далее это:
to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')
Может также иметь значение для правильной обработки конверсии. Взято отсюда
Если ваша проблема состоит в том, чтобы получить метку времени локальной зоны, вы можете использовать это:
Timestamp.from(Instant.now()).toLocalDateTime()