Современное состояние дел со временем
Со времен, когда придумали JDBC и стандартизировали его интерфейсы, прошло лет 20, и за это время много чего изменилось.
Во-первых, мир стал глобальным и теперь один сервер может обслуживать пользователей со всего мира. Скорость интернета порешала. Поэтому в SQL был добавлен еще один тип данных для работы со временем. Теперь типы выглядят вот так:
- DATE – хранит дату: год, месяц, день.
- TIME – хранит время: часы, минуты, секунды.
- TIMESTAMP – хранит конкретный момент времени: дата, время и миллисекунды.
- TIMESTAMP WITH TIME ZONE – TIMESTAMP и временная зона (имя зоны или смещение).
Во-вторых, в Java появился DateTime API для глобальной работы со временем. В нем появились такие классы:
- Дата и время:
- LocalDate
- LocalTime
- Точное мгновение:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
- Время с часовым поясом:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Третий интересный момент состоит в том, что очень многим SQL-клиентам хотелось бы получать время с сервера уже в своей локальное зоне. Преобразовывать время на лету конечно можно, но это не удобно, да и ошибки бывают.
Например, я хочу получить из базы все задания на сегодня. У SQL-сервера есть функция CURDATE() для этого дела. Только вот сервер находится в США, а я – в Японии. И хотелось бы чтобы он мне вернул все записи за “мое сегодня”, а не “его сегодня”.
В общем, SQL-сервер тоже должен уметь по-умному работать с клиентами в разных временных зонах.
Современные проблемы требуют современных решений
В принципе, новые типы из Java DateTime API и типы из SQL можно удобно сопоставлять. Для того чтобы представить тип DATE в Java нужно использовать класс java.time.LocalDate из JDK 8 DateTime API.
Тип TIME из базы данных можно представить двумя типами из Java: java.time.LocalTime и java.time.OffsetTime. Тоже ничего сложного.
Конкретный момент времени, представленный типом TIMESTAMP в базе, в Java можно представить 4 типами:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Ну, и наконец, TIMESTAMP WITH TIME ZONE можно представить двумя типами:
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Так как ты уже знаком с DateTime API, то запомнить это дело тебе труда не составит :)
Запишу в виде таблицы, так будет проще:
SQL TYPE | Java Type |
---|---|
DATE | java.time.LocalDate |
TIME | java.time.LocalTime java.time.OffsetTime |
TIMESTAMP | java.time.Instant java.time.LocalDateTime java.time.OffsetDateTime java.time.ZonedDateTime |
TIMESTAMP WITH TIME ZONE | java.time.OffsetDateTime java.time.ZonedDateTime |
Получение даты
У меня для тебя хорошая новость. Первая за долгое время. Мы можем обойти ограничение метода getDate(), который возвращает тип java.sql Date.
Дело в том, что у объекта ResultSet есть еще один интересный метод — getObject(). Этот метод принимает два параметра: колонку и тип, и возвращает значение колонки, преобразованное к заданному типу. Общий вид метода такой:
ИмяКласса имя = getObject(column, ИмяКласса);
И если вы хотите преобразовать тип DATE к типу java.time.LocalDate, то нужно написать что-то типа:
LocalDate localDate = results.getObject(4, LocalDate.class);
А любой TIMESTAMP вообще можно преобразовать к куче типов:
java.time.Instant instant = results.getObject(9, java.time.Instant.class);
java.time.LocalDateTime local = results.getObject(9, java.time. LocalDateTime.class);
java.time.OffsetDateTime offset = results.getObject(9, java.time. OffsetDateTime.class);
java.time.ZonedDateTime zoned = results.getObject(9, java.time. ZonedDateTime.class);
Важно! Этот код не будет работать если у вас устаревший MySQL JDBC Driver. Обрати внимание на версию “mysql-connector-java”, прописанную в твоем pom.xml, или добавленную в Libraries в настройках проекта.
Кстати, таким же способом можно обойти и невозможность хранить null у примитивных типов. Если колонка таблицы имеет тип INT, то есть пара способов получить из нее null. Смотри пример ниже:
Integer id1 = results.getObject(8, Integer.class); //так будет работать
Integer id2 = results.getObject(8, int.class); //так тоже будет работать
int id3 = results.getObject(8, Integer.class); //метод вернет null, JVM кинет NPE
int id4 = results.getObject(8, int.class); //метод вернет null, JVM кинет NPE
Настройка часового пояса в MySQL
С MySQL тоже произошло много всего интересного. Как ты знаешь, при создании соединения с MySQL к нему можно добавлять различные параметры:mysql://localhost:3306/db_scheme?имя=значение&имя=значение
Так вот, для работы с временными зонами в MySQL добавили три параметра. Эти параметры ты можешь передавать, когда устанавливаешь соединение с сервером.
Ниже я приведу таблицу с ними:
Параметр | Значения | Значение по умолчанию |
---|---|---|
connectionTimeZone | LOCAL | SERVER | user-zone | SERVER |
forceConnectionTimeZoneToSession | true | false | true |
preserveInstants | true | false | false |
С помощью параметра connectionTimeZone мы выбираем временную зону (часовой пояс), в котором будут выполняться все запросы. С точки зрения клиента, сервер работает в указанной временной зоне.
Параметр forceConnectionTimeZoneToSession заставляет игнорировать переменную time_zone сессии и заменить ее на connectionTimeZone.
И наконец параметр preserveInstants управляет преобразование точного-момента-времени между JVM timeZone и connectionTimeZone.
Самые частые конфигурации такие:
connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=false — соответствует старому MySQL JDBC драйверу версии 5.1 с параметром useLegacyDatetimeCode=true.
connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=true — новый режим, обеспечивающий наиболее естественный способ обработки значений даты и времени.
connectionTimeZone=SERVER & preserveInstants=true — соответствует старому MySQL JDBC драйверу версии 5.1 с параметром useLegacyDatetimeCode=false.
connectionTimeZone=user_defined & preserveInstants=true — помогает преодолеть ситуацию, когда часовой пояс сервера не может быть распознан коннектором, потому что он установлен как общая аббревиатура, такая как CET/CEST.
Да, даты — тема интересная и проблем с ними много. Как говорится: это конечно страшно, но и я не сыкло! :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ