Временные зоны в JavaScript: как не сойти с ума

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему время — это боль

Каждый, кто хоть раз писал код на JavaScript, сталкивался с этой болью. Вы показываете пользователю время 12:00, а он пишет: «У меня 14:00, всё сломалось». Или вы сохраняете дату в базу данных, а при загрузке она сдвигается на час назад. Знакомо?

Работа с временными зонами в JavaScript — это та тема, которую многие разработчики откладывают «на потом». А зря. Потому что если не разобраться с этим сразу, потом придётся переписывать половину приложения.

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

Почему JavaScript так странно работает с датами?

Начнём с главного: объект Date в JavaScript — это, мягко говоря, не подарок. Он хранит время в миллисекундах с 1 января 1970 года UTC. И всё бы ничего, но:

  • getMonth() возвращает месяцы от 0 до 11 (январь — это 0, декабрь — 11)
  • Методы getHours(), getMinutes() возвращают время в локальном часовом поясе пользователя
  • А методы getUTCHours(), getUTCMinutes() — в UTC

И вот тут начинается путаница. Допустим, вы пишете:

const now = new Date();
console.log(now.getHours());      // 14 — если у вас MSK
console.log(now.getUTCHours());   // 11 — UTC

Если вы сохраните now в базу данных, а затем достанетет его на сервере в другой таймзоне, получите кашу. Поэтому первое правило работы с датами и временными зонами в JavaScript — всегда храните время в UTC, а конвертируйте на клиенте.

Intl.DateTimeFormat: встроенный спаситель

Многие не знают, но в современном JavaScript есть мощный встроенный API для работы с форматированием дат с учётом таймзоны. Это Intl.DateTimeFormat. Он поддерживает все основные часовые пояса и умеет красиво форматировать даты.

Базовый пример

const date = new Date('2025-03-15T12:00:00Z');

const formatter = new Intl.DateTimeFormat('ru-RU', {
    timeZone: 'America/New_York',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit'
});

console.log(formatter.format(date)); 
// "15 марта 2025 г., 08:00" — время в Нью-Йорке

Обратите внимание: мы передаём дату в UTC (буква Z в конце), а Intl.DateTimeFormat сам конвертирует её в нужную таймзону. Это идеально для отображения времени пользователям из разных регионов.

Список всех таймзон

Если вы не знаете, какую таймзону указать, можно получить полный список:

console.log(Intl.supportedValuesOf('timeZone'));
// ["Africa/Abidjan", "Africa/Accra", ..., "Pacific/Wallis"]

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

Moment.js: классика, но с оговорками

Долгое время moment.js был стандартом для работы с датами в JavaScript. Он умеет парсить, форматировать, манипулировать датами и, конечно, работать с временными зонами через плагин moment-timezone.

Установка

npm install moment moment-timezone

Пример с таймзонами

const moment = require('moment-timezone');

const now = moment();
console.log(now.tz('Europe/Moscow').format('YYYY-MM-DD HH:mm'));
// 2025-03-15 14:00 — московское время

const nyTime = moment.tz('2025-03-15 12:00', 'America/New_York');
console.log(nyTime.clone().tz('Europe/Moscow').format('YYYY-MM-DD HH:mm'));
// 2025-03-15 19:00 — разница 7 часов

Важно: moment.js — это legacy-библиотека. Авторы сами рекомендуют переходить на более современные решения. Но если у вас старый проект — он работает, и это главное.

Luxon: современная замена moment.js

Luxon — это библиотека от одного из авторов moment.js, но без legacy-проблем. Она использует нативный Intl, работает быстрее и легче.

Установка

npm install luxon

Работа с временными зонами в Luxon

const { DateTime } = require('luxon');

const now = DateTime.now();
console.log(now.toISO()); // 2025-03-15T11:00:00.000+00:00

const inMoscow = now.setZone('Europe/Moscow');
console.log(inMoscow.toFormat('yyyy-MM-dd HH:mm')); // 2025-03-15 14:00

// Конвертация из одной таймзоны в другую
const fromNY = DateTime.fromISO('2025-03-15T12:00:00', { zone: 'America/New_York' });
const inTokyo = fromNY.setZone('Asia/Tokyo');
console.log(inTokyo.toFormat('yyyy-MM-dd HH:mm')); // 2025-03-16 01:00

Luxon умеет работать с часовыми поясами нативно, поддерживает DST (переход на летнее время) и не требует дополнительных плагинов. Это мой личный фаворит для новых проектов.

Temporal API: будущее уже близко

Если вы следите за новинками, то знаете, что в JavaScript скоро появится Temporal API — полностью переработанный API для работы с датами и временем. Он уже на стадии Stage 3 (почти готов) и доступен в виде полифила.

Пример с Temporal

// Полифил: npm install @js-temporal/polyfill
const { Temporal } = require('@js-temporal/polyfill');

const now = Temporal.Now.zonedDateTimeISO();
console.log(now.toString()); 
// 2025-03-15T14:00:00+03:00[Europe/Moscow]

// Конвертация в другую таймзону
const inLondon = now.withTimeZone('Europe/London');
console.log(inLondon.toString()); 
// 2025-03-15T11:00:00+00:00[Europe/London]

// Арифметика с учётом таймзон
const tomorrow = now.add({ days: 1 });
console.log(tomorrow.toString());
// 2025-03-16T14:00:00+03:00[Europe/Moscow]

Temporal решает все проблемы Date: он immutable, поддерживает календари, таймзоны, DST и арифметику. Когда он станет стандартом, мы забудем о moment.js и Luxon.

Блоги

Рекомендаций по книгам