2013-04-24 36 views
20

Чтобы сделать запрос JDBC, мне нужно передать ему дату. Дата хранится в базе данных PostgreSql Date field, которая представляет конкретный день без какого-либо времени.Преобразование Joda LocalTime в java.sql.Date

Поскольку мне нужна только дата, я решил использовать конкретный объект, который представляет только дату без времени, то есть LocalDate from Joda-Time package. Я подумал, что это важно, потому что, если бы я использовал объект 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 подготовленное заявление?

+0

И какова семантика на стороне db? Если это * мгновенно *, а не * дата *, вы все равно жареные. –

+0

@MarkoTopolnik Как я получаю от [table] (http://www.postgresql.org/docs/9.1/static/datatype-datetime.html), это просто дата с интервалом времени (так как это занимает 4 байта, а не 8 байты как типы DateTime). – ovgolovin

+0

Должно быть безопасно использовать вашу технику, потому что все проблемы с часовым поясом должны быть приняты во внимание с помощью «LocalDate # toDate». –

ответ

10

Это должно быть безопасно использовать свою технику, потому что будут приняты все вопросы часового пояса на счет LocalDate#toDate. Полученный миллисекундный момент, который у вас есть, не зависит от контекста: он однозначно относится к часовому поясу, действительному в тот момент времени в пределах локали, которую вы используете для преобразования. Другими словами, если вы повторите преобразование одного и того же миллисекундного значения в течение года, вы получите точно такой же ответ: , даже если изменения в часовом поясе изменяются для вашего места, тем временем, так как JDK ссылается на базу данных, документирующую полная история всех изменений часового пояса во всем мире.

При анализе этих проблем важно помнить, что ваш текущий часовой пояс не влияет на преобразование, которое параметризуется вашей локалью и разрешает часовой пояс только в контексте моментального преобразования.

Я искренне сочувствую тошноте, что вы падали на все это: она превращает простую и напряженную операцию в сложный лабиринт расчетов, который ничего не делает, кроме как предлагать проблемы.Надеемся, что все будет позитивным поворотом с Java 8 и новым (да, снова!) API Дата/Время, основанным на JodaTime.

+0

Надеюсь, JDBC также воспользуется этим и начнет принимать объект 'LocatDate' в качестве параметра' setDate'! – ovgolovin

+1

@ovgolovin Действительно, вы * можете * обменивать * java.time * объекты непосредственно с JDBC 4.2 и более поздними версиями. Используйте 'PreparedStatement :: setObject' и' ResultSet :: getObject', а не 'setDate' /' getDate'. См. [Мой ответ] (https://stackoverflow.com/a/32805605/642706) по этому же вопросу для получения дополнительной информации. 'myPreparedStatement.setObject (..., localDate)' и 'myResultSet.getObject (..., LocalDate.class)'. –

0

Попытайтесь использовать LocalDate#toDateMidnight(), который устанавливает время до 0, а затем DateMidnight#toDate().

Date date = localDate.toDateMidnight().toDate(); 

Или, если вы используете JodaTime 1,5 или более поздней версии, используйте LocalDate#toDateTimeAtStartOfDay(), а затем DateTime#toDate()

Надежда, что помогает

+0

Что использует 'toDateMidnight'? – ovgolovin

+0

[JodaTime-DateMidnight] (http://joda-time.sourceforge.net/apidocs/org/joda/time/DateMidnight.html) – Khalil

+1

Это все еще отличает дату к конкретному экземпляру момента. И может случиться так, что после этого преобразования текущий момент пройдет конец периода DST, и часы будут перематываться на один час назад. После этого экземпляр момента, который мы сохранили, будет представлять 23:00 ** предыдущего дня **. Это проблема. И я ожидал, что должен быть способ работать с датами, не прибегая к случаям времени. – ovgolovin

4

У меня такая же проблема сегодня. Я использую JDK 8. Потратив несколько часов на поиск, я нашел ответ на Java SE 8 Documentation. Это решение:

statement.setDate(5, java.sql.Date.valueOf(personToUpdate.getBirthday())); 

Заявление выполнено в форме PreparedStatement. "personToUpdate.getBirthday()" - это тип LocalDate.

+1

Я не верю, что это сработает для Joda LocalTime – Mark

+0

Вы вводите в заблуждение Java 8 LocalDate с Joda LocalDate. Речь идет о Joda LocalDate. – oulenz

1

Поскольку 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()))); 
+0

Является ли 'toDateTimeAtStartOfDay' опечаткой? Вы имели в виду метод [withTimeAtStartOfDay] (http://www.joda.org/joda-time/apidocs/org/joda/time/DateTime.html#withTimeAtStartOfDay()) в Joda-Time ['DateTime'] (http://www.joda.org/joda-time/apidocs/org/joda/time/DateTime.html) класс? –

+0

Нет, это не опечатка. Прежде чем использовать его, я устанавливаю startDate, скажем, «2015-01-01», и в нем он сохранялся в базе данных как «2014-12-31». Это теперь работает, потому что преобразование в LocalDate выполняется так, как должно. – Oshkosh1017

+0

So вы можете связаться с документом? Я не могу найти этот метод. –

1

ТЛ; Др

myPreparedStatement.setObject( // Pass java.time objects directly to database with JDBC 4.2 or later. 
    … , 
    LocalDate.now()    // Get current date today. Better to pass optional `ZoneId` time zone object explicitly than rely implicitly on JVM’s current default. 
) 

Java. время

В Java 8 an d позже новая структура 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")); 

Если вы не указали, то для определения даты используется текущий часовой пояс вашего JVM. Другими словами, следующие две строки эквивалентны.

LocalDate today = LocalDate.now(); 
LocalDate today = LocalDate.now(ZoneId.systemDefault()); 

Я считаю, что первый вариант, now(), чтобы быть плохим выбором дизайна API. Это неявное применение текущего часового пояса JVM не приводит к путанице, ошибкам и страданиям среди наивных разработчиков. Во-первых, текущее значение JVM зависит от машины, настроек хост-системы и системных администраторов. Хуже того, текущий по умолчанию JVM может изменить в любой момент, во время выполнения, любым кодом в любом потоке любого приложения в пределах этой JVM. Поэтому наилучшей практикой является всегда указывать желаемый/ожидаемый часовой пояс.

Теперь, когда у нас есть объект java.time для «today», как его получить в базе данных?

С помощью JDBC 4.2 или более поздней версии обменивайтесь java.time объектами с вашей базе данных.

myPreparedStatement.setObject(… , today) ; 

Чтобы получить:

LocalDate ld = myResultSet.getObject(… , LocalDate.class) ; 

Если вы пока не можете обновить до JDBC 4.2 или более поздней версии: Используйте 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); 

Дата только против Даты времени

Возможно, у вас есть опасения по поводу такой даты только фитинг ваших потребностей бизнеса.

Если вам необходимо знать, например, если контракт был заключен к концу дня по юридическим причинам, то дата-только является неправильным типом данных, если это означает определенный момент, такой как удар в полночь в Монреаль. Новый день наступает раньше в Париже, чем в Монреале, поэтому «сегодня» в Париже «вчера» в Монреале. Если конечный срок вашего контракта определяется юридически в конце дня в Монреале, вы должны применить часовой пояс. Чтобы применить часовой пояс, вы должны иметь дату-время, а не только дату. Вы можете сделать прыжок с LocalDate в ZonedDateTime, но я считаю, что слишком сложный. Ваша база данных должна была использовать тип даты с начала.

В Postgres тип даты - тип TIMESTAMP WITH TIME ZONE. Это имя является неправильным, поскольку часовой пояс фактически не хранится. Подумайте об этом как «отметка времени с уважением для часового пояса». Postgres использует любое смещение-from-UTC или информацию о часовом поясе, сопровождающую входящие данные, для настройки на UTC, и эта информация о смещении/зоне затем отбрасывается. Другой тип, TIMESTAMP WITHOUT TIME ZONE, полностью игнорирует информацию о смещении/зоне, и это неправильное поведение для большинства бизнес-приложений.

Я подозреваю, что многие разработчики или администраторы баз данных могут сделать наивную ошибку в мышлении по интуиции, что только с датой имеет очевидный смысл. Но на самом деле, если у вас есть конкретные или строгие ориентированные на мгновение потребности, такие как законности в отношении таких событий, как «подписанный контракт», «полученная фактура» или «нанятый сотрудник компании», тогда следует использовать значение даты-времени, а не дату -только.

Другими словами, в отношении автора на вопрос в комментарии:

и я ожидал, там должен быть способ работы с датами, не прибегая к экземплярам времени.

Нет, я бы сказал, что просит неприятностей. Если момент имеет значение, используйте дату-время, а не дату-только.


О java.time

java.time каркас встроен в Java 8 и более поздних версий. Эти классы вытесняют неприятные старые legacy классы времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time, в настоящее время в maintenance mode, советует перейти на классы java.time.

Чтобы узнать больше, см. Oracle Tutorial. И поиск Stack Overflow для многих примеров и объяснений. Спецификация: JSR 310.

Где получить классы java.time?

  • Java SE 8, Java SE 9, а затем
    • Встроенный.
    • Часть стандартного Java API с объединенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и Java SE 7
    • Большая часть функциональности java.time будет обратно портирован на Java 6 & 7 в ThreeTen-Backport.
  • Android
    • Более поздние версии Android реализаций пачке классов java.time.
    • Для более ранних Android, проект ThreeTenABP адаптируется ThreeTen-Backport (упомянутый выше). См. How to use ThreeTenABP….

ThreeTen-Extra Проект расширяет java.time с дополнительными классами. Этот проект является доказательством возможных будущих дополнений к java.time. Здесь вы можете найти полезные классы, такие как Interval, YearWeek, YearQuarter и more.

Смежные вопросы