Перейти к основному содержимому

Руководство по SimpleDateFormat

· 5 мин. чтения

1. Введение

В этом руководстве мы подробно рассмотрим класс SimpleDateFormat .

Мы рассмотрим простые стили создания экземпляров и форматирования, а также полезные методы, предоставляемые классом для обработки локалей и часовых поясов .

2. Простое создание

Во-первых, давайте посмотрим, как создать экземпляр нового объекта SimpleDateFormat .

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

Давайте начнем с шаблона даты, разделенного тире, например:

"dd-MM-yyyy"

Это правильно отформатирует дату, начиная с текущего дня месяца, текущего месяца года и, наконец, текущего года. Мы можем протестировать наш новый модуль форматирования с помощью простого модульного теста. Мы создадим новый объект SimpleDateFormat и передадим известную дату:

SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
assertEquals("24-05-1977", formatter.format(new Date(233345223232L)));

В приведенном выше коде средство форматирования преобразует миллисекунды в удобочитаемую дату — 24 мая 1977 года.

2.1. Заводские методы

Хотя SimpleDateFormat — это удобный класс для быстрого создания средства форматирования даты, мы рекомендуем использовать фабричные методы класса DateFormat getDateFormat() , getDateTimeFormat() , getTimeFormat() .

Приведенный выше пример выглядит немного иначе при использовании этих фабричных методов:

DateFormat formatter = DateFormat.getDateInstance(DateFormat.SHORT);
assertEquals("5/24/77", formatter.format(new Date(233345223232L)));

Как видно из вышеизложенного, количество параметров форматирования предопределено полями в классе DateFormat . Это в значительной степени ограничивает наши доступные варианты форматирования , поэтому в этой статье мы будем придерживаться SimpleDateFormat .

2.2. Потокобезопасность

В JavaDoc для SimpleDateFormat явно указано:

Форматы даты не синхронизируются. Рекомендуется создавать отдельные экземпляры формата для каждого потока. Если несколько потоков одновременно обращаются к формату, он должен быть синхронизирован извне.

Таким образом , экземпляры SimpleDateFormat не являются потокобезопасными , и мы должны осторожно использовать их в параллельных средах.

Лучший способ решить эту проблему `— использовать их в сочетании с ThreadLocal . **Таким образом, каждый поток получает собственный экземпляр SimpleDateFormat`** , а отсутствие совместного использования делает программу потокобезопасной: ** **

private final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal
.withInitial(() -> new SimpleDateFormat("dd-MM-yyyy"));

Аргументом метода withInitial является поставщик экземпляров SimpleDateFormat . Каждый раз, когда ThreadLocal необходимо создать экземпляр, он будет использовать этого поставщика.

Затем мы можем использовать средство форматирования через экземпляр ThreadLocal :

formatter.get().format(date)

Метод ThreadLocal.get() сначала инициализирует SimpleDateFormat для текущего потока, а затем повторно использует этот экземпляр.

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

Есть еще два подхода к решению той же проблемы:

  • Использование синхронизированных блоков или ReentrantLock s
  • Создание одноразовых экземпляров SimpleDateFormat по запросу

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

Стоит отметить, что начиная с Java 8 был представлен новый класс DateTimeFormatter . Новый класс DateTimeFormatter является неизменяемым и потокобезопасным. Если мы работаем с Java 8 или более поздней версии , рекомендуется использовать новый класс DateTimeFormatter .

3. Даты разбора

SimpleDateFormat и DateFormat позволяют не только форматировать даты, но и выполнять операцию в обратном порядке. Используя метод parse , мы можем ввести строковое представление даты и вернуть `` эквивалент объекта Date :

SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
Date myDate = new Date(233276400000L);
Date parsedDate = formatter.parse("24-05-1977");
assertEquals(myDate.getTime(), parsedDate.getTime());

Здесь важно отметить, что шаблон, предоставленный в конструкторе, должен быть в том же формате, что и дата, проанализированная с помощью метода parse .

4. Шаблоны даты и времени

SimpleDateFormat предоставляет широкий спектр различных опций при форматировании дат. Хотя полный список доступен в JavaDocs , давайте рассмотрим некоторые наиболее часто используемые параметры:

   | Письмо    | Компонент даты    | Пример   | 
| М | Месяц | 12; декабрь |
| у | год | 94 |
| г | день | 23; пн |
| ЧАС | час | 03 |
| м | минута | 57 |

Вывод , возвращаемый компонентом даты, также сильно зависит от количества символов, используемых в String . Например, возьмем июнь месяц. Если мы определим строку даты как:

"MM"

Тогда наш результат появится в виде числового кода — 06. Однако, если мы добавим еще одну букву М к нашей строке даты:

"MMM"

Затем наша результирующая отформатированная дата появляется как слово Jun .

5. Применение локалей

Класс SimpleDateFormat также поддерживает широкий диапазон локалей , которые задаются при вызове конструктора.

Давайте применим это на практике, отформатировав дату на французском языке. Мы создадим экземпляр объекта SimpleDateFormat , передав конструктору Locale.FRANCE .

SimpleDateFormat franceDateFormatter = new SimpleDateFormat("EEEEE dd-MMMMMMM-yyyy", Locale.FRANCE);
Date myWednesday = new Date(1539341312904L);
assertTrue(franceDateFormatter.format(myWednesday).startsWith("vendredi"));

Указав заданную дату, полдень среды, мы можем утверждать, что наш franceDateFormatter правильно отформатировал дату. Новая дата правильно начинается с Vendredi - по-французски среда!

Стоит отметить небольшую загвоздку в версии конструктора Locale — несмотря на то, что поддерживаются многие локали, полное покрытие не гарантируется . Oracle рекомендует использовать фабричные методы в классе DateFormat , чтобы обеспечить охват локали.

6. Изменение часовых поясов

Поскольку SimpleDateFormat расширяет класс DateFormat , мы также можем манипулировать часовым поясом с помощью метода setTimeZone . Давайте посмотрим на это в действии:

Date now = new Date();

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEEE dd-MMM-yy HH:mm:ssZ");

simpleDateFormat.setTimeZone(TimeZone.getTimeZone("Europe/London"));
logger.info(simpleDateFormat.format(now));

simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
logger.info(simpleDateFormat.format(now));

В приведенном выше примере мы указываем одну и ту же дату для двух разных часовых поясов в одном и том же объекте SimpleDateFormat . Мы также добавили символ «Z» в конец строки шаблона, чтобы указать разницу в часовых поясах `` . Выходные данные метода форматирования затем регистрируются для пользователя.

Нажав run, мы можем увидеть текущее время относительно двух часовых поясов:

INFO: Friday 12-Oct-18 12:46:14+0100
INFO: Friday 12-Oct-18 07:46:14-0400

7. Резюме

В этом уроке мы глубоко погрузились в тонкости SimpleDateFormat .

Мы рассмотрели, как создать экземпляр SimpleDateFormat , а также как шаблон String влияет на форматирование даты .

Мы поигрались с изменением локалей выходной строки, прежде чем, наконец, поэкспериментировать с часовыми поясами .

Как всегда, полный исходный код можно найти на GitHub .