Преобразование даты ввода в формате YYYY-MM-DD'T'HH:MM:SS:SSS+HHMM в java.sql.Timestamp с часовым поясом из ввода

У нас есть требование, согласно которому строка передается из пользовательского интерфейса в формате ГГГГ-ММ-ДД'Т'ЧЧ:ММ:СС:ССС+ЧЧММ вместе с часовым поясом (например, 2020-01-20'T '05:40:334-0500), который необходимо преобразовать в отметку времени sql для вставки в БД.

Все попытки игнорируют временную метку, отправленную с входа, и устанавливают локальную временную метку при настройке в БД. Пожалуйста, совет, если есть решение?


person Ajay Dsouza    schedule 30.04.2020    source источник
comment
Создать объект даты и использовать подготовленный оператор?   -  person Thorbjørn Ravn Andersen    schedule 30.04.2020
comment
Не уверен, как вы сохраняете метку времени в БД, поскольку вы не предоставили ни одного примера кода. Но я считаю, что следующий пост будет полезен для вас, чтобы понять, в чем проблема и отличное решение: stackoverflow.com/questions/14070572/   -  person trims    schedule 30.04.2020
comment
@ ThorbjørnRavnAndersen java.sql.Date не хранит в нем информацию о часовом поясе. Итак, я использую java.sql.Timestamp, однако мне не удается преобразовать часовой пояс из входной строки в java.sql.Timezone (при преобразовании часовой пояс по умолчанию устанавливается в localtimezone)   -  person Ajay Dsouza    schedule 30.04.2020
comment
@trims Я устанавливаю для этого объекта метки времени тип таблицы, который будет передан в качестве входных данных для вызова хранимой процедуры с интерфейсом SpringStoredProcedure. Настоящая проблема заключается в том, что преобразование в java.sql.Timestamp стирает информацию о часовом поясе, поэтому после преобразования с использованием DateFormatter я также не могу видеть часовой пояс при печати в журналах консоли. Я прошел по указанной вами ссылке, спасибо за то же самое. Но, к сожалению, на сценарий, о котором я упоминал, там нет ответа. Еще раз спасибо за ответы.   -  person Ajay Dsouza    schedule 30.04.2020
comment
С какой стати вам нужен часовой пояс, сохраняемый для каждой даты?   -  person Thorbjørn Ravn Andersen    schedule 30.04.2020
comment
Я рекомендую вам не использовать java.sql.Timestamp. Этот класс плохо разработан и давно устарел. Вместо этого используйте либо OffsetDateTime, либо LocalDateTime, оба из java.time, современного API даты и времени Java< /а>.   -  person Ole V.V.    schedule 30.04.2020
comment
Тип данных в SQL: timestamp без часового пояса или timestamp with time zone? (Последнее рекомендуется.)   -  person Ole V.V.    schedule 30.04.2020
comment
Я верю и надеюсь, что 2020-01-20'T'05:40:334-0500 не совсем верно? (1) Хотя у вас есть одинарные кавычки вокруг T в шаблоне формата, вы не должны этого делать в самой строке. (2) Это 334 секунды после 05:40 утра??! Наверное опечатка?   -  person Ole V.V.    schedule 30.04.2020


Ответы (2)


java.time и JDBC 4.2 или новее

Я рекомендую вам использовать java.time, современный API даты и времени Java, для вашей работы с датой и временем. Класс java.sql.Timestamp плохо разработан, настоящий хак поверх уже плохо спроектированного класса java.util.Date. К счастью, оба тоже давно устарели. Возможно, вы подумали, что вам нужен Timestamp для передачи значения даты и времени в вашу базу данных SQL. Начиная с JDBC 4.2 это уже не так.

Во-первых, чтобы проанализировать вашу строку:

    DateTimeFormatter uiFormatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
            .appendPattern("XX")
            .toFormatter();

    String stringFromUi = "2020-01-20T05:40:34-0500";

    OffsetDateTime dateTime = OffsetDateTime.parse(stringFromUi, uiFormatter);

    System.out.println(dateTime);

Выведите это далеко:

2020-01-20T05:40:34-05:00

Я исправил вашу строку, однако. Он настолько близок к формату ISO 8601, что я был убежден, что он должен соответствовать этому стандарту. Если вы намеревались указать миллисекунды, не проблема, код будет работать и с дробью секунд, например 2020-01-20T05:40:30.334-0500. Если бы вы могли убедить своих UI-специалистов отправлять строку с двоеточием в смещении, например, 2020-01-20T05:40:34-05:00, вам не понадобился бы форматтер, но вы могли бы использовать OffsetDateTime.parse(String) с одним аргументом.

Затем я предположил, что вы запросили Timestamp для взаимодействия с вашей базой данных. И поскольку вы запросили Timestamp с часовым поясом из ввода, я также предположил, что тип данных на стороне SQL — timestamp with time zone. Что рекомендуется для временных меток. В этом случае, начиная с JDBC 4.2, вы напрямую передаете наш OffsetDateTime драйверу JDBC. Для упрощенного примера:

    PreparedStatement ps = yourDatabaseConnection.prepareStatement(
            "insert into your_table(your_timestamp_with_time_zone_column) values (?);");
    ps.setObject(1, dateTime);
    int rowsInserted = ps.executeUpdate();

Если вам нужен Timestamp для какого-то устаревшего API, который вы не можете позволить себе изменить прямо сейчас, сначала знайте, что java.sql.Timestamp не может иметь часовой пояс или смещение от UTC. Хороший способ получить Timestamp — преобразовать Instant, другой современный класс:

    Instant instant = dateTime.toInstant();
    Timestamp ts = Timestamp.from(instant);
    System.out.println(ts);

Вывод в моем часовом поясе, Европа/Копенгаген:

2020-01-20 11:40:34.0

Это правильно, даже если может показаться, что это 6 часов неправильно. Европа/Копенгаген в январе был со смещением UTC +01:00. Когда мы печатаем Timestamp, мы неявно вызываем его метод toString. Timestamp.toString() использует настройку часового пояса JVM для рендеринга возвращаемой строки. Таким образом, ваша строка даты и времени теперь была преобразована сначала из смещения UTC -05:00 в какой-то внутренний формат, о котором нам не нужно заботиться, а затем в смещение +01:00, разница в 6 часов с начальной точки.

Ссылки

person Ole V.V.    schedule 30.04.2020
comment
Спасибо @Ole-v-v за хорошее объяснение. Мы попробуем этот вариант и обновим, как только он сработает. Спасибо еще раз. - person Ajay Dsouza; 15.05.2020

@Ajay Dsouza Пробовали ли вы использовать Java 8 ZonedDateTime вместо стандартного Date для вашего варианта использования и явно установите часовой пояс в операторе. Например:

ZonedDateTime receivedTimestamp = somevalue;
Timestamp ts = new Timestamp(receivedTimestamp.toInstant().toEpochMilli());
ps.setTimestamp(
   1, 
   ts, 
   Calendar.getInstance(TimeZone.getTimeZone(receivedTimestamp.getZone()))
); 

На самом деле проблема, с которой вы сталкиваетесь при преобразовании часового пояса из входной строки в java.sql.Timezone, на самом деле довольно хорошо известна. hibernate) явно изменить это, или вы можете использовать метод перегруженного подготовленного оператора, как я предоставил выше, чтобы явно установить часовой пояс в операторе.

В документе PreparedStatement setTimestamp Javadoc указано, что:

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

В Spring-boot вы можете установить следующее свойство для глобальной установки часового пояса (вероятно, это не то, что вам нужно, но):

spring.jpa.properties.hibernate.jdbc.time_zone=UTC
person Ananthapadmanabhan    schedule 30.04.2020
comment
Спасибо @Ananthapadmanabhan, я (и мой товарищ по команде) попробую ZonedDateTime и посмотрю, хорошо ли это. Спасибо за ответ. - person Ajay Dsouza; 15.05.2020