Date/Time Printing Can Be Elegant Too

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Я обязан своей достаточно высокой репутацией на Stack Overflow (ссылка) в основном этому вопросу, который я задал несколько лет назад: Как распечатать дату ISO 8601 на Java? С тех пор он собрал много голосов “за” и более 20 ответов, включая мой собственный. Почему же в Java, такой развитой экосистеме, не было встроенного простого решения для этой примитивной задачи? Я считаю, что это связано с тем, что разработчики SDK Java были 1) достаточно умными, чтобы не создавать метод print() непосредственно в классе Date, и 2) недостаточно умными, чтобы предоставить нам расширяемый набор классов и интерфейсов для разбора и печати дат элегантным способом.

По моим данным, существуют в основном три способа разделения ответственности между разбором и печатью в JDK:

Первый случай - когда что-то отвечает за печать и разбор, в то время как объект является просто хранилищем данных. Существует класс SimpleDateFormat, который должен быть настроен сначала с правильным часовым поясом и шаблоном форматирования. Затем его следует использовать для печати:

Для его разбора обратно существует метод parse():

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

В Java 8 был добавлен класс Instant с методом toString(), который возвращает время в формате ISO-8601:

Для его разбора обратно существует статический метод parse() в том же классе Instant.

Данный подход выглядит более объектно-ориентированным, но проблема заключается в том, что невозможно изменить шаблон печати любым способом (например, удалить миллисекунды или полностью изменить формат). Более того, метод parse() является статическим, что означает отсутствие полиморфизма - мы не можем изменить логику разбора. Также мы не можем изменить логику печати, поскольку Instant является финальным классом, а не интерфейсом.

Такой дизайн кажется приемлемым, если все, что нам нужно, это строки даты/времени ISO 8601. В момент, когда мы решим расширить его каким-либо образом, мы столкнемся с проблемами.

В Java 8 также есть DateTimeFormatter, который вводит третий способ работы с объектами даты/времени. Чтобы напечатать дату в виде строки, мы создаем экземпляр “форматтера” и передаем его объекту времени.

Для обратного анализа нам необходимо отправить formatter в статический метод parse(), вместе с текстом для анализа.

Как они общаются, LocalDateTime и DateTimeFormatter? Объект времени является TemporalAccessor с методом get(), позволяющим извлекать то, что находится внутри. Другими словами, снова DTO. Форматер все еще является утилитарным классом (даже не интерфейсом), который ожидает прибытие DTO, извлекает то, что находится внутри, и печатает.

Как они разбираются? Метод parse() считывает шаблон, строит и возвращает другой TemporalAccessor DTO.

А что насчет инкапсуляции? “На этот раз нет,” говорят разработчики JDK.

Вот как я бы его спроектировал. Сначала я создал бы общий неизменяемый Template с таким интерфейсом:

Он будет использоваться таким образом:

Этот шаблон внутренне определяет, как распечатать поступающие данные, в зависимости от инкапсулированного шаблона. Вот как Date смог бы распечатать себя:

Так работает разбор (в целом это не очень хорошая идея помещать код в конструктор, но для этого эксперимента это приемлемо):

Допустим, мы хотим вывести время в формате “13-е января 2019 года” (на русском языке). Как мы можем это сделать? Мы не создаем новый Template, а несколько раз декорируем существующий. Сначала создаем экземпляр того, что у нас есть:

Этот текст будет выглядеть примерно так:

Date не отправляет значение MMMM в него, поэтому он не заменяет текст правильно. Нам нужно его украсить:

Теперь, чтобы получить русскую дату из объекта Date, мы делаем следующее:

Допустим, мы хотим вывести дату в другом часовом поясе. Мы создаем еще один декоратор, который перехватывает вызов с помощью "HH" и вычитает (или добавляет) разницу во времени.

Этот код будет выводить Московское время (UTC+3) на русском языке.

Мы можем украшать настолько, насколько нам нужно, делая Template настолько мощным, насколько это необходимо. Элегантность этого подхода заключается в том, что класс Date полностью отделен от Template, что делает их как заменяемыми, так и полиморфными.

Может быть, кого-то заинтересует создание библиотеки печати и разбора даты и времени с открытым исходным кодом для Java с учетом этих принципов?

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-17 at 16:10

sixnines availability badge   GitHub stars