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 .