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

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

Введение

Метод format() возвращает полностью отформатированную строку, например «15 января 2025» или «15.01.2025». Это удобно для простого отображения, но вы не сможете стилизовать отдельные части по-разному. Например, нельзя сделать название месяца жирным, выделить год цветом или добавить специальную разметку к отдельным компонентам.

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

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

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

Представьте календарь, который показывает даты с названием месяца жирным шрифтом. С помощью 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);
// Output: "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: " at " },
//   { 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() — это применение разных стилей к разным компонентам. Можно обработать массив parts, чтобы обернуть определённые типы в 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);
// Output: "<strong>January</strong> 15, 2025"

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

Отдельное оформление года:

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);
// Output: "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);
// Output: "<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: Эра, например, до н.э., н.э. или BCE
  • year: Год, например, 2025
  • month: Название или номер месяца, например, январь или 01
  • day: День месяца, например, 15
  • dayPeriod: AM, PM или другие периоды дня, зависящие от локали
  • hour: Час, например, 14 или 2
  • minute: Минута, например, 30
  • second: Секунда, например, 45
  • fractionalSecond: Миллисекунды или другие доли секунды
  • timeZoneName: Название часового пояса, например, PST или Pacific Standard Time
  • 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: "BC" }
// ]

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

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);
// Parts will include { type: "timeZoneName", value: "PST" } or similar

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

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);
// Parts will include { 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));
// Output: "January <span class="text-blue-600 font-bold">18</span>, 2025"

const monday = new Date(2025, 0, 13);
console.log(formatDateWithHighlight(monday));
// Output: "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));
// Output: "<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().