Достаточно часто при работе с датами и хранением их в базе данных возникают различные проблемы. Сегодня рассмотрим одну из них. А именно, случай, когда при сохранении сущности, которая содержит LocalDate, данные в Java' и БД отличаются на один день.
Опишу ситуацию при которой возникла проблема.
Есть веб-приложение Spring + ReactJS. Данные хранятся в mysql, а живёт это всё на Linux. В определенный момент, замечаешь, что выбранная дата в интерфейсе после сохранения на сервере и обновления UI, показывает выбранное значение, но из него вычтен один день. На самом деле это часто возникающая бага.
Посмотрев в networkManager'е браузера какие данные идут на сервер и возвращаются с него, убедился, что проблема не на стороне фронтенда. Данные на сервер отправляются в формате json строкой вида "2017-02-10". А возвращаются после обновления в таком виде "2017-02-09".
Далее я направился к Spring'у. Посмотрел, что endpoint, принимает DTO класс, в котором есть поле типа LocalDate прямиком из 8ой java'ы. Добавив вывод в лог, узнали что в endpoint приходит правильное значение "2017-02-10".
И что тогда? Тогда проблема лежит где-то в сохранении данных в БД. Немного размышлений приводят к тому, что проблемы в разных timezone'ах. Давайте подключимся к БД и проверим, какая таймзона стоит там.
mysql> SELECT @@global.time_zone, @@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| SYSTEM | SYSTEM |
+--------------------+---------------------+
Таймзону БД берёт из системы, давайте посмотрим, что установлено в системе.
cat /etc/timezone
Europe/Moscow
Для большей уверенности попробуем сравнить время в системе и БД.
date
Чт дек 28 19:40:26 MSK 2017
mysql> select now();
+---------------------+
| now() |
+---------------------+
| 2017-12-28 19:40:49 |
+---------------------+
Время совпадает. Следовательно, java по default'у должна брать такое же системное время, какое и в mysql? Но в чём тогда проблема?
Предлагаю сразу перейти к ответу:
serverTimezone
Override detection/mapping of time zone. Used when time zone from server doesn't map to Java time zone
Что это такое? Это документация по JDBC в mysql. Можно почитать это здесь. Что в неё такого интересного? А то, что, при задании jdbc.url в него также передаются различные параметры. ServerTimezone - это как раз параметр, который опредялет с какой таймзоной будет посылаться время из java'ы в mysql. Следовательно, БД получив дату в с таймозоной не свопадающей с ней, приводит её к своей таймзоне. Где это можно посмотреть?
В application.properties ищем следующую строку
spring.datasource.url = jdbc:mysql://${DB_ADDRESS}:3306/${DB_NAME}?serverTimezone=UTC
Как видно, в нашем примере проблема в том, что задана таймзона UTC, а в БД используется Europe/Moscow. Чтобы решить эту проблему, нужно сменить параметр serverTimezone.
serverTimezone=Europe/Moscow
Если вы пользуетесь конфигурацией datasource через сервер приложений, то datasource.url задаётся в админке сервера приложений, или же в его конфигах.
После изменения параметра, всё стало работать корректно. На этой ноте я вас покину.