Сравнение с учетом часовых поясов

Библиотека Day.js по умолчанию работает с локальным часовым поясом среды выполнения. Для корректного сравнения дат между регионами требуется подключение дополнительных плагинов:

  • utc
  • timezone
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'

dayjs.extend(utc)
dayjs.extend(timezone)

Плагин utc добавляет поддержку времени UTC, а timezone позволяет работать с часовыми поясами IANA:

'Europe/Moscow'
'Asia/Almaty'
'America/New_York'
'Europe/Berlin'

Почему сравнение дат с часовыми поясами — сложная задача

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

Пример:

const moscow = dayjs.tz('2025-05-10 12:00', 'Europe/Moscow')
const newYork = dayjs.tz('2025-05-10 12:00', 'America/New_York')

Обе даты отображают 12:00, но фактически между ними несколько часов разницы.

Проверка:

console.log(moscow.toISOString())
console.log(newYork.toISOString())

Результат:

2025-05-10T09:00:00.000Z
2025-05-10T16:00:00.000Z

Day.js сравнивает именно абсолютные временные метки, а не строковое отображение времени.


Сравнение через isBefore

Метод isBefore() определяет, произошла ли одна дата раньше другой.

const first = dayjs.tz('2025-01-01 10:00', 'Europe/Moscow')
const second = dayjs.tz('2025-01-01 10:00', 'Asia/Tokyo')

console.log(first.isBefore(second))

Несмотря на одинаковое локальное время, результат будет зависеть от фактического UTC-времени.

Внутренний механизм

Day.js приводит обе даты к Unix Timestamp:

console.log(first.valueOf())
console.log(second.valueOf())

Сравнение выполняется именно по миллисекундам.


Сравнение через isAfter

const london = dayjs.tz('2025-06-01 08:00', 'Europe/London')
const paris = dayjs.tz('2025-06-01 08:00', 'Europe/Paris')

console.log(london.isAfter(paris))

Поскольку Париж находится в другом часовом поясе, одинаковое локальное время соответствует различным UTC-моментам.


Сравнение через isSame

const utcDate = dayjs.utc('2025-01-01T12:00:00Z')

const localDate = dayjs.tz(
  '2025-01-01 15:00',
  'Europe/Moscow'
)

console.log(utcDate.isSame(localDate))

Результат:

true

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


Сравнение по единицам времени

Методы сравнения поддерживают второй аргумент — единицу измерения.

date1.isSame(date2, 'day')
date1.isBefore(date2, 'month')
date1.isAfter(date2, 'year')

Пример:

const berlin = dayjs.tz(
  '2025-07-01 23:30',
  'Europe/Berlin'
)

const tokyo = dayjs.tz(
  '2025-07-02 06:30',
  'Asia/Tokyo'
)

console.log(berlin.isSame(tokyo, 'day'))

Результат зависит от локального календарного дня каждой даты.


Нормализация к UTC перед сравнением

Наиболее безопасный способ сравнения — перевод обеих дат в UTC.

const a = dayjs.tz(
  '2025-03-10 15:00',
  'America/New_York'
).utc()

const b = dayjs.tz(
  '2025-03-10 21:00',
  'Europe/Moscow'
).utc()

console.log(a.isSame(b))

Такой подход особенно важен:

  • в серверных приложениях;
  • при работе с международными пользователями;
  • в системах бронирования;
  • в финансовых сервисах;
  • в системах логирования.

Разница между utc() и tz()

utc()

Переводит дату в UTC.

const date = dayjs().utc()

tz()

Создает дату в указанном часовом поясе.

const date = dayjs.tz(
  '2025-01-01 12:00',
  'Asia/Almaty'
)

Сравнение локального времени и UTC

const local = dayjs('2025-01-01 12:00')

const utc = dayjs.utc('2025-01-01 12:00')

console.log(local.isSame(utc))

Результат может быть false, потому что:

  • локальная дата интерпретируется в часовом поясе системы;
  • UTC-дата имеет нулевое смещение.

Использование diff() с часовыми поясами

Метод diff() вычисляет разницу между датами.

const sydney = dayjs.tz(
  '2025-05-01 10:00',
  'Australia/Sydney'
)

const toronto = dayjs.tz(
  '2025-04-30 20:00',
  'America/Toronto'
)

console.log(sydney.diff(toronto, 'hour'))

Day.js автоматически учитывает смещения.


Переход на летнее время

Одной из самых сложных проблем являются DST-переходы (Daylight Saving Time).

Пример:

const before = dayjs.tz(
  '2025-03-30 01:30',
  'Europe/Berlin'
)

const after = dayjs.tz(
  '2025-03-30 03:30',
  'Europe/Berlin'
)

console.log(after.diff(before, 'hour'))

В некоторых случаях результат будет отличаться от ожидаемого, потому что один час может быть пропущен.


Неоднозначное локальное время

Во время обратного перехода часов одинаковое локальное время может существовать дважды.

const ambiguous = dayjs.tz(
  '2025-10-26 02:30',
  'Europe/Berlin'
)

Такие ситуации особенно критичны:

  • в авиасистемах;
  • в банковских транзакциях;
  • в расписаниях;
  • в системах событий.

Сравнение дат из API

Многие API возвращают даты в UTC:

{
  "createdAt": "2025-05-01T12:00:00Z"
}

Правильная обработка:

const apiDate = dayjs.utc(data.createdAt)

const localDate = dayjs()

console.log(apiDate.isBefore(localDate))

Сравнение дат пользователей из разных стран

const user1 = dayjs.tz(
  '2025-08-01 09:00',
  'Asia/Tokyo'
)

const user2 = dayjs.tz(
  '2025-08-01 09:00',
  'America/Chicago'
)

console.log(user1.isSame(user2))

Локальное время совпадает, но реальные моменты времени различаются.


Получение текущего времени в разных часовых поясах

const london = dayjs().tz('Europe/London')
const dubai = dayjs().tz('Asia/Dubai')

console.log(london.format())
console.log(dubai.format())

Сравнение:

console.log(london.isBefore(dubai))

Установка часового пояса по умолчанию

dayjs.tz.setDefault('Europe/Moscow')

После этого:

const date = dayjs.tz('2025-01-01 10:00')

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

Сброс:

dayjs.tz.setDefault()

Проверка смещения UTC

Метод utcOffset() возвращает смещение в минутах.

const date = dayjs.tz(
  '2025-01-01 12:00',
  'Asia/Tokyo'
)

console.log(date.utcOffset())

Результат:

540

Сравнение через Unix Timestamp

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

const a = dayjs.tz(
  '2025-01-01 12:00',
  'Europe/Moscow'
)

const b = dayjs.tz(
  '2025-01-01 08:00',
  'UTC'
)

console.log(a.valueOf() === b.valueOf())

Сериализация и сравнение

При передаче данных между сервером и клиентом рекомендуется использовать ISO 8601.

const iso = dayjs().toISOString()

Пример:

2025-05-22T10:15:00.000Z

Восстановление:

const parsed = dayjs(iso)

Частые ошибки

Сравнение строк вместо дат

Неправильно:

'2025-01-01 10:00' > '2025-01-01 09:00'

Правильно:

dayjs(date1).isAfter(date2)

Игнорирование UTC

Неправильно:

const date = dayjs(apiDate)

Правильно:

const date = dayjs.utc(apiDate)

Отсутствие плагина timezone

dayjs.tz(...)

без подключения плагина вызовет ошибку.


Практический пример: международная система встреч

const meetingUtc = dayjs.utc(
  '2025-09-01T14:00:00Z'
)

const moscow = meetingUtc.tz('Europe/Moscow')
const tokyo = meetingUtc.tz('Asia/Tokyo')
const newYork = meetingUtc.tz('America/New_York')

console.log(moscow.format())
console.log(tokyo.format())
console.log(newYork.format())

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


Практический пример: проверка истечения срока

const expiresAt = dayjs.utc(
  '2025-12-31T23:59:59Z'
)

const now = dayjs.utc()

if (now.isAfter(expiresAt)) {
  console.log('Срок истек')
}

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


Практический пример: фильтрация событий

const events = [
  {
    date: dayjs.tz(
      '2025-05-01 10:00',
      'Europe/Berlin'
    )
  },
  {
    date: dayjs.tz(
      '2025-05-01 12:00',
      'Asia/Tokyo'
    )
  }
]

const now = dayjs.utc()

const futureEvents = events.filter(event =>
  event.date.utc().isAfter(now)
)

Рекомендации по работе с часовыми поясами

Хранение дат

На сервере рекомендуется хранить даты:

  • в UTC;
  • в формате ISO 8601;
  • в виде Unix Timestamp.

Отображение пользователю

Преобразование в локальный часовой пояс должно происходить только на этапе отображения.

dayjs.utc(date).tz(userTimezone)

Сравнение дат

Наиболее надежный подход:

date1.utc().isSame(date2.utc())

Работа с базами данных

Большинство СУБД поддерживают UTC:

  • PostgreSQL
  • MySQL
  • MongoDB

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


Производительность

Day.js значительно легче многих альтернатив и хорошо подходит для клиентских приложений.

Особенно важно при:

  • обработке больших массивов событий;
  • календарях;
  • таймлайнах;
  • real-time интерфейсах.

При массовом сравнении дат желательно избегать повторных преобразований:

const normalized = date.utc()

normalized.isBefore(other)
normalized.isAfter(other)

Проверка валидности даты

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

const date = dayjs(input)

console.log(date.isValid())

Некорректные даты могут привести к ошибкам логики сравнения.


Комбинирование с другими плагинами

Для расширенного сравнения часто используются:

import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
import minMax from 'dayjs/plugin/minMax'

Пример:

dayjs.extend(isSameOrAfter)

date.isSameOrAfter(otherDate)

Сравнение диапазонов времени

const start = dayjs.utc('2025-01-01')
const end = dayjs.utc('2025-12-31')

const target = dayjs.utc()

const isInside =
  target.isAfter(start) &&
  target.isBefore(end)

Сравнение времени без даты

Иногда требуется сравнивать только часы и минуты.

const first = dayjs.tz(
  '2025-01-01 09:30',
  'Europe/Moscow'
)

const second = dayjs.tz(
  '2025-07-10 09:30',
  'Europe/Moscow'
)

console.log(
  first.format('HH:mm') ===
  second.format('HH:mm')
)

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