Как сделать это правильно для веб-приложения Java

Что может быть проще, чем указать дату своего рождения в форме и сохранить ее и получить позже по мере необходимости? Конечно, это выглядит легко. А может и нет.

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

Рассмотрим приложение Bonita BPM, развернутое по всему миру, с 3 различными средами, каждая из которых находится в своем часовом поясе:

  • компьютер, планшет или телефон конечного пользователя
  • сервер приложений Java EE, на котором размещено веб-приложение Bonita BPM
  • СУБД, в которой хранятся ваши бизнес-данные

Когда информация о дате рождения поступает из веб-браузера конечного пользователя в базу данных, она проходит через серию преобразований:

  • от буквенно-цифрового ввода пользователя к объекту даты JavaScript (он будет включать информацию о дате и времени)
  • из даты JavaScript в текст JSON в HTTP-запросе
  • из текста JSON в Java java.util.Date (если вы используете версию Bonita ранее, чем 7.5)
  • из java.util.Date в java.sql.Date
  • из java.sql.Date в формат SQL TIMESTAMP
  • из SQL TIMESTAMP в конкретный формат, определенный поставщиком базы данных

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

Возьмем пример. Уолтер, который живет в Сан-Франциско, заполняет форму 23 мая 2017 года в 22:00 по тихоокеанскому времени - и в ней просит его указать дату своего рождения (18 июня 1980 года).

Классический способ хранить такую ​​информацию в JavaScript - использовать объект Date, который включает дату и время. Поэтому, когда Уолтер вводит 18 июня 1980 года в свой компьютер в 22:00 PDT, это фактически сохраняется как 19 июня 1980 года, 05:00 UTC (то есть дата, выбранная Уолтером, плюс текущее время на его компьютере).

Тип JavaScript Date хранит дату в миллисекундах с 1 января 1970 года по всемирному координированному времени (эпоха). Это не проблема, если Уолтер проверяет информацию, которую он только что отправил, потому что его веб-браузер просто выполнит преобразование из UTC обратно в PDT и вернет исходный ввод.

Но это проблема для Элен. Элен - коллега Уолтера, которая находится во Франции, и ей необходимо получить доступ к информации о дате рождения Уолтера. Когда ее система пытается отобразить данные о дате и времени, полученные от Уолтера, она преобразует UTC в ее местный часовой пояс и отображает только день-месяц-год, который больше не является датой рождения Уолтера! В ее часовом поясе (CEST, UTC +2) дата и время становятся 19 июня 1980 г. 07:00 CEST, что отображается как 19 июня 1980 г. (Примечание: мы предполагаем, что объекты Date совместно используются в веб-браузерах с использованием нейтральный бэкэнд.)

Объект JavaScript Date не хранит часовой пояс Уолтера, поэтому мы не можем использовать его для отображения даты в формате день-месяц-год.

В Bonita BPM мы решили решить эту проблему, заставив временную часть объекта JavaScript Date, созданного в виджете даты, принять за полночь в часовом поясе UTC (таким образом, минуя местный часовой пояс пользователя). Итак, днем ​​рождения Уолтера считается 18 июня 1980 года, 00:00 UTC.

Таким же образом, когда отображается дата, она всегда обрабатывается как дата в формате UTC. Это правильно отображает даты в формах - если хранилище на стороне сервера не вводит преобразование часовых поясов.

Итак, перейдем к серверной части.

Дата принимается в виде строки JSON (например, 1980–06–18T00: 00: 00.000Z) в формате UTC. (Это поведение виджета даты, предусмотренное контрактом в Bonita). Преобразование строки JSON в java.util.Date работает нормально, поскольку оба используют UTC.

Следующим шагом является сохранение java.util.Date в базе данных. Поскольку java.util.Date включает дату и время, он преобразуется в тип метки времени SQL.

Это преобразование снова приведет к неожиданному поведению, если виртуальная машина Java не использует UTC. Допустим, сервер приложений Java находится в часовом поясе EDT (UTC -4). В этом случае 00:00 UTC 18 июня 1980 г. будет сохранено в базе данных как 20:00 17 июня 1980 г. Итак, мы снова имеем несоответствие между пользовательским вводом и тем, что хранится. Если мы запросим базу данных с помощью любых стандартных инструментов, мы получим дату, отличную от данных Уолтера.

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

Мы хотели решить эту проблему и создать меньше кошмаров для наших конечных пользователей (и разработчиков приложений!). Итак, давайте взглянем на решение, доступное с Bonita BPM 7.5.

Если вы хотите сохранить дату рождения, вы действительно хотите сохранить только дату без необходимости хранить информацию о времени или часовом поясе. Ничего страшного, если ваш друг поздравит вас с днем ​​рождения на день раньше, потому что он в Окленде, а вы в Анкоридже!

В Bonita BPM 7.5 вы можете выбрать тип только дата для атрибутов бизнес-объекта. При этом будет использоваться новый тип Java 8 java.time.LocalDate, который хранится в базе данных как текстовое представление ISO 8601, поэтому нежелательного смещения из-за несовпадения часовых поясов не будет. Сам виджет выбора даты остается неизменным, поскольку он уже правильно отправляет информацию о дате в виде текста JSON. Синтаксический анализ на стороне сервера был обновлен, чтобы иметь возможность конвертировать из JSON в LocalDate.

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

Для других случаев использования Bonita BPM поддерживает дату и время с часовыми поясами (java.time.OffsetDateTime) и данные и время без часовых поясов (java.time.LocalDateTime), которые хранятся в базе данных в виде текста ISO 8601.

Моя глобальная компания предоставляет платформу разработки, используемую для создания приложений, развертываемых по всему миру. Эта проблема возникла довольно рано. После глубокого изучения спецификации JavaScript, новых возможностей Java 8 и управления датами базы данных я рад, что теперь у нас есть чистое решение.