Кастомные локаль дата

Работа с датами в FormatJS строится поверх стандарта ECMAScript Internationalization API (Intl), где основным механизмом форматирования выступает Intl.DateTimeFormat. Библиотека react-intl расширяет этот механизм, добавляя декларативные компоненты, единые наборы форматов и централизованное управление локализацией дат через провайдер международных настроек.

Форматирование даты в локализованном приложении всегда опирается на три уровня: исходное значение (обычно timestamp или Date), локаль (например, ru, en-US) и набор правил форматирования (short, medium, long или кастомные шаблоны). FormatJS предоставляет абстракцию над этими уровнями, позволяя избегать прямого использования Intl.DateTimeFormat в каждом компоненте интерфейса.


Основные инструменты для работы с датами — компоненты FormattedDate, FormattedTime и FormattedDateTime (в зависимости от версии API может использоваться только FormattedDate с опциями).

Простейший пример форматирования:

import { FormattedDate } from 'react-intl';

function Example({ date }) {
  return (
    <FormattedDate
      value={date}
    />
  );
}

По умолчанию используется формат, соответствующий локали, переданной через IntlProvider. Однако поведение можно изменять через свойства year, month, day, hour, minute, second, а также через format.


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

FormatJS поддерживает именованные форматы, которые можно воспринимать как предопределённые профили отображения даты.

<FormattedDate
  value={date}
  year="numeric"
  month="long"
  day="2-digit"
/>

Такой подход напрямую транслируется в Intl.DateTimeFormatOptions:

  • numeric — числовое представление
  • 2-digit — фиксированная ширина
  • long, short, narrow — текстовые формы

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


IntlProvider и централизованное управление форматами

Ключевой механизм кастомизации дат в FormatJS — передача объекта formats в IntlProvider. Этот объект позволяет определить переиспользуемые именованные шаблоны.

import { IntlProvider } from 'react-intl';

const formats = {
  date: {
    short: {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit'
    },
    long: {
      year: 'numeric',
      month: 'long',
      day: '2-digit'
    },
    timeOnly: {
      hour: '2-digit',
      minute: '2-digit'
    }
  }
};

function App({ locale, messages }) {
  return (
    <IntlProvider locale={locale} messages={messages} formats={formats}>
      <Root />
    </IntlProvider>
  );
}

Теперь любой компонент может использовать эти шаблоны:

<FormattedDate value={date} format="long" />

Или комбинировать форматирование времени:

<FormattedTime value={date} format="timeOnly" />

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

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

const formats = {
  date: {
    short: {
      year: '2-digit',
      month: '2-digit',
      day: '2-digit'
    }
  }
};

<IntlProvider
  locale="ru"
  formats={formats}
>

При этом базовые правила локали сохраняются: разделители, порядок элементов и система календаря берутся из CLDR-данных, а кастомный формат влияет только на уровень детализации.


Работа через useIntl и программное форматирование

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

import { useIntl } from 'react-intl';

function Example({ date }) {
  const intl = useIntl();

  const formatted = intl.formatDate(date, {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });

  return <span>{formatted}</span>;
}

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


Разделение форматов: дата, время и комбинированные представления

В FormatJS различаются три уровня:

  • formatDate — только календарная дата
  • formatTime — только время суток
  • formatDateTime — комбинированное представление
intl.formatDateTime(date, {
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit'
});

Комбинированные форматы особенно важны, поскольку порядок даты и времени зависит от локали. Например, в одних локалях сначала идёт дата, затем время, в других — наоборот.


Учёт временных зон

Форматирование даты в FormatJS опирается на стандарт Intl, который поддерживает временные зоны через параметр timeZone.

<FormattedDate
  value={date}
  timeZone="Europe/Moscow"
  year="numeric"
  month="long"
  day="numeric"
/>

При программном использовании:

intl.formatDate(date, {
  timeZone: 'Asia/Almaty',
  year: 'numeric',
  month: 'short',
  day: '2-digit'
});

Важно учитывать, что поведение зависит от окружения: Node.js требует ICU-данных для полной поддержки всех временных зон.


Пользовательские форматы как слой абстракции

Кастомные форматы в FormatJS часто применяются как слой дизайн-системы. Вместо прямого указания параметров в компонентах создаются именованные стили:

const formats = {
  date: {
    uiTable: {
      year: 'numeric',
      month: 'short',
      day: '2-digit'
    },
    uiHeader: {
      year: 'numeric',
      month: 'long'
    },
    uiCompact: {
      month: '2-digit',
      day: '2-digit'
    }
  }
};

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


Поведение в разных локалях и роль CLDR

FormatJS использует данные CLDR (Common Locale Data Repository), которые определяют:

  • порядок компонентов даты
  • локальные названия месяцев и дней недели
  • разделители и символы
  • правила сокращений

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

{ year: 'numeric', month: 'long', day: 'numeric' }

в ru и en-US будет выглядеть по-разному структурно, несмотря на одинаковые параметры.


Динамическое переключение форматов

В реальных приложениях формат может зависеть от контекста: типа экрана, плотности интерфейса или пользовательских настроек.

function getDateFormat(size) {
  switch (size) {
    case 'compact':
      return 'uiCompact';
    case 'header':
      return 'uiHeader';
    default:
      return 'uiTable';
  }
}

<FormattedDate value={date} format={getDateFormat(viewMode)} />

Такой подход позволяет отделить бизнес-логику от правил отображения.


Ограничения кастомизации

Несмотря на гибкость, FormatJS ограничен возможностями Intl.DateTimeFormat:

  • невозможно произвольно менять порядок полей внутри одной локали
  • нельзя добавлять пользовательские текстовые элементы внутри даты через форматтер
  • ограничен набор доступных типов представления (numeric, long, short)

Для более сложных сценариев требуется ручная сборка строки на основе intl.formatDateToParts, которая возвращает структурированные части даты.


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

formatDateToParts позволяет получить доступ к каждому элементу даты отдельно:

const parts = intl.formatDateToParts(date, {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

Результат представляет массив:

[
  { type: 'day', value: '29' },
  { type: 'literal', value: ' ' },
  { type: 'month', value: 'мая' },
  { type: 'literal', value: ' ' },
  { type: 'year', value: '2026' }
]

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


Интеграция кастомных форматов в архитектуру приложения

В крупных системах форматирование дат выносится в отдельный слой конфигурации, который объединяет:

  • базовые форматы (design system)
  • локальные переопределения (per-locale overrides)
  • контекстные форматы (UI state)

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