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

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

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

1. Обзор

В этом руководстве мы рассмотрим класс Java 8 DateTimeFormatter и его шаблоны форматирования . Мы также обсудим возможные варианты использования этого класса.

Мы можем использовать DateTimeFormatter для унифицированного форматирования даты и времени в приложении с предопределенными или определяемыми пользователем шаблонами.

2. DateTimeFormatter с предопределенными экземплярами

DateTimeFormatter поставляется с несколькими предопределенными форматами даты/времени, которые соответствуют стандартам ISO и RFC. Например, мы можем использовать экземпляр ISO_LOCAL_DATE для анализа даты, такой как «2018-03-09»:

DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.of(2018, 3, 9));

Чтобы разобрать дату со смещением, мы можем использовать ISO_OFFSET_DATE , чтобы получить результат вроде «2018-03-09-03:00»:

DateTimeFormatter.ISO_OFFSET_DATE.format(LocalDate.of(2018, 3, 9).atStartOfDay(ZoneId.of("UTC-3")));

Большинство предопределенных экземпляров класса DateTimeFormatter ориентированы на стандарт ISO-8601. ISO-8601 — это международный стандарт форматирования даты и времени.

Однако существует один другой предопределенный экземпляр, который анализирует RFC-1123 «Требования к интернет-хостам», опубликованный IETF:

DateTimeFormatter.RFC_1123_DATE_TIME.format(LocalDate.of(2018, 3, 9).atStartOfDay(ZoneId.of("UTC-3")));

Этот фрагмент генерирует ' Пт, 9 марта 2018 г. 00:00:00 -0300. '

Иногда нам приходится манипулировать датой, которую мы получаем в виде строки известного формата. Для этого мы можем использовать метод parse() :

LocalDate.from(DateTimeFormatter.ISO_LOCAL_DATE.parse("2018-03-09")).plusDays(3);

Результатом этого фрагмента кода является представление LocalDate на 12 марта 2018 года.

3. DateTimeFormatter с FormatStyle

Иногда мы можем захотеть напечатать даты в удобочитаемом виде.

В таких случаях мы можем использовать значения перечисления java.time.format.FormatStyle (FULL, LONG, MEDIUM, SHORT) с нашим DateTimeFormatter :

LocalDate anotherSummerDay = LocalDate.of(2016, 8, 23);
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(anotherSummerDay));

Вывод этих разных стилей форматирования одной и той же даты:

Tuesday, August 23, 2016
August 23, 2016
Aug 23, 2016
8/23/16

Мы также можем использовать предопределенные стили форматирования для даты и времени. Чтобы использовать FormatStyle со временем, мы должны использовать экземпляр ZonedDateTime , иначе будет выдано исключение DateTimeException :

LocalDate anotherSummerDay = LocalDate.of(2016, 8, 23);
LocalTime anotherTime = LocalTime.of(13, 12, 45);
ZonedDateTime zonedDateTime = ZonedDateTime.of(anotherSummerDay, anotherTime, ZoneId.of("Europe/Helsinki"));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)
.format(zonedDateTime));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
.format(zonedDateTime));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
.format(zonedDateTime));
System.out.println(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
.format(zonedDateTime));

Обратите внимание, что на этот раз мы использовали метод ofLocalizedDateTime() класса DateTimeFormatter .

На выходе мы получаем:

Tuesday, August 23, 2016 1:12:45 PM EEST
August 23, 2016 1:12:45 PM EEST
Aug 23, 2016 1:12:45 PM
8/23/16 1:12 PM

Мы также можем использовать FormatStyle для анализа строки даты и времени , преобразуя ее , например, в ZonedDateTime .

Затем мы можем использовать проанализированное значение для управления переменной даты и времени:

ZonedDateTime dateTime = ZonedDateTime.from(
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)
.parse("Tuesday, August 23, 2016 1:12:45 PM EET"));
System.out.println(dateTime.plusHours(9));

Вывод этого фрагмента: «2016-08-23T22:12:45+03:00[Европа/Бухарест]». Обратите внимание, что время изменилось на «22:12:45».

4. DateTimeFormatter с пользовательскими форматами

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

4.1. DateTimeFormatter для даты

Предположим, мы хотим представить объект java.time.LocalDate , используя обычный европейский формат, например 31.12.2018. Для этого мы могли бы вызвать фабричный метод DateTimeFormatter . ofPattern("дд.мм.гггг").

Это создаст соответствующий экземпляр DateTimeFormatter , который мы можем использовать для форматирования нашей даты:

String europeanDatePattern = "dd.MM.yyyy";
DateTimeFormatter europeanDateFormatter = DateTimeFormatter.ofPattern(europeanDatePattern);
System.out.println(europeanDateFormatter.format(LocalDate.of(2016, 7, 31)));

Вывод этого фрагмента кода будет «31.07.2016».

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

Symbol  Meaning                     Presentation      Examples
------ ------- ------------ -------
u year year 2004; 04
y year-of-era year 2004; 04
M/L month-of-year number/text 7; 07; Jul; July; J
d day-of-month number 10

Это выдержка из официальной документации Java для класса DateTimeFormatter .

Количество букв в шаблоне имеет большое значение .

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

Если нам случится использовать четырехбуквенный шаблон для месяца «ММММ», то мы получим представление «полной формы». В нашем примере это будет «июль». Пятибуквенный шаблон «МММММ» заставит средство форматирования использовать «узкую форму». В нашем случае будет использоваться «J».

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

DateTimeFormatter europeanDateFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
System.out.println(LocalDate.from(europeanDateFormatter.parse("15.08.2014")).isLeapYear());

Этот фрагмент кода проверяет, является ли дата « 15.08.2014 » високосным годом, а это не так.

4.2. DateTimeFormatter для времени

Существуют также буквы шаблонов, которые можно использовать для временных шаблонов:

Symbol  Meaning                     Presentation      Examples
------ ------- ------------ -------
H hour-of-day (0-23) number 0
m minute-of-hour number 30
s second-of-minute number 55
S fraction-of-second fraction 978
n nano-of-second number 987654321

Использовать DateTimeFormatter для форматирования экземпляра java.time.LocalTime довольно просто . Предположим, мы хотим показать время (часы, минуты и секунды), разделенные двоеточием:

String timeColonPattern = "HH:mm:ss";
DateTimeFormatter timeColonFormatter = DateTimeFormatter.ofPattern(timeColonPattern);
LocalTime colonTime = LocalTime.of(17, 35, 50);
System.out.println(timeColonFormatter.format(colonTime));

Это сгенерирует вывод « 17:35:50.

Если мы хотим добавить миллисекунды к выводу, мы должны добавить «SSS» к шаблону:

String timeColonPattern = "HH:mm:ss SSS";
DateTimeFormatter timeColonFormatter = DateTimeFormatter.ofPattern(timeColonPattern);
LocalTime colonTime = LocalTime.of(17, 35, 50).plus(329, ChronoUnit.MILLIS);
System.out.println(timeColonFormatter.format(colonTime));

Это дает нам вывод « 17:35:50 329 » .

Обратите внимание, что «ЧЧ» — это модель часов дня, которая генерирует выходные данные от 0 до 23. Когда мы хотим показать AM/PM, мы должны использовать строчную букву «чч» для обозначения часов и добавить шаблон «а»:

String timeColonPattern = "hh:mm:ss a";
DateTimeFormatter timeColonFormatter = DateTimeFormatter.ofPattern(timeColonPattern);
LocalTime colonTime = LocalTime.of(17, 35, 50);
System.out.println(timeColonFormatter.format(colonTime));

Сгенерированный вывод: « 17:35:50.

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

DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("hh:mm:ss a");
System.out.println(LocalTime.from(timeFormatter.parse("12:25:30 AM")).isBefore(LocalTime.NOON));

Вывод этого последнего фрагмента показывает, что данное время на самом деле до полудня.

4.3. DateTimeFormatter для часовых поясов

Часто мы хотим увидеть часовой пояс какой-то конкретной переменной даты и времени. Если мы используем дату и время в Нью-Йорке (UTC -4), мы можем использовать букву шаблона «z» для имени часового пояса:

String newYorkDateTimePattern = "dd.MM.yyyy HH:mm z";
DateTimeFormatter newYorkDateFormatter = DateTimeFormatter.ofPattern(newYorkDateTimePattern);
LocalDateTime summerDay = LocalDateTime.of(2016, 7, 31, 14, 15);
System.out.println(newYorkDateFormatter.format(ZonedDateTime.of(summerDay, ZoneId.of("UTC-4"))));

Это сгенерирует вывод «31.07.2016 14:15 UTC-04:00».

Мы можем анализировать строки даты и времени с часовыми поясами так же, как и раньше:

DateTimeFormatter zonedFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm z");
System.out.println(ZonedDateTime.from(zonedFormatter.parse("31.07.2016 14:15 GMT+02:00")).getOffset().getTotalSeconds());

Вывод этого кода составляет «7200» секунд или 2 часа, как мы и ожидали.

Мы должны убедиться, что мы предоставляем правильную строку даты и времени для метода parse() . Если мы передаем «31.07.2016 14:15» без часового пояса в zonedFormatter из последнего фрагмента кода, мы получим DateTimeParseException .

4.4. DateTimeFormatter для Instant

DateTimeFormatter поставляется с отличным средством мгновенного форматирования ISO под названием ISO_INSTANT . Как следует из названия, этот модуль форматирования предоставляет удобный способ форматирования или анализа мгновения в формате UTC.

Согласно официальной документации , момент не может быть отформатирован как дата или время без указания часового пояса . Таким образом, попытка использовать ISO_INSTANT для объектов LocalDateTime или LocalDate приведет к исключению:

@Test(expected = UnsupportedTemporalTypeException.class)
public void shouldExpectAnExceptionIfInputIsLocalDateTime() {
DateTimeFormatter.ISO_INSTANT.format(LocalDateTime.now());
}

Однако мы можем использовать ISO_INSTANT для форматирования экземпляра ZonedDateTime без каких-либо проблем:

@Test
public void shouldPrintFormattedZonedDateTime() {
ZonedDateTime zonedDateTime = ZonedDateTime.of(2021, 02, 15, 0, 0, 0, 0, ZoneId.of("Europe/Paris"));
String formattedZonedDateTime = DateTimeFormatter.ISO_INSTANT.format(zonedDateTime);

Assert.assertEquals("2021-02-14T23:00:00Z", formattedZonedDateTime);
}

Как мы видим, мы создали наш ZonedDateTime с часовым поясом «Европа/Париж». Однако отформатированный результат находится в формате UTC.

Точно так же при парсинге в ZonedDateTime нам нужно указать часовой пояс :

@Test
public void shouldParseZonedDateTime() {
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault());
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2021-10-01T05:06:20Z", formatter);

Assert.assertEquals("2021-10-01T05:06:20Z", DateTimeFormatter.ISO_INSTANT.format(zonedDateTime));
}

Невыполнение этого требования приведет к DateTimeParseException :

@Test(expected = DateTimeParseException.class)
public void shouldExpectAnExceptionIfTimeZoneIsMissing() {
ZonedDateTime zonedDateTime = ZonedDateTime.parse("2021-11-01T05:06:20Z", DateTimeFormatter.ISO_INSTANT);
}

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

5. Вывод

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

Мы можем узнать больше об API Date/Time в Java 8 в предыдущих руководствах . Как всегда, исходный код, использованный в этой статье, доступен на GitHub .