W3docs

Форматирование дат в Java

Форматируйте даты и время в строки с помощью DateTimeFormatter и стандартных или пользовательских шаблонов.

Форматирование дат — это преобразование значения даты/времени в строку, понятную человеку. В этой главе рассматривается DateTimeFormatter — как его создать (встроенный, локализованный или на основе шаблона), полный алфавит шаблонов, работа с локалями и часовыми поясами, а также типичные ошибки, приводящие к некорректному выводу. Форматировщик работает с каждым типом из java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime и Instant.

У каждого типа java.time есть метод toString(), который возвращает представление в формате ISO-8601: 2025-11-04, 14:30:00, 2025-11-04T14:30:00Z. Это удобно для логов и передачи данных между машинами. Для отображения, понятного пользователю ("November 4, 2025" или "4 Nov, 14:30"), нужен форматировщик.

Класс — java.time.format.DateTimeFormatter. Это современная, потокобезопасная, неизменяемая замена устаревшему java.text.SimpleDateFormat (который не был потокобезопасным и порождал трудноуловимые баги в продакшене при совместном использовании). Храните экземпляр как static final и используйте его из нескольких потоков без какой-либо синхронизации или защитного копирования.

Три способа создать форматировщик

// 1. Built-in ISO formatters
DateTimeFormatter.ISO_LOCAL_DATE;                  // 2025-11-04
DateTimeFormatter.ISO_LOCAL_DATE_TIME;             // 2025-11-04T14:30:00
DateTimeFormatter.ISO_OFFSET_DATE_TIME;            // 2025-11-04T14:30:00-05:00
DateTimeFormatter.ISO_ZONED_DATE_TIME;             // 2025-11-04T14:30:00-05:00[America/New_York]
DateTimeFormatter.ISO_INSTANT;                     // 2025-11-04T19:30:00Z
DateTimeFormatter.BASIC_ISO_DATE;                  // 20251104

// 2. Localised formatters
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);          // November 4, 2025 (en-US)
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);    // Nov 4, 2025, 2:30:00 PM

// 3. Pattern-based formatters
DateTimeFormatter.ofPattern("dd MMM yyyy");                   // 04 Nov 2025
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm zzz");          // 2025-11-04 14:30 EST

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

Форматирование

Форма вызова симметрична с обеих сторон:

String s = formatter.format(temporal);
String s2 = temporal.format(formatter);                       // same thing, fluent style

Оба варианта работают. Большинство кода использует fluent-форму.

LocalDate today = LocalDate.now();
String us = today.format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));         // 11/04/2025
String iso = today.format(DateTimeFormatter.ISO_LOCAL_DATE);                 // 2025-11-04
String eu = today.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));          // 04.11.2025

Алфавит шаблонов

Большая таблица, к которой вы будете возвращаться. Буквы чувствительны к регистру, и количество повторений имеет значение.

БукваЗначениеПример
yгодy2025, yy25, yyyy2025
MмесяцM11, MM11, MMMNov, MMMMNovember
dдень месяцаd4, dd04
Eдень неделиETue, EEEETuesday
Hчас 0-23H14, HH14
hчас 1-12h2, hh02 (используется с a)
aAM/PMaPM
mминутыm5, mm05
sсекундыs9, ss09
Sдоли секундыSSS123 (миллисекунды)
nнаносекундыnnnnnnnnn123456789
zназвание зоныzEST, zzzzEastern Standard Time
Zсмещение зоныZ-0500, ZZ-0500, ZZZZGMT-05:00
XISO-смещениеX-05, XX-0500, XXX-05:00
Vидентификатор зоныVVAmerica/New_York

Литеральный текст заключается в одинарные кавычки:

DateTimeFormatter.ofPattern("EEEE, MMMM d 'at' h:mm a");      // Tuesday, November 4 at 2:30 PM

Для вывода самой одинарной кавычки используйте две подряд: ''.

Самая распространённая путаница — m против M (строчная = минуты, прописная = месяц) и H против h (прописная = 0–23, строчная = 1–12). Большинство ошибок вида «время отображается неправильно» вызваны именно этими опечатками.

Локализация: Locale и withLocale

Форматировщик использует локаль JVM по умолчанию, если не указана другая. Чтобы вывод был «всегда на английском» или «всегда на немецком», явно задайте локаль:

DateTimeFormatter english = DateTimeFormatter.ofPattern("EEEE, MMMM d", Locale.US);
DateTimeFormatter german  = DateTimeFormatter.ofPattern("EEEE, d. MMMM", Locale.GERMAN);
DateTimeFormatter french  = DateTimeFormatter.ofPattern("EEEE d MMMM", Locale.FRENCH);

today.format(english);   // Tuesday, November 4
today.format(german);    // Dienstag, 4. November
today.format(french);    // mardi 4 novembre

Для серверного рендеринга всегда передавайте локаль. «Локаль JVM по умолчанию» непредсказуема на продакшен-серверах и является источником ошибок вида «на моём ноутбуке всё правильно, на сервере — нет».

Отображение часового пояса

ZonedDateTime и Instant — единственные типы, содержащие информацию о часовом поясе. Форматирование LocalDateTime с шаблоном, включающим z или Z, выбросит исключение — поясу нечего печатать. Сначала выполните преобразование:

ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z"));   // 2025-11-04 14:30 EST

Для Instant форматировщику тоже нужна зона — у Instant её нет, поэтому форматировщики для отображения, включающие зависящие от зоны поля, требуют withZone:

DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
    .withZone(ZoneId.of("America/New_York"));
f.format(Instant.now());                                      // formatter supplies the zone for display

Без withZone форматирование Instant с календарным шаблоном выбросит исключение.

Стилизованные форматировщики с FormatStyle

Локализованные фабрики предоставляют четыре стандартных размера:

DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);   // 11/4/25 (en-US), 04.11.25 (de-DE)
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);  // Nov 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);    // November 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);    // Tuesday, November 4, 2025

Те же четыре размера есть у ofLocalizedTime и ofLocalizedDateTime. Используйте их, когда хотите, чтобы структура отображения следовала локали пользователя, а не навязывала единственный формат. Сочетайте с .withLocale(...) для явного указания локали.

Практический пример: одна дата, шесть вариантов отображения

Программа ниже форматирует один ZonedDateTime шестью распространёнными способами: ISO для машинных логов, 12-часовой формат для американских пользователей, 24-часовой европейский для немецких, длинная локализованная форма, пользовательский шаблон со встроенным литеральным текстом и форматировщик Instant через withZone для сырых временны́х меток.

java— editable, runs on the server

Что следует вынести из запуска:

  • Кешированные поля static final DateTimeFormatter — это правильный подход. DateTimeFormatter неизменяем и потокобезопасен; его создание недорого, но не бесплатно, поэтому повторное использование одного экземпляра — рекомендованный JDK-паттерн. Не создавайте новый экземпляр внутри горячего цикла.
  • Один и тот же ZonedDateTime дал шесть разных строк в зависимости от форматировщика. Объект-значение не изменился; только форматировщик управляет представлением. В этом и состоит разделение, ради которого существует DateTimeFormatter — держите тип значения чистым, а представление передавайте форматировщику.
  • Блок «common typo» напечатал 14:11 для HH:MM, потому что M — это месяц, а не минуты. Это самая распространённая путаница в алфавите шаблонов. Если отображаемое время выглядит подозрительно похожим на компонент даты — проверьте регистр букв в шаблоне.
  • Лестница FormatStyle выдала четыре строки прогрессирующей длины. Используйте FormatStyle.MEDIUM как разумный вариант по умолчанию для «показать дату пользователю, не задумываясь»; LONG и FULL — там, где год и день недели должны быть однозначными; SHORT — в узких элементах интерфейса.
  • LocalDateTime с шаблоном, содержащим зону, выбросил исключение — форматировщику нужны данные о зоне, а в LocalDateTime их нет. Решение: преобразовать (ldt.atZone(zone)) или убрать зависящее от зоны поле из шаблона. В любом случае сбой обнаруживается сразу в рантайме.

Что дальше

Форматирование — это направление значение → строка. Следующая глава, Java Date Parsing, — обратная операция: строка → значение — с теми же шаблонами DateTimeFormatter и теми же оговорками. Вместе они образуют I/O-границу для любого кода, обменивающегося датами с пользователями, конфигами, логами или удалёнными API.

Практика

Практика
Веб-сервер записывает временны́е метки с помощью `ZonedDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'))`. На JVM с немецкой локалью месяц отображается как 'Nov' вместо '11'. Какова наиболее вероятная причина?
Веб-сервер записывает временны́е метки с помощью `ZonedDateTime.now().format(DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm'))`. На JVM с немецкой локалью месяц отображается как 'Nov' вместо '11'. Какова наиболее вероятная причина?
Was this page helpful?