Преобразование Joda LocalTime в java.sql.Date
Чтобы сделать запрос JDBC, мне нужно передать ему дату. Дата хранится в Date
поле типа базы данных PostgreSql, которая представляет определенный день без какого-либо времени.
Поскольку мне нужна только дата, я решил использовать конкретный объект, который представляет только дату без времени, которая LocalDate
из пакета Joda-Time. Я подумал, что это важно, потому что, если бы я использовал объект DateTime, он имел бы данные избыточного времени, а также может привести к ошибкам в конце летнего времени, когда часы будут отложены назад на один час (хотя ситуация беспрецедентно редка, невозможно).
Но когда я начал пытаться скомпоновать объект LocalDate
с принятыми аргументами метода preparedStatement.setDate
, я не нашел подходящего способа сделать это.
setDate
принимает java.sql.Date
как параметр. И единственным вариантом для построения объекта java.sql.Date
является передача его в миллисекундах.
Но это побеждает все цели использования LocalDate
из пакета Joda-Time, так как при этом преобразовании мы возвращаемся к миллисекундам, и, хотя эти преобразования происходят, часы могут быть возвращены на один час и изменить дату на предыдущий дата.
Итак, теперь у меня есть эта строка в моем коде:
preparedStatement.setDate(1, new java.sql.Date(localDate.toDate().getTime()));
Но это лучший способ конвертировать LocalDate
в формат setDate
?
Являются ли мои проблемы связанными с летним временем и соответствующими тактовыми сдвигами?
Есть ли лучший способ передать дату (и только дату без времени) в JDBC подготовленное утверждение?
Ответы
Ответ 1
Не следует использовать вашу технику, потому что все проблемы с часовым поясом будут учитываться с помощью LocalDate#toDate
. Полученный миллисекундный момент, который у вас есть, не зависит от контекста: он однозначно относится к часовому поясу, действительному в тот момент времени в пределах локали, которую вы используете для преобразования. Другими словами, если вы повторите преобразование одного и того же миллисекундного значения в течение года, вы будете последовательно получать тот же самый ответ, даже если изменения в часовом поясе изменяются для вашего места тем временем, поскольку JDK ссылается на базу данных, документирующую полную историю всех изменений во времени в мире.
При анализе этих проблем важно помнить, что ваш текущий часовой пояс не влияет на преобразование, которое параметризуется вашей локалью и разрешает часовой пояс только в контексте моментального преобразования.
Я всем сердцем сочувствую тошноте, в которой вы падали все это: она превращает простую и напряженную операцию в сложный лабиринт расчетов, который ничего не делает, кроме как предлагать проблемы. Надеемся, что все будет иметь положительный поворот с Java 8 и новым (да, снова!) API Дата/Время, основанным на JodaTime.
Ответ 2
Сегодня у меня такая же проблема. Я использую JDK 8. Потратив несколько часов на поиск, я нашел ответ на Java SE 8 Documentation. Это решение:
statement.setDate(5, java.sql.Date.valueOf(personToUpdate.getBirthday()));
Оператор является экземпляром PreparedStatement. "personToUpdate.getBirthday()" - тип LocalDate.
Ответ 3
Поскольку org.joda.time.toDateMidnight() и org.joda.time.toDateMidnight(зона DateTimeZone) устарели, это решение отлично работает для меня.
Мой типичный класс, который нужно сохранить:
...
import org.joda.time.LocalDate;
...
public class MyObject implements Serializable {
...
private LocalDate startDate;
...
private EndDate startDate;
// Getters and Setters
...
...
}
Im мой другой класс, где я сохраняю startDate, у меня есть:
myObject.setStartDate(new LocalDate(myObject.getStartDate().toDateTimeAtStartOfDay(DateTimeZone.getDefault())));
Ответ 4
java.time
В Java 8 и более поздних версиях новая структура java.time теперь встроена. Этот преемник Joda-Time определяется JSR 310 и расширен ThreeTen-Extra.
Мы надеемся, что в конечном итоге мы увидим, что драйверы JDBC обновлены, чтобы напрямую обрабатывать новые типы java.time. Но до тех пор нам по-прежнему нужны типы java.sql. *. К счастью, новые методы были добавлены для удобного преобразования между типами.
Для даты только, без времени суток и без часового пояса, тип Java LocalDate
(аналогично к Joda-Time).
Что касается вашей озабоченности по поводу часового пояса, связанного с LocalDate
, это имеет значение, когда вы переводите дату только на дату-время, на мгновение на временной шкале. Только для даты - это просто неопределенная идея, без реального смысла, пока вы не переведете ее на временной промежуток времени на шкале времени (от полуночи до полуночи в какой-то часовой пояс). Например, для определения "сегодня" требуется часовой пояс. В java.time мы используем класс ZoneId
.
LocalDate today = LocalDate.now( ZoneId.of( "America/Montreal" ) );
Если этот параметр опущен, ваш текущий часовой пояс JVMs используется для определения даты. Другими словами, следующие две строки эквивалентны.
LocalDate today = LocalDate.now();
LocalDate today = LocalDate.now( ZoneId.systemDefault() );
Я считаю, что первая версия now()
является плохим выбором дизайна API. Это неявное применение текущего часового пояса JVMs по умолчанию не приводит к путанице, ошибкам и страданиям среди наивных разработчиков. Во-первых, текущее значение JVM зависит от машины, настроек хост-системы и системных администраторов. Хуже того, текущее значение по умолчанию JVM может измениться в любой момент, во время выполнения, любым кодом в любом потоке любого приложения в этой JVM. Поэтому лучше всего указать желаемый/ожидаемый часовой пояс.
Теперь, когда у нас есть объект java.time для "today", как его получить в базе данных? Через объект java.sql.Date
. В Java 8 этот старый класс получил новые методы, toLocalDate
и valueOf
. Последний является нашим мостом от типа java.time до типа java.sql.
java.sql.Date sqlToday = java.sql.Date.valueOf( today );
Оттуда выполняется обычная обработка PreparedStatement
.
myPreparedStatement.setDate( 1 , sqlToday );
Date-only vs Date-time
Возможно, у вас есть опасения по поводу такой даты - только для удовлетворения потребностей вашего бизнеса.
Если вам нужно знать, например, если контракт был подписан к концу дня по юридическим причинам, то дата-только является неправильным типом данных, если это означает определенный момент, такой как удар в полночь в Монреале, Новый день наступает раньше в Париже, чем в Монреале, поэтому "сегодня" в Париже "вчера" в Монреале. Если конечный срок вашего контракта определяется юридически в конце дня в Монреале, вы должны применить часовой пояс. Чтобы применить часовой пояс, вы должны иметь дату-время, а не только дату. Вы можете сделать переход из LocalDate
в ZonedDateTime
, но я считаю это слишком сложным. Ваша база данных должна была использовать тип даты с начала.
В Postgres тип даты-времени означает тип TIMESTAMP WITH TIME ZONE
. Это имя является неправильным, поскольку часовой пояс фактически не хранится. Подумайте об этом как о "временной отметке с уважением к часовому поясу". Postgres использует любое смещение-from- UTC или информацию о часовом поясе, сопровождающую входящие данные, для настройки на UTC, и эта информация о смещении/зоне затем отбрасывается. Другой тип TIMESTAMP WITHOUT TIME ZONE
полностью игнорирует информацию о смещении/зоне, и это неправильное поведение для большинства бизнес-приложений.
Я подозреваю, что многие разработчики или администраторы баз данных могут сделать наивную ошибку в мышлении по интуиции, что только с датой имеет очевидный смысл. Но на самом деле, если у вас есть конкретные или строгие ориентированные на мгновение потребности, такие как законности в отношении таких событий, как "подписанный контракт", "полученная фактура" или "нанятый сотрудник компании", тогда следует использовать значение даты-времени, а не дату -только.
Другими словами, в отношении комментария авторов вопросов:
И я ожидал, что должен быть способ работать с датами, не прибегая к экземплярам времени.
Нет, я бы сказал, что просит неприятностей. Если момент имеет значение, используйте дату-время, а не только дату.
Ответ 5
Попробуйте использовать LocalDate#toDateMidnight()
, который устанавливает время на 0, а затем DateMidnight#toDate()
.
Date date = localDate.toDateMidnight().toDate();
Или, если вы используете JodaTime 1.5 или новее, используйте LocalDate#toDateTimeAtStartOfDay()
, а затем DateTime#toDate()
Надеюсь, что поможет