Посібник з SimpleDateFormat

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))); 

У наведеному вище коді, то Formatter перетворює мілісекунд , так як л Онг в зрозумілому людині дату - 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 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 не тільки дозволяють нам форматувати дати, але ми також можемо змінити операцію. За допомогою методу синтаксичного аналізу ми можемо ввести String- представлення дати та повернути еквівалент об'єкта 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());

Тут важливо зауважити, що шаблон, наданий у конструкторі, повинен мати той самий формат, що і дата, проаналізована методом синтаксичного аналізу .

4. Шаблони дати та часу

SimpleDateFormat надає широкий спектр різних варіантів при форматуванні дат. Незважаючи на те, що повний список доступний у JavaDocs, давайте розглянемо деякі найбільш часто використовувані варіанти:

Лист Компонент дати Приклад
М Місяць 12; Грудня
р рік 94
d день 23; Пн
H год 03
м хвилини 57

Вихід повертається компоненти дати також в значній мірі залежить від кількості символів , використовуваних в рядках . Для прикладу візьмемо місяць червень. Якщо ми визначимо рядок дати як:

"MM"

Тоді наш результат з’явиться у вигляді цифрового коду - 06. Однак, якщо ми додамо ще один M до нашого рядка дати:

"MMM"

Тоді наша отримана відформатована дата відображається як слово червень .

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 правильно відформатував дату. Нова дата правильно починається з Вендреді- французької на середу!

It's worth noting a little gotcha in the Locale version of the constructor – whilst many locales are supported, full coverage is not guaranteed. Oracle recommends using the factory methods on DateFormat class to ensure locale coverage.

6. Changing Time Zones

Since SimpleDateFormat extends the DateFormat class, we can also manipulate the time zone using the setTimeZone method. Let's take a look at this in action:

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));

In the above example, we supply the same Date to two different time zones on the same SimpleDateFormat object. We've also added the ‘Z' character to the end of the pattern String to indicate the time zone differences. The output from the format method is then logged for the user.

Hitting run, we can see the current times relative to the two time zones:

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

7. Summary

In this tutorial, we've taken a deep dive into the intricacies of SimpleDateFormat.

We've looked at how to instantiate SimpleDateFormat as well as how the pattern String impacts how the date is formatted.

Ми погралися зі зміною локалей вихідного рядка, перш ніж нарешті експериментувати з використанням часових поясів .

Як завжди, повний вихідний код можна знайти на GitHub.