Как получить отдельные части отформатированной даты в JavaScript

Используйте formatToParts(), чтобы получить доступ к каждому компоненту даты, отформатированной для локали, отдельно

Введение

Метод format() возвращает полностью отформатированную строку, например, "15 января 2025 года" или "15/01/2025". Это хорошо подходит для простого отображения, но вы не можете стилизовать отдельные части по-разному. Вы не можете сделать название месяца жирным, выделить год другим цветом или применить пользовательскую разметку к определённым компонентам.

JavaScript предоставляет метод formatToParts() для решения этой проблемы. Вместо того чтобы возвращать одну строку, он возвращает массив объектов, каждый из которых представляет одну часть отформатированной даты. Каждая часть имеет тип, например, month, day или year, и значение, например, "январь", "15" или "2025". Вы можете обработать эти части, чтобы применить пользовательское форматирование, создать сложные макеты или интегрировать отформатированные даты в богатые пользовательские интерфейсы.

Почему сложно кастомизировать отформатированные строки

Когда вы получаете отформатированную строку, например, "15 января 2025 года", вы не можете легко определить, где заканчивается месяц и начинается день. Разные локали располагают компоненты в разном порядке. Некоторые локали используют разные разделители. Надёжный разбор таких строк требует сложной логики, которая дублирует правила форматирования, уже реализованные в API Intl.

Рассмотрим приложение-календарь, которое отображает даты с названием месяца, выделенным жирным шрифтом. С методом format() вам нужно будет:

  1. Определить, какие символы представляют название месяца
  2. Учитывать пробелы и знаки препинания между компонентами
  3. Обрабатывать разные форматы месяцев в разных локалях
  4. Аккуратно разбирать строку, чтобы не нарушить дату

Этот подход ненадёжен и подвержен ошибкам. Любое изменение правил форматирования локали ломает вашу логику разбора.

Метод formatToParts() устраняет эту проблему, предоставляя компоненты отдельно. Вы получаете структурированные данные, которые точно указывают, какая часть за что отвечает, независимо от локали.

Использование formatToParts для получения компонентов даты

Метод formatToParts() работает идентично методу format(), за исключением возвращаемого значения. Вы создаёте форматтер с теми же параметрами, а затем вызываете formatToParts() вместо format().

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);

Это выводит массив объектов:

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2025" }
]

Каждый объект содержит свойство type, указывающее, что представляет часть, и свойство value, содержащее фактическую строку. Части появляются в том же порядке, что и в отформатированном выводе.

Вы можете проверить это, объединив все значения:

const formatted = parts.map(part => part.value).join("");
console.log(formatted);
// Вывод: "January 15, 2025"

Объединённые части дают точно такой же результат, как вызов format().

Понимание типов частей

Свойство type определяет каждый компонент. Различные параметры форматирования создают разные типы частей.

Для базового форматирования даты:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" }
// ]

Тип month представляет название или номер месяца. Тип day представляет день месяца. Тип year представляет год. Тип literal представляет пробелы, знаки препинания или другой текст, добавленный форматтером.

Для дат с днями недели:

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "weekday", value: "Wednesday" },
//   { type: "literal", value: ", " },
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" }
// ]

Тип weekday представляет день недели.

Для дат со временем:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric",
  second: "numeric"
});

const date = new Date(2025, 0, 15, 14, 30, 45);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" },
//   { type: "literal", value: " в " },
//   { type: "hour", value: "2" },
//   { type: "literal", value: ":" },
//   { type: "minute", value: "30" },
//   { type: "literal", value: ":" },
//   { type: "second", value: "45" },
//   { type: "literal", value: " " },
//   { type: "dayPeriod", value: "PM" }
// ]

Типы hour, minute и second представляют компоненты времени. Тип dayPeriod представляет AM или PM в 12-часовом формате.

Применение пользовательского стиля к частям даты

Основной случай использования formatToParts() — это применение различных стилей к разным компонентам. Вы можете обработать массив частей, чтобы обернуть определенные типы в HTML-элементы.

Выделение названия месяца жирным шрифтом:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "month") {
      return `<strong>${part.value}</strong>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Результат: "<strong>January</strong> 15, 2025"

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

Выделение года другим стилем:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);
const html = parts
  .map(part => {
    if (part.type === "year") {
      return `<span class="text-gray-500">${part.value}</span>`;
    }
    return part.value;
  })
  .join("");

console.log(html);
// Результат: "January 15, <span class="text-gray-500">2025</span>"

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

Создание пользовательских отображений даты с несколькими стилями

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

const formatter = new Intl.DateTimeFormat("en-US", {
  weekday: "long",
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const html = parts
  .map(part => {
    switch (part.type) {
      case "weekday":
        return `<span class="weekday">${part.value}</span>`;
      case "month":
        return `<span class="month">${part.value}</span>`;
      case "day":
        return `<span class="day">${part.value}</span>`;
      case "year":
        return `<span class="year">${part.value}</span>`;
      case "literal":
        return `<span class="literal">${part.value}</span>`;
      default:
        return part.value;
    }
  })
  .join("");

console.log(html);
// Результат: "<span class="weekday">Wednesday</span><span class="literal">, </span><span class="month">January</span><span class="literal"> </span><span class="day">15</span><span class="literal">, </span><span class="year">2025</span>"

Этот детальный контроль позволяет точно стилизовать каждый компонент. Затем вы можете использовать CSS для стилизации каждого класса по-разному.

Создание пользовательских форматов даты

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

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
const parts = formatter.formatToParts(date);

const day = parts.find(p => p.type === "day").value;
const month = parts.find(p => p.type === "month").value;
const year = parts.find(p => p.type === "year").value;

const customLayout = `
  <div class="date-card">
    <div class="day-large">${day}</div>
    <div class="month-small">${month}</div>
    <div class="year-small">${year}</div>
  </div>
`;

console.log(customLayout);

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

Все доступные типы частей

Свойство type может принимать следующие значения в зависимости от используемых параметров форматирования:

  • weekday: День недели, например, понедельник или пн
  • era: Индикатор эпохи, например, до н.э., н.э. или нашей эры
  • year: Год, например, 2025
  • month: Название или номер месяца, например, январь или 01
  • day: День месяца, например, 15
  • dayPeriod: AM или PM или другие специфичные для локали периоды дня
  • hour: Час, например, 14 или 2
  • minute: Минута, например, 30
  • second: Секунда, например, 45
  • fractionalSecond: Миллисекунды или другие доли секунды
  • timeZoneName: Название часового пояса, например, PST или Тихоокеанское стандартное время
  • literal: Пробелы, пунктуация или другой текст, добавленный форматированием
  • relatedYear: Григорианский год в альтернативных календарных системах
  • yearName: Названный год в некоторых календарных системах
  • unknown: Неопознанные токены

Не каждая опция форматирования создает все типы частей. Полученные части зависят от значения даты и конфигурации форматтера.

Даты с индикаторами эпохи:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  era: "short"
});

const date = new Date(-100, 0, 1);
const parts = formatter.formatToParts(date);
console.log(parts);
// [
//   { type: "year", value: "101" },
//   { type: "literal", value: " " },
//   { type: "era", value: "до н.э." }
// ]

Даты с часовыми поясами:

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric",
  timeZoneName: "short"
});

const date = new Date(2025, 0, 15, 14, 30);
const parts = formatter.formatToParts(date);
console.log(parts);
// Части будут включать { type: "timeZoneName", value: "PST" } или подобное

Даты с долями секунды:

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 3
});

const date = new Date(2025, 0, 15, 14, 30, 45, 123);
const parts = formatter.formatToParts(date);
// Части будут включать { type: "fractionalSecond", value: "123" }

Условное выделение компонентов даты

Некоторые приложения выделяют определённые компоненты даты на основе бизнес-логики. С помощью formatToParts() вы можете применять стили в зависимости от значения даты, сохраняя правильное форматирование.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

function formatDateWithHighlight(date) {
  const parts = formatter.formatToParts(date);
  const isWeekend = date.getDay() === 0 || date.getDay() === 6;

  const html = parts
    .map(part => {
      if (part.type === "day" && isWeekend) {
        return `<span class="text-blue-600 font-bold">${part.value}</span>`;
      }
      return part.value;
    })
    .join("");

  return html;
}

const saturday = new Date(2025, 0, 18);
console.log(formatDateWithHighlight(saturday));
// Вывод: "January <span class="text-blue-600 font-bold">18</span>, 2025"

const monday = new Date(2025, 0, 13);
console.log(formatDateWithHighlight(monday));
// Вывод: "January 13, 2025"

Дата получает правильное форматирование для локали, а условное стилизование применяется на основе бизнес-логики.

Создание доступных отображений даты

Вы можете использовать formatToParts() для добавления атрибутов доступности к отформатированным датам. Это помогает экранным чтецам правильно озвучивать значения.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

function formatAccessibleDate(date) {
  const parts = formatter.formatToParts(date);
  const formatted = parts.map(part => part.value).join("");
  const isoDate = date.toISOString().split('T')[0];

  return `<time datetime="${isoDate}">${formatted}</time>`;
}

const date = new Date(2025, 0, 15);
console.log(formatAccessibleDate(date));
// Вывод: "<time datetime="2025-01-15">January 15, 2025</time>"

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

Как части сохраняют форматирование, специфичное для локали

Массив частей автоматически сохраняет правила форматирования, специфичные для локали. Разные локали располагают компоненты в разном порядке и используют разные форматы, но formatToParts() обрабатывает эти различия.

const usFormatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2025, 0, 15);
console.log(usFormatter.formatToParts(date));
// [
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "day", value: "15" },
//   { type: "literal", value: ", " },
//   { type: "year", value: "2025" }
// ]

const ukFormatter = new Intl.DateTimeFormat("en-GB", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(ukFormatter.formatToParts(date));
// [
//   { type: "day", value: "15" },
//   { type: "literal", value: " " },
//   { type: "month", value: "January" },
//   { type: "literal", value: " " },
//   { type: "year", value: "2025" }
// ]

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

const jpFormatter = new Intl.DateTimeFormat("ja-JP", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(jpFormatter.formatToParts(date));
// [
//   { type: "year", value: "2025" },
//   { type: "literal", value: "年" },
//   { type: "month", value: "1月" },
//   { type: "day", value: "15" },
//   { type: "literal", value: "日" }
// ]

Японский формат использует другой порядок и включает символы, такие как 年 (год) и 日 (день). Массив частей автоматически отражает эти особенности, специфичные для локали.

Комбинирование formatToParts с компонентами фреймворков

Современные фреймворки, такие как React, могут использовать formatToParts() для эффективного создания компонентов.

function DateDisplay({ date, locale, options }) {
  const formatter = new Intl.DateTimeFormat(locale, options);
  const parts = formatter.formatToParts(date);

  return (
    <span className="date-display">
      {parts.map((part, index) => {
        if (part.type === "month") {
          return <strong key={index}>{part.value}</strong>;
        }
        if (part.type === "year") {
          return <span key={index} className="text-sm text-gray-500">{part.value}</span>;
        }
        return <span key={index}>{part.value}</span>;
      })}
    </span>
  );
}

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

Когда использовать formatToParts вместо format

Используйте format(), если вам нужна простая отформатированная строка без какой-либо настройки. Это наиболее распространённый случай для отображения дат.

Используйте formatToParts(), если вам нужно:

  • Применить разное оформление к различным частям даты
  • Создавать HTML или JSX с отформатированными датами
  • Добавлять атрибуты или метаданные к определённым компонентам
  • Переставлять компоненты даты в пользовательском порядке
  • Интегрировать отформатированные даты в сложные макеты
  • Программно обрабатывать отформатированный вывод

Метод formatToParts() имеет немного большую нагрузку, чем format(), так как создаёт массив объектов вместо одной строки. Эта разница незначительна для типичных приложений, но если вы форматируете тысячи дат в секунду, format() работает быстрее.

Для большинства приложений выбирайте метод в зависимости от ваших потребностей в оформлении, а не из-за производительности. Если вам не нужно настраивать вывод, используйте format(). Если вам нужно пользовательское оформление или разметка, используйте formatToParts().