Intl.DateTimeFormat API

Форматируйте даты и время для любого языка без внешних библиотек

Введение

Даты отображаются по-разному в разных странах. 15 января 2024 года выглядит как 1/15/2024 в США, 15/1/2024 в Великобритании и 2024/1/15 в Японии. Форматы времени тоже отличаются. Американцы используют 12-часовой формат с AM и PM, а большинство стран — 24-часовой формат.

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

API Intl.DateTimeFormat решает эту задачу. Она предоставляет встроенное форматирование дат и времени с учётом локали во всех современных браузерах. Внешние библиотеки не нужны.

Базовое использование

Самый простой способ отформатировать дату — создать экземпляр Intl.DateTimeFormat и вызвать его метод format().

const date = new Date(2024, 0, 15, 14, 30);

const formatter = new Intl.DateTimeFormat("en-US");
formatter.format(date);
// "1/15/2024"

Измените локаль, чтобы увидеть разные правила форматирования.

const ukFormatter = new Intl.DateTimeFormat("en-GB");
ukFormatter.format(date);
// "15/01/2024"

const japanFormatter = new Intl.DateTimeFormat("ja-JP");
japanFormatter.format(date);
// "2024/1/15"

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

Что такое локаль

Локаль — это строка, определяющая язык и региональные предпочтения. Формат соответствует стандарту BCP 47: код языка, при необходимости с кодом региона.

Примеры шаблонов локалей:

"en"      // English (generic)
"en-US"   // English (United States)
"en-GB"   // English (United Kingdom)
"es"      // Spanish (generic)
"es-MX"   // Spanish (Mexico)
"es-ES"   // Spanish (Spain)
"zh-CN"   // Chinese (China, Simplified)
"zh-TW"   // Chinese (Taiwan, Traditional)

Если не указать параметр локали, браузер использует локаль пользователя по умолчанию.

const formatter = new Intl.DateTimeFormat();
formatter.format(date);
// Output varies based on user's browser locale

Можно также передать массив локалей. Браузер выберет первую поддерживаемую.

const formatter = new Intl.DateTimeFormat(["es-MX", "es", "en"]);
// Uses Spanish (Mexico) if available, falls back to generic Spanish, then English

Обзор параметров форматирования

Конструктор Intl.DateTimeFormat принимает объект с опциями, которые определяют, что будет отображаться в отформатированной дате. Есть два подхода к форматированию.

Первый способ использует стиль-шорткаты. Они позволяют быстро и стандартно оформить данные.

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "full",
  timeStyle: "short"
});

formatter.format(date);
// "Monday, January 15, 2024 at 2:30 PM"

Второй способ использует опции компонентов. Это даёт детальный контроль над каждой частью даты.

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

formatter.format(date);
// "January 15, 2024 at 2:30 PM"

Нельзя смешивать стиль-шорткаты с опциями компонентов. Используйте только один подход для каждого форматтера.

Стиль-шорткаты

Опции dateStyle и timeStyle предоставляют четыре предустановленных уровня форматирования.

Опция dateStyle форматирует только дату.

const date = new Date(2024, 0, 15);

const full = new Intl.DateTimeFormat("en-US", { dateStyle: "full" });
full.format(date);
// "Monday, January 15, 2024"

const long = new Intl.DateTimeFormat("en-US", { dateStyle: "long" });
long.format(date);
// "January 15, 2024"

const medium = new Intl.DateTimeFormat("en-US", { dateStyle: "medium" });
medium.format(date);
// "Jan 15, 2024"

const short = new Intl.DateTimeFormat("en-US", { dateStyle: "short" });
short.format(date);
// "1/15/24"

Опция timeStyle форматирует только время.

const date = new Date(2024, 0, 15, 14, 30, 45);

const full = new Intl.DateTimeFormat("en-US", { timeStyle: "full" });
full.format(date);
// "2:30:45 PM Eastern Standard Time"

const long = new Intl.DateTimeFormat("en-US", { timeStyle: "long" });
long.format(date);
// "2:30:45 PM EST"

const medium = new Intl.DateTimeFormat("en-US", { timeStyle: "medium" });
medium.format(date);
// "2:30:45 PM"

const short = new Intl.DateTimeFormat("en-US", { timeStyle: "short" });
short.format(date);
// "2:30 PM"

Комбинируйте обе опции, чтобы отформатировать дату и время вместе.

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short"
});

formatter.format(date);
// "Jan 15, 2024, 2:30 PM"

Стиль-шорткаты автоматически подстраиваются под локальные стандарты.

const usFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short",
  timeStyle: "short"
});
usFormatter.format(date);
// "1/15/24, 2:30 PM"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  dateStyle: "short",
  timeStyle: "short"
});
deFormatter.format(date);
// "15.1.24, 14:30"

В немецком формате используются точки как разделители, день и месяц меняются местами, а время отображается в 24-часовом формате. В американском — слэши, сначала месяц, а время в 12-часовом формате с AM/PM. Одни и те же опции — разный результат в зависимости от локали.

Опции компонентов

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

Опция year выводит год.

const formatter = new Intl.DateTimeFormat("en-US", { year: "numeric" });
formatter.format(date);
// "2024"

const twoDigit = new Intl.DateTimeFormat("en-US", { year: "2-digit" });
twoDigit.format(date);
// "24"

Опция month выводит месяц в разных форматах.

const numeric = new Intl.DateTimeFormat("en-US", { month: "numeric" });
numeric.format(date);
// "1"

const twoDigit = new Intl.DateTimeFormat("en-US", { month: "2-digit" });
twoDigit.format(date);
// "01"

const long = new Intl.DateTimeFormat("en-US", { month: "long" });
long.format(date);
// "January"

const short = new Intl.DateTimeFormat("en-US", { month: "short" });
short.format(date);
// "Jan"

const narrow = new Intl.DateTimeFormat("en-US", { month: "narrow" });
narrow.format(date);
// "J"

Опция day выводит день месяца.

const formatter = new Intl.DateTimeFormat("en-US", { day: "numeric" });
formatter.format(date);
// "15"

const twoDigit = new Intl.DateTimeFormat("en-US", { day: "2-digit" });
twoDigit.format(date);
// "15"

Опция weekday выводит день недели.

const long = new Intl.DateTimeFormat("en-US", { weekday: "long" });
long.format(date);
// "Monday"

const short = new Intl.DateTimeFormat("en-US", { weekday: "short" });
short.format(date);
// "Mon"

const narrow = new Intl.DateTimeFormat("en-US", { weekday: "narrow" });
narrow.format(date);
// "M"

Комбинируйте разные параметры компонентов, чтобы создавать свои форматы дат.

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

formatter.format(date);
// "Monday, January 15, 2024"

Браузер сам расставляет пробелы и знаки препинания по правилам выбранной локали.

Форматирование времени

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

const date = new Date(2024, 0, 15, 14, 30, 45);

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

formatter.format(date);
// "2:30:45 PM"

Параметр hour12 отвечает за 12- или 24-часовой формат времени.

const hour12 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  hour12: true
});
hour12.format(date);
// "2:30 PM"

const hour24 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  hour12: false
});
hour24.format(date);
// "14:30"

Параметр hourCycle позволяет точнее настроить отображение часов. Есть четыре варианта.

// h11: 0-11
const h11 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h11"
});
h11.format(new Date(2024, 0, 15, 0, 30));
// "0:30 AM"

// h12: 1-12
const h12 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h12"
});
h12.format(new Date(2024, 0, 15, 0, 30));
// "12:30 AM"

// h23: 0-23
const h23 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h23"
});
h23.format(new Date(2024, 0, 15, 0, 30));
// "0:30"

// h24: 1-24
const h24 = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  hourCycle: "h24"
});
h24.format(new Date(2024, 0, 15, 0, 30));
// "24:30"

Разница важна в полночь. Цикл h11 использует 0, а h12 — 12. Аналогично, h23 — 0, а h24 — 24.

Параметр dayPeriod добавляет обозначения периодов для 12-часового формата.

const morning = new Date(2024, 0, 15, 10, 30);
const afternoon = new Date(2024, 0, 15, 14, 30);
const night = new Date(2024, 0, 15, 22, 30);

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

formatter.format(morning);
// "10:30 in the morning"

formatter.format(afternoon);
// "2:30 in the afternoon"

formatter.format(night);
// "10:30 at night"

Параметр dayPeriod работает только с 12-часовым форматом.

Параметр fractionalSecondDigits показывает доли секунды.

const date = new Date(2024, 0, 15, 14, 30, 45, 123);

const oneDigit = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 1
});
oneDigit.format(date);
// "2:30:45.1 PM"

const threeDigits = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  second: "numeric",
  fractionalSecondDigits: 3
});
threeDigits.format(date);
// "2:30:45.123 PM"

Можно указать одну, две или три дробные цифры.

Работа с часовыми поясами

Параметр timeZone переводит даты в нужный часовой пояс.

const date = new Date("2024-01-15T14:30:00Z"); // UTC time

const newYork = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "full",
  timeStyle: "long"
});
newYork.format(date);
// "Monday, January 15, 2024 at 9:30:00 AM EST"

const tokyo = new Intl.DateTimeFormat("ja-JP", {
  timeZone: "Asia/Tokyo",
  dateStyle: "full",
  timeStyle: "long"
});
tokyo.format(date);
// "2024年1月15日月曜日 23:30:00 日本標準時"

const london = new Intl.DateTimeFormat("en-GB", {
  timeZone: "Europe/London",
  dateStyle: "full",
  timeStyle: "long"
});
london.format(date);
// "Monday, 15 January 2024 at 14:30:00 GMT"

Один и тот же момент времени отображается по-разному в зависимости от пояса. Используйте идентификаторы IANA, например America/New_York, Europe/London или Asia/Tokyo.

Параметр timeZoneName показывает название часового пояса.

const formatter = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "short"
});
formatter.format(date);
// "9:30 AM EST"

const long = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "long"
});
long.format(date);
// "9:30 AM Eastern Standard Time"

const shortOffset = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "shortOffset"
});
shortOffset.format(date);
// "9:30 AM GMT-5"

const longOffset = new Intl.DateTimeFormat("en-US", {
  hour: "numeric",
  minute: "numeric",
  timeZone: "America/New_York",
  timeZoneName: "longOffset"
});
longOffset.format(date);
// "9:30 AM GMT-05:00"

Разные значения timeZoneName дают разную детализацию.

Календарные системы и системы счисления

Опция calendar поддерживает негригорианские календари.

const date = new Date(2024, 0, 15);

const gregorian = new Intl.DateTimeFormat("en-US", {
  calendar: "gregory",
  year: "numeric",
  month: "long",
  day: "numeric"
});
gregorian.format(date);
// "January 15, 2024"

const japanese = new Intl.DateTimeFormat("ja-JP", {
  calendar: "japanese",
  year: "numeric",
  month: "long",
  day: "numeric"
});
japanese.format(date);
// "令和6年1月15日"

const islamic = new Intl.DateTimeFormat("ar-SA", {
  calendar: "islamic",
  year: "numeric",
  month: "long",
  day: "numeric"
});
islamic.format(date);
// "٦ رجب ١٤٤٥"

const chinese = new Intl.DateTimeFormat("zh-CN", {
  calendar: "chinese",
  year: "numeric",
  month: "long",
  day: "numeric"
});
chinese.format(date);
// "2023甲辰年腊月初五"

Одна и та же дата может отображаться по-разному в разных календарных системах. Доступные календари включают gregory, japanese, islamic, islamic-umalqura, islamic-tbla, islamic-civil, islamic-rgsa, chinese, hebrew, indian, persian и другие.

Опция numberingSystem позволяет отображать цифры в разных системах письма.

const date = new Date(2024, 0, 15);

const western = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "latn",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
western.format(date);
// "1/15/2024"

const arabic = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "arab",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
arabic.format(date);
// "١‏/١٥‏/٢٠٢٤"

const devanagari = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "deva",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
devanagari.format(date);
// "१/१५/२०२४"

const bengali = new Intl.DateTimeFormat("en-US", {
  numberingSystem: "beng",
  year: "numeric",
  month: "numeric",
  day: "numeric"
});
bengali.format(date);
// "১/১৫/২০২৪"

Доступные системы счисления включают latn (западные цифры), arab (арабско-индийские цифры), arabext (расширенные арабско-индийские цифры), beng (бенгальские цифры), deva (деванагари), и многие другие.

Форматирование диапазонов дат

Метод formatRange() форматирует диапазон дат с умным опущением повторяющейся информации.

const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);

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

formatter.formatRange(start, end);
// "January 15 – 20, 2024"

Форматтер опускает повторяющуюся информацию. Обе даты находятся в январе 2024 года, поэтому месяц и год выводятся только один раз.

Если диапазон охватывает разные месяцы, оба месяца отображаются.

const start = new Date(2024, 0, 15);
const end = new Date(2024, 1, 20);

formatter.formatRange(start, end);
// "January 15 – February 20, 2024"

Если диапазон охватывает разные годы, отображается вся информация.

const start = new Date(2024, 0, 15);
const end = new Date(2025, 1, 20);

formatter.formatRange(start, end);
// "January 15, 2024 – February 20, 2025"

Метод formatRange() работает и с диапазонами времени.

const start = new Date(2024, 0, 15, 14, 30);
const end = new Date(2024, 0, 15, 16, 45);

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

formatter.formatRange(start, end);
// "January 15, 2:30 – 4:45 PM"

Дата отображается один раз, так как оба времени относятся к одному дню.

Доступ к отформатированным частям

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

const date = new Date(2024, 0, 15, 14, 30);

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

const parts = formatter.formatToParts(date);

Каждый объект в массиве содержит свойства type и value.

[
  { type: "month", value: "January" },
  { type: "literal", value: " " },
  { type: "day", value: "15" },
  { type: "literal", value: ", " },
  { type: "year", value: "2024" },
  { type: "literal", value: " at " },
  { type: "hour", value: "2" },
  { type: "literal", value: ":" },
  { type: "minute", value: "30" },
  { type: "literal", value: " " },
  { type: "dayPeriod", value: "PM" }
]

Вы можете фильтровать и изменять эти части для создания своих форматов.

const dateParts = parts.filter(part =>
  ["month", "day", "year"].includes(part.type)
);

const dateString = dateParts.map(part => part.value).join("/");
// "January/15/2024"

Метод formatRangeToParts() предоставляет такую же функциональность для диапазонов дат.

const start = new Date(2024, 0, 15);
const end = new Date(2024, 0, 20);

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

const parts = formatter.formatRangeToParts(start, end);

Каждый объект part содержит свойство source, указывающее, относится ли он к начальной или конечной дате.

[
  { type: "month", value: "January", source: "startRange" },
  { type: "literal", value: " ", source: "startRange" },
  { type: "day", value: "15", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "day", value: "20", source: "endRange" },
  { type: "literal", value: ", ", source: "shared" },
  { type: "year", value: "2024", source: "shared" }
]

Лучшие практики

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

// Less efficient
dates.forEach(date => {
  const formatted = new Intl.DateTimeFormat("en-US").format(date);
  console.log(formatted);
});

// More efficient
const formatter = new Intl.DateTimeFormat("en-US");
dates.forEach(date => {
  const formatted = formatter.format(date);
  console.log(formatted);
});

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

const formatter = new Intl.DateTimeFormat();
// Uses browser locale automatically

Указывайте запасные локали при таргетировании на определённые регионы. Если предпочтительная локаль недоступна, браузер использует следующий вариант.

const formatter = new Intl.DateTimeFormat(["fr-CA", "fr", "en"]);
// Prefers French (Canada), falls back to French, then English

Используйте сокращённые стили для стандартных отображений даты и времени. Они автоматически подстраиваются под локальные стандарты и требуют меньше настроек.

const formatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium",
  timeStyle: "short"
});

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

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

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

const formatter = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York",
  dateStyle: "full",
  timeStyle: "long"
});

Используйте formatRange() для диапазонов дат вместо форматирования каждой даты по отдельности и их объединения. Этот метод сам убирает лишнюю повторяющуюся информацию.

// Less clear
const startFormatted = formatter.format(start);
const endFormatted = formatter.format(end);
const range = `${startFormatted} to ${endFormatted}`;

// Better
const range = formatter.formatRange(start, end);

Проверьте совместимость браузеров для продвинутых функций, таких как dayPeriod, fractionalSecondDigits и некоторых значений timeZoneName. Все современные браузеры поддерживают основную функциональность, но для новых опций могут понадобиться запасные решения для старых браузеров.