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

Форматирование составных значений в js-joda опирается на работу с объектами, реализующими интерфейс TemporalAccessor, где дата, время и дополнительные компоненты представляют собой единое значение, но логически состоят из нескольких частей. На практике это чаще всего LocalDateTime, ZonedDateTime, OffsetDateTime и их комбинации с часовыми поясами и смещениями. Форматирование таких структур требует точного управления шаблонами, локалью и правилами вывода отдельных полей.

В основе системы форматирования лежит DateTimeFormatter, который преобразует временные объекты в строку и обратно. Форматтер работает не с конкретными классами, а с абстракцией временных данных, что позволяет единообразно обрабатывать составные значения.

Форматирование всегда происходит через метод format, принимающий объект временного типа:

import { DateTimeFormatter } from '@js-joda/core';
import { LocalDateTime } from '@js-joda/core';

const dt = LocalDateTime.of(2026, 1, 15, 10, 30, 45);

const formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

const result = formatter.format(dt);

ISO-форматы являются базовой категорией форматирования и обеспечивают стандартизированное представление составных значений без локализации.

ISO-форматы как композиция компонентов

ISO-форматы в js-joda представляют собой заранее определённые композиции полей даты и времени. Например, ISO_LOCAL_DATE_TIME включает дату и время без зоны, а ISO_ZONED_DATE_TIME добавляет идентификатор часового пояса.

Состав таких форматов можно рассматривать как фиксированную последовательность элементов:

  • год, месяц, день
  • разделитель T
  • часы, минуты, секунды, наносекунды
  • смещение или идентификатор зоны (в зависимости от формата)

При использовании ZonedDateTime форматирование автоматически включает дополнительные компоненты:

import { ZonedDateTime, ZoneId, DateTimeFormatter } from '@js-joda/core';

const zdt = ZonedDateTime.of(
  2026, 1, 15, 10, 30, 45, 0,
  ZoneId.of('Europe/Berlin')
);

const formatted = DateTimeFormatter.ISO_ZONED_DATE_TIME.format(zdt);

Паттерны и синтаксис ofPattern

Наиболее гибкий способ форматирования составных значений — использование пользовательских шаблонов через DateTimeFormatter.ofPattern.

Шаблон представляет собой строку, где каждый символ или группа символов соответствует определённому полю временного объекта.

Основные токены

  • y — год
  • M — месяц
  • d — день
  • H — часы (24-часовой формат)
  • m — минуты
  • s — секунды
  • S — доли секунды
  • V, z, O, X, x — часовые пояса

Пример составного форматирования:

const formatter = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss');

const result = formatter.format(dt);

Паттерн объединяет несколько временных компонентов в одну строку, обеспечивая строгий контроль над порядком и разделителями.

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

При работе с ZonedDateTime добавляется дополнительный уровень сложности: идентификатор зоны или смещение.

Форматы зон

  • Z — смещение от UTC (+0100)
  • X — ISO-смещение (+01, +0100, +01:00)
  • z — текстовое имя зоны (Europe/Berlin)
  • V — идентификатор зоны

Пример:

import { ZonedDateTime, ZoneId, DateTimeFormatter } from '@js-joda/core';

const zdt = ZonedDateTime.of(
  2026, 5, 24, 14, 45, 0, 0,
  ZoneId.of('Asia/Almaty')
);

const formatter = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss Z VV');

const result = formatter.format(zdt);

Здесь составное значение включает одновременно локальную дату-время, числовое смещение и идентификатор зоны.

DateTimeFormatterBuilder и конструирование сложных форматов

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

Добавление компонентов

import { DateTimeFormatterBuilder } from '@js-joda/core';

const builder = new DateTimeFormatterBuilder()
  .appendValue('year')
  .appendLiteral('-')
  .appendValue('monthOfYear', 2)
  .appendLiteral('-')
  .appendValue('dayOfMonth');

Такой подход позволяет явно управлять каждым элементом составного значения.

Временные компоненты

const builder = new DateTimeFormatterBuilder()
  .appendValue('hourOfDay', 2)
  .appendLiteral(':')
  .appendValue('minuteOfHour', 2)
  .appendLiteral(':')
  .appendValue('secondOfMinute', 2);

Каждый компонент добавляется отдельно, что исключает неоднозначность шаблона.

Условные блоки и опциональные части

Составные значения часто содержат необязательные компоненты, например секунды или миллисекунды. Для этого используются конструкции optionalStart и optionalEnd.

const formatter = new DateTimeFormatterBuilder()
  .appendValue('hourOfDay', 2)
  .appendLiteral(':')
  .appendValue('minuteOfHour', 2)
  .optionalStart()
  .appendLiteral(':')
  .appendValue('secondOfMinute', 2)
  .optionalEnd()
  .toFormatter();

В результате секундная часть отображается только при наличии значения.

Локализация составных значений

Форматирование может зависеть от локали, что влияет на порядок компонентов, текстовые представления месяцев и дней недели, а также формат разделителей.

import { Locale } from '@js-joda/locale';

const formatter = DateTimeFormatter
  .ofPattern('EEEE, d MMMM yyyy')
  .withLocale(Locale.forLanguageTag('ru'));

const result = formatter.format(dt);

Локализация особенно важна при форматировании составных значений, включающих текстовые элементы.

Текстовые представления компонентов

Некоторые элементы временных данных могут быть представлены в текстовом виде:

  • месяцы (January, February)
  • дни недели (Monday, Tuesday)
  • зоны времени (Europe/Berlin)

Для этого используются методы appendText в DateTimeFormatterBuilder или соответствующие токены в шаблонах.

const formatter = new DateTimeFormatterBuilder()
  .appendValue('dayOfMonth')
  .appendLiteral(' ')
  .appendText('monthOfYear')
  .appendLiteral(' ')
  .appendValue('year')
  .toFormatter();

Форматирование дробных частей времени

Составные значения часто включают наносекунды или миллисекунды. Для их форматирования используется токен S.

const formatter = DateTimeFormatter.ofPattern('HH:mm:ss.SSS');

Количество символов определяет точность вывода:

  • S — десятые доли
  • SS — сотые
  • SSS — миллисекунды

Объединение даты, времени и зоны в одном формате

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

const formatter = DateTimeFormatter.ofPattern(
  'yyyy-MM-dd HH:mm:ss.SSS VV'
);

Такой формат охватывает:

  • календарную дату
  • локальное время
  • дробную часть секунды
  • идентификатор временной зоны

При форматировании ZonedDateTime все элементы извлекаются автоматически из одного объекта.

Управление фиксированной шириной и заполнением

Для составных значений важна выравненность полей. В DateTimeFormatterBuilder можно задавать минимальную и максимальную ширину:

const formatter = new DateTimeFormatterBuilder()
  .appendValue('dayOfMonth', 2)
  .appendLiteral('/')
  .appendValue('monthOfYear', 2)
  .appendLiteral('/')
  .appendValue('year', 4)
  .toFormatter();

Это обеспечивает стабильный формат даже при изменении значений.

Разделители и литералы в составных форматах

Форматирование часто включает статические элементы: пробелы, знаки пунктуации, текстовые маркеры. Они добавляются через appendLiteral или напрямую в шаблон ofPattern.

const formatter = DateTimeFormatter.ofPattern('dd.MM.yyyy \'at\' HH:mm');

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

Взаимодействие форматирования и парсинга

Форматирование составных значений тесно связано с обратной операцией — разбором строки. Один и тот же форматтер может использоваться для round-trip преобразований:

const formatter = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss');

const text = formatter.format(dt);

const parsed = LocalDateTime.parse(text, formatter);

Согласованность шаблона критична для корректного восстановления составных значений.

Особенности работы с частично заполненными значениями

Некоторые временные типы не содержат всех компонентов. Например, LocalDate не включает время, а LocalTime не содержит даты. При попытке форматирования составного шаблона используется только доступная часть данных, а отсутствующие элементы либо игнорируются, либо вызывают ошибку в зависимости от структуры форматтера.

DateTimeFormatterBuilder позволяет явно контролировать поведение через опциональные блоки и правила заполнения.

Сложные композиции через builder API

Builder API позволяет формировать многоуровневые структуры, где разные части даты и времени комбинируются в единую строку с разными правилами форматирования:

const formatter = new DateTimeFormatterBuilder()
  .appendValue('year')
  .appendLiteral('-')
  .appendValue('monthOfYear')
  .appendLiteral('-')
  .appendValue('dayOfMonth')
  .appendLiteral(' ')
  .appendValue('hourOfDay')
  .appendLiteral(':')
  .appendValue('minuteOfHour')
  .appendLiteral(':')
  .appendValue('secondOfMinute')
  .appendLiteral(' ')
  .appendZoneId()
  .toFormatter();

Такая конструкция представляет собой полное составное значение, объединяющее календарь, время и зону в единую строку с детализированным контролем каждого элемента.