Является ли 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

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

Это дает мне следующие появления

  1. Date.getTime() не в UTC
  2. Или 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 объект для построения 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.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()
Другие вопросы по тегам