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 .