Диапазон дат представляет собой пару значений — начальную и конечную дату, которые интерпретируются как единый временной интервал. В прикладных интерфейсах такие диапазоны используются для отображения периодов бронирования, событий, аналитических срезов, финансовых отчётных интервалов.
Локализация диапазонов дат требует не только перевода формата отображения, но и учёта правил конкретной локали: порядка компонентов, разделителей, сокращений месяцев, правил опущения повторяющихся частей даты. FormatJS предоставляет инструменты, основанные на стандарте ECMAScript Internationalization API (Intl), для корректного форматирования таких интервалов.
Современный JavaScript включает встроенный метод форматирования диапазонов дат:
const formatter = new Intl.DateTimeFormat('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
formatter.formatRange(
new Date(2026, 0, 1),
new Date(2026, 0, 10)
);
Метод formatRange автоматически определяет, какие части
дат можно опустить для компактного отображения. Например, если год
одинаковый, он может быть показан один раз:
Поведение зависит от локали и реализовано в движке JavaScript, а не в самой библиотеке FormatJS.
FormatJS расширяет и нормализует работу с датами через набор пакетов, основными из которых являются:
@formatjs/intl-datetimeformatreact-intl@formatjs/intl-utilsОсновная задача FormatJS — обеспечить:
formatRange)В экосистеме React основным инструментом является
react-intl. Он предоставляет форматирование диапазонов
через метод formatDateTimeRange.
Пример использования:
import { useIntl } from 'react-intl';
function DateRange() {
const intl = useIntl();
const range = intl.formatDateTimeRange(
new Date(2026, 0, 1),
new Date(2026, 0, 10),
{
year: 'numeric',
month: 'long',
day: 'numeric'
}
);
return <span>{range}</span>;
}
Метод инкапсулирует вызовы
Intl.DateTimeFormat.formatRange и добавляет поддержку
fallback-логики для сред, где метод отсутствует.
Форматирование диапазонов дат существенно зависит от локали.
new Intl.DateTimeFormat('ru-RU', { month: 'short', day: 'numeric' })
.formatRange(new Date(2026, 0, 1), new Date(2026, 0, 10));
Результат:
new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' })
.formatRange(new Date(2026, 0, 1), new Date(2026, 0, 10));
Результат:
Различие обусловлено правилами порядка компонентов и локальными соглашениями о сокращениях месяцев.
Одной из ключевых особенностей форматирования диапазонов является интеллектуальное сокращение повторяющихся элементов.
new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).formatRange(
new Date(2026, 0, 1),
new Date(2026, 0, 10)
);
Результат:
Здесь месяц и год выводятся один раз, поскольку они совпадают.
new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: 'long',
day: 'numeric'
}).formatRange(
new Date(2026, 0, 28),
new Date(2026, 1, 3)
);
Результат:
В этом случае повторение месяца необходимо для корректного восприятия диапазона.
FormatJS и Intl позволяют управлять степенью детализации через options:
yearmonthdayhourminutesecondnew Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
}).formatRange(
new Date(2026, 0, 1, 10, 0),
new Date(2026, 0, 1, 18, 30)
);
Результат:
Если диапазон находится в пределах одной даты, дата может быть показана один раз, а изменяются только временные компоненты.
В старых средах метод formatRange может отсутствовать.
FormatJS решает эту проблему через полифилл
@formatjs/intl-datetimeformat.
Пример подключения:
import '@formatjs/intl-datetimeformat/polyfill';
import '@formatjs/intl-datetimeformat/locale-data/en';
import '@formatjs/intl-datetimeformat/locale-data/ru';
После подключения поведение становится консистентным во всех поддерживаемых окружениях.
Если formatRange недоступен, библиотека может
использовать стратегию:
Упрощённая логика:
function fallbackRangeFormat(a, b, formatter) {
return `${formatter.format(a)} – ${formatter.format(b)}`;
}
В реальных реализациях FormatJS используется более сложная система сравнения полей даты (year, month, day, hour и т.д.), чтобы минимизировать избыточность.
Форматирование диапазона зависит от различий между компонентами:
Это поведение определяется через внутренние поля
DateTimeFormatPart и алгоритмы сравнения локализованных
частей.
FormatJS часто используется вместе с ICU MessageFormat, где диапазоны дат могут быть частью интернационализированных строк.
Пример:
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
event: {
id: 'event.period',
defaultMessage: 'Событие проходит с {start} по {end}'
}
});
intl.formatMessage(messages.event, {
start: intl.formatDateTimeRange(start, end)
});
Здесь диапазон предварительно форматируется и подставляется как готовая строка, учитывающая локаль.
Форматирование диапазонов включает:
Оптимизации включают:
Intl.DateTimeFormatНекоторые аспекты требуют учёта:
formatRange не полностью идентично во всех
движках JavaScriptFormatJS сглаживает часть этих различий, но не устраняет фундаментальные различия ICU-реализаций.
В некоторых случаях диапазон может содержать неполные даты (например, только месяц и год). Тогда форматтер адаптируется к доступной точности:
new Intl.DateTimeFormat('ru-RU', { month: 'long', year: 'numeric' })
.formatRange(
new Date(2026, 0, 1),
new Date(2026, 5, 1)
);
Результат:
Уровень детализации автоматически снижается до месяца.
Если начальная и конечная даты совпадают:
new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
.formatRange(date, date);
Результат:
Диапазон схлопывается до единственного значения, что предотвращает избыточное отображение.
Ручное формирование диапазона:
`${start.toLocaleDateString()} - ${end.toLocaleDateString()}`
Проблемы такого подхода:
Использование formatRange устраняет эти проблемы за счёт
стандартизированного алгоритма ICU.