Back

Отображение человекочитаемого времени в браузере

Отображение человекочитаемого времени в браузере

Ваш сервер хранит временные метки в UTC. Ваши пользователи живут в десятках часовых поясов. Разрыв между этими двумя реальностями — сырыми ISO-строками и фразами вроде «2 часа назад» — определяет, будет ли ваш интерфейс восприниматься как нативный или чужеродный.

Современные браузеры теперь обрабатывают человекочитаемое время в JavaScript без сторонних библиотек. Эта статья охватывает нативные API, к которым следует обращаться: Intl.DateTimeFormat, Intl.RelativeTimeFormat, Intl.DurationFormat и Temporal API для логики с учётом часовых поясов.

Ключевые выводы

  • Используйте Intl.DateTimeFormat для локализованных абсолютных временных меток, передавая идентификаторы часовых поясов IANA напрямую, чтобы избежать ручных вычислений смещений.
  • Используйте Intl.RelativeTimeFormat с небольшой вспомогательной функцией для создания естественных фраз вроде «вчера» или «3 часа назад».
  • Используйте Intl.DurationFormat для отображения прошедшего времени и обратного отсчёта, но проверяйте поддержку браузерами, поскольку базовая доступность появилась только в 2025 году.
  • Temporal API заменяет подверженный ошибкам объект Date явными, неизменяемыми типами — используйте его для новых проектов, полагаясь на форматтеры Intl для существующих кодовых баз.

Абсолютные временные метки с Intl.DateTimeFormat

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

const date = new Date('2026-03-15T14:30:00Z')

const formatter = new Intl.DateTimeFormat('en-US', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'America/New_York'
})

console.log(formatter.format(date)) // "Mar 15, 2026, 10:30 AM"

API принимает идентификаторы часовых поясов IANA напрямую, устраняя необходимость ручных вычислений смещений. Для форматирования в локальном времени пользователя определяйте часовой пояс автоматически:

const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone

Это возвращает значения вроде "Europe/London" или "Asia/Tokyo", которые можно передавать напрямую в форматтеры.

Относительное время с Intl.RelativeTimeFormat

Социальные ленты, уведомления и журналы активности выигрывают от относительных временных меток. Intl.RelativeTimeFormat создаёт фразы вроде «3 дня назад» или «через 2 часа» с правильной локализацией.

const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })

rtf.format(-1, 'day')    // "yesterday"
rtf.format(-3, 'hour')   // "3 hours ago"
rtf.format(2, 'week')    // "in 2 weeks"

Опция numeric: 'auto' создаёт естественный язык («yesterday») вместо буквальных чисел («1 day ago»), когда это уместно.

Вам нужно вычислить разницу самостоятельно — API форматирует значения, а не даты. Подойдёт простая вспомогательная функция:

function getRelativeTime(date) {
  const now = Date.now()
  const diffInSeconds = Math.round((date - now) / 1000)
  const units = [
    { unit: 'year', seconds: 31536000 },
    { unit: 'month', seconds: 2592000 },
    { unit: 'day', seconds: 86400 },
    { unit: 'hour', seconds: 3600 },
    { unit: 'minute', seconds: 60 },
    { unit: 'second', seconds: 1 }
  ]

  for (const { unit, seconds } of units) {
    if (Math.abs(diffInSeconds) >= seconds) {
      const value = Math.round(diffInSeconds / seconds)
      return new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
        .format(value, unit)
    }
  }
  return 'just now'
}

Значения месяцев и лет выше используют фиксированные приближения в секундах. Для календарно-точных различий с учётом переменной длины месяцев или високосных лет используйте Temporal или специализированную библиотеку для работы с датами вместо фиксированных секундных констант.

Обратите внимание, что Math.round иногда может сдвинуть значение в следующую единицу (например, 89 секунд округляется до 1 при делении на 60, выдавая «1 minute ago» вместо «89 seconds ago»). Если вам нужны более строгие границы, используйте Math.trunc.

Форматирование длительности с Intl.DurationFormat

Для прошедшего времени, обратного отсчёта или длительности видео Intl.DurationFormat обеспечивает согласованный вывод для разных локалей:

const duration = { hours: 2, minutes: 45, seconds: 30 }

const df = new Intl.DurationFormat('en', { style: 'long' })
console.log(df.format(duration)) // "2 hours, 45 minutes, 30 seconds"

const dfShort = new Intl.DurationFormat('en', { style: 'digital' })
console.log(dfShort.format(duration)) // "2:45:30"

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

Temporal API для более безопасной работы с датами

Temporal API решает давние проблемы объекта Date в JavaScript — изменяемость, путаницу с часовыми поясами и несогласованность парсинга. Temporal поддерживается в современных релизах Chromium и Firefox по состоянию на 2026 год, но определение возможностей остаётся необходимым, поскольку поддержка ещё не универсальна во всех браузерах.

if (typeof Temporal !== 'undefined') {
  const now = Temporal.Now.zonedDateTimeISO('America/Los_Angeles')
  console.log(now.toString())
}

Temporal предоставляет отдельные типы для разных концепций: Temporal.PlainDate для календарных дат, Temporal.PlainTime для времени суток и Temporal.ZonedDateTime для моментов с учётом часовых поясов. Эта явность предотвращает ошибки, которые преследуют код на основе Date.

Для новых проектов Temporal предлагает наиболее чистую основу. Для существующих кодовых баз форматтеры Intl работают безупречно с устаревшими объектами Date.

Практические рекомендации

Храните временные метки как UTC-строки в формате ISO 8601. Форматируйте их на стороне клиента, используя API Intl. Избегайте парсинга не-ISO строк дат — они ведут себя несогласованно в разных браузерах.

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

Используйте семантический HTML для доступности:

<time datetime="2026-03-15T14:30:00Z">March 15, 2026</time>

Заключение

Нативная платформа теперь обрабатывает то, что раньше требовало Moment.js или подобных библиотек. Intl.DateTimeFormat покрывает абсолютные временные метки, Intl.RelativeTimeFormat — относительные фразы, Intl.DurationFormat — прошедшее время, а Temporal API обеспечивает надёжную основу для логики дат с учётом часовых поясов. Для человекочитаемого времени в браузере эти встроенные инструменты — всё, что вам нужно.

Часто задаваемые вопросы

Нет. API только форматирует числовое значение и единицу в локализованную строку. Вы должны самостоятельно вычислить разницу между двумя датами и выбрать подходящую единицу перед передачей их в метод format. Вспомогательная функция, показанная в этой статье, является распространённым паттерном для такого вычисления.

Temporal доступен в современных релизах Chromium и Firefox по состоянию на 2026 год, но ещё не имеет универсальной поддержки во всех браузерах. Всегда используйте определение возможностей перед вызовом методов Temporal и рассмотрите полифилл, если вам нужна широкая совместимость. Для одного лишь форматирования API Intl уже имеют широкую поддержку и прекрасно работают с устаревшим объектом Date.

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

По умолчанию да. Если вы опускаете опцию timeZone, форматтер использует часовой пояс среды выполнения, который в браузере соответствует системной настройке пользователя. Вы можете получить это значение явно через Intl.DateTimeFormat().resolvedOptions().timeZone и передать его другим форматтерам или отправить на ваш сервер.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay