JavaRush /Курсы /Модуль 4. Работа с БД /Работа со временем

Работа со временем

Модуль 4. Работа с БД
7 уровень , 4 лекция
Открыта

Современное состояние дел со временем

Со времен, когда придумали 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.

Да, даты — тема интересная и проблем с ними много. Как говорится: это конечно страшно, но и я не сыкло! :)

1
Задача
Модуль 4. Работа с БД, 7 уровень, 4 лекция
Недоступна
Получение даты
В методе main создай подключение к БД с помощью метода getConnection(String, String, String) класса DriverManager. Используй URL "jdbc:mysql://localhost:3306/test", пользователя "root" и такой же пароль. Получи Statement используя метод createStatement без параметров.
Комментарии (8)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Мая Уровень 82
23 сентября 2025
что-то непонятен последний обзац(((
Денис Стёпшин Уровень 25
15 июня 2023
Как говорится: это конечно страшно, но и я не сыкло! :) эммм. спасибо за материал, но что это за лексикон такой в обучающем материале на обучающем ресурсе ?
Максим Мамчур Уровень 110
8 июля 2023
та норм лексика)
25 октября 2023
ахах) забавно
Александр Уровень 83
28 октября 2023
ну поплачь
Andrew Cooper Уровень 1 Expert
2 ноября 2023
я смотрю ты уже разнылся тут...
Роман Уровень 88
5 января 2025
я сам не фанат таких выражений, но тут они помогают разбавить материал
Александр Уровень 111 Expert
26 декабря 2022
После этой задачи вьехал, как правильно передаются аргументы нумерации колонок. Помог данный пример: LocalDateTime_JDBC