Работа с временем в тестах требует строгой детерминированности: любые зависимости от системного времени, локали или часового пояса способны приводить к нестабильным результатам. В контексте Day.js это особенно важно, поскольку библиотека активно использует системные настройки окружения, если явно не заданы правила преобразования времени.
Day.js сам по себе не хранит состояние времени, но при подключении
плагинов utc и timezone поведение начинает
зависеть от IANA-зон и конфигурации среды выполнения.
Перед тестированием временных сценариев необходимо зафиксировать среду выполнения. Основные источники нестабильности:
process.env.TZ)Для Node.js ключевым инструментом является переменная окружения:
TZ=UTC jest
или в конфигурации npm-скрипта:
{
"scripts": {
"test": "TZ=UTC jest"
}
}
Это позволяет унифицировать поведение Date и Day.js в
разных окружениях.
Для корректной работы с часовыми поясами в Day.js используются два основных плагина:
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
dayjs.extend(utc)
dayjs.extend(timezone)
Плагин utc обеспечивает нормализацию времени, а
timezone добавляет поддержку IANA-зон и методов
tz.
Для предсказуемых тестов важно контролировать текущее время. В Jest и аналогичных фреймворках используется подмена системного времени.
beforeAll(() => {
jest.useFakeTimers()
jest.setSystemTime(new Date('2025-01-01T12:00:00Z'))
})
afterAll(() => {
jest.useRealTimers()
})
После этого все вызовы dayjs() будут возвращать
фиксированную точку отсчёта.
Основной сценарий — проверка конвертации времени между зонами.
const date = dayjs.tz('2025-01-01 12:00', 'UTC')
const tokyo = date.tz('Asia/Tokyo')
const berlin = date.tz('Europe/Berlin')
expect(tokyo.format()).toBe('2025-01-01T21:00:00+09:00')
expect(berlin.format()).toBe('2025-01-01T13:00:00+01:00')
Здесь важно понимать, что Day.js хранит момент времени в UTC, а преобразование зоны влияет только на отображение.
При отсутствии явного tz Day.js использует системный
часовой пояс:
const local = dayjs('2025-01-01T12:00:00')
console.log(local.format())
Для тестов такое поведение нежелательно, поэтому фиксируют окружение:
process.env.TZ = 'UTC'
Альтернативно можно принудительно задавать зону:
dayjs.tz.setDefault('UTC')
Одной из сложных проблем является переход на летнее время. Day.js корректно учитывает DST через IANA-базы, но тесты должны явно фиксировать критические даты.
Пример проверки перехода:
const beforeDST = dayjs.tz('2025-03-30 01:30', 'Europe/Berlin')
const afterDST = dayjs.tz('2025-03-30 03:30', 'Europe/Berlin')
expect(afterDST.diff(beforeDST, 'hour')).toBe(1)
Важно не использовать локальное время без явного указания зоны, иначе результат может зависеть от окружения.
При параллельном запуске тестов возможны конфликты глобальных настроек Day.js. Особенно это касается:
dayjs.tz.setDefaultprocess.env.TZРекомендуется изолировать изменения:
const originalTZ = process.env.TZ
beforeEach(() => {
process.env.TZ = 'UTC'
})
afterEach(() => {
process.env.TZ = originalTZ
})
Day.js не рекомендуется сравнивать напрямую через строки формата. Вместо этого используется сравнение Unix timestamp:
const a = dayjs.tz('2025-01-01 00:00', 'UTC')
const b = dayjs.tz('2025-01-01 03:00', 'Europe/Moscow')
expect(a.valueOf()).toBe(b.valueOf())
Это гарантирует, что сравнение не зависит от формата вывода.
Форматирование зависит от активной зоны:
const date = dayjs.tz('2025-01-01 12:00', 'UTC')
expect(date.tz('Asia/Tokyo').format('YYYY-MM-DD HH:mm'))
.toBe('2025-01-01 21:00')
expect(date.tz('America/New_York').format('YYYY-MM-DD HH:mm'))
.toBe('2025-01-01 07:00')
При этом важно избегать смешивания локального dayjs() и
dayjs.tz() в одном тесте без явной необходимости.
Snapshot-тесты полезны для фиксации сложных временных сценариев:
const result = dayjs.tz('2025-06-01 10:00', 'Europe/Paris')
.tz('Asia/Dubai')
.format()
expect(result).toMatchSnapshot()
Однако такие тесты чувствительны к изменению IANA-баз, поэтому их следует использовать только для стабильных зон.
Частые проблемы:
process.env.TZDate.now() без мокированияutc и
timezoneИногда требуется создать полностью изолированную конфигурацию Day.js:
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
const testDayjs = dayjs
testDayjs.extend(utc)
testDayjs.extend(timezone)
testDayjs.tz.setDefault('UTC')
Такой подход предотвращает влияние глобальных изменений на другие тесты.
В реальных приложениях часовые пояса часто используются для:
Пример теста бизнес-логики:
function isExpired(dateString, zone) {
return dayjs.tz(dateString, zone).isBefore(dayjs())
}
jest.setSystemTime(new Date('2025-01-01T12:00:00Z'))
expect(isExpired('2024-12-31 23:59', 'UTC')).toBe(true)
В CI-средах часто встречаются отличия:
Поэтому стандартной практикой становится:
TZ=UTC NODE_ENV=test jest --runInBand
и обязательное использование UTC в тестовой среде.