Дата и время в JavaScript: объект Date без боли

Содержание

О проверке кода. Примеры не запускались — в отличие от материалов по Node.js с фактическим выводом. Говорим прямо, а не ставим бейдж «проверено».

Объект Date — самая критикуемая часть JavaScript: нумерация месяцев с нуля, путаница таймзон, неудобный API. Но базовые задачи он решает, если знать ловушки.

Создание даты

new Date();                       // текущий момент
new Date('2026-07-17');           // из ISO-строки (рекомендуется)
new Date('2026-07-17T14:30:00');  // с временем
new Date(2026, 6, 17);            // ⚠️ год, месяц (6 = ИЮЛЬ!), день
new Date(1752761400000);          // из timestamp (миллисекунды)

Date.now();                       // timestamp текущего момента — просто число

Самый надёжный способ — ISO-строка '2026-07-17'. Конструктор с числами new Date(2026, 6, 17) опасен из-за месяцев с нуля (об этом ниже). Парсинг произвольных строк вроде '17.07.2026' ненадёжен и зависит от браузера — не используйте.

Главная ловушка: месяцы с нуля

const d = new Date(2026, 6, 17);   // это 17 ИЮЛЯ, а не июня!
d.getMonth();                       // 6 — июль, потому что январь = 0
d.getDate();                        // 17 — а вот дни считаются с 1

new Date(2026, 0, 1);   // 1 января
new Date(2026, 11, 31); // 31 декабря

Месяцы в Date нумеруются с нуля: январь — 0, декабрь — 11. А дни месяца — с 1. Эта непоследовательность порождает ошибки на единицу в каждом проекте. Правило: увидели число месяца в конструкторе или getMonth — вычтите или прибавьте единицу в уме. Ещё одна причина предпочесть ISO-строки, где месяц пишется по-человечески.

Получение частей даты

const d = new Date('2026-07-17T14:30:45');

d.getFullYear();   // 2026  (getYear устарел — не используйте)
d.getMonth();      // 6     (июль, с нуля!)
d.getDate();       // 17    (день месяца)
d.getDay();        // 5     (день недели: 0 = воскресенье)
d.getHours();      // 14
d.getMinutes();    // 30
d.getTime();       // timestamp в миллисекундах

Осторожно с двумя похожими: getDate()число месяца (17), getDay()день недели (0–6, где 0 = воскресенье). Их постоянно путают. И всегда getFullYear(), а не устаревший getYear().

Форматирование: toLocale вместо ручной сборки

const d = new Date('2026-07-17T14:30:00');

// ❌ хрупкая ручная сборка (и месяц с нуля не забыть)
`${d.getDate()}.${d.getMonth() + 1}.${d.getFullYear()}`;   // '17.7.2026'

// ✅ локализованное форматирование
d.toLocaleDateString('ru-RU');   // '17.07.2026'
d.toLocaleString('ru-RU');       // '17.07.2026, 14:30:00'
d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' });
// '17 июля 2026 г.'

d.toISOString();   // '2026-07-17T14:30:00.000Z' — для API и хранения

toLocaleDateString/toLocaleString с локалью — правильный способ показать дату пользователю. Они сами расставят разделители, порядок и падежи по локали. Ручная сборка из getDate/getMonth хрупка и спотыкается о месяцы с нуля. Для хранения и обмена — toISOString() (стандартный машинный формат).

Разница между датами

const start = new Date('2026-07-01');
const end = new Date('2026-07-17');

const diffMs = end - start;                        // 1382400000 (миллисекунды)
const diffDays = diffMs / (1000 * 60 * 60 * 24);   // 16 дней

Вычитание дат даёт разницу в миллисекундах — при арифметике Date автоматически приводится к числу (timestamp). Дальше делят на нужную единицу: /1000 → секунды, /(1000*60) → минуты, /(1000*60*60*24) → дни. Для «прибавить 3 дня» удобнее setDate(d.getDate() + 3), который сам обрабатывает переход через границу месяца.

Timestamp — дата как число

Date.now();               // 1752761400000 — текущий момент числом
const d = new Date('2026-07-17');
d.getTime();              // timestamp этой даты

// сравнение дат — как чисел
date1.getTime() < date2.getTime();   // раньше ли date1

Timestamp — миллисекунды с 1 января 1970 (эпоха Unix). Хранить и сравнивать даты как числа удобнее, чем как объекты: сравнение, сортировка, вычисление разницы — обычная арифметика. Многие API отдают даты именно в этом формате.

Когда брать библиотеку

Встроенного Date хватает для простых задач. Но как только появляются таймзоны, разбор нестандартных форматов, сложная арифметика — берут инструмент посильнее:

  • date-fns — набор функций (format, addDays, differenceInDays), модульный и лёгкий;
  • Temporal — новый стандарт языка, который чинит исторические проблемы Date (иммутабельность, честные таймзоны, месяцы с единицы). Постепенно появляется в средах.

Правило то же, что и везде на сайте: инструмент под задачу. Для «показать дату» — встроенный Date, для календарного приложения — библиотека.


Смежные темы: числа и Math — timestamp это число; строки — форматирование; типы данных. Полный список — в уроках JavaScript.

Частые вопросы

Почему месяцы в Date считаются с нуля?
Историческое решение: getMonth и конструктор new Date(2026, 0) используют январь как 0, декабрь как 11. Это источник частых ошибок на единицу. Дни месяца при этом считаются с 1, что усиливает путаницу.
Как получить разницу между двумя датами?
Вычесть одну дату из другой — получится разница в миллисекундах: date2 - date1. Дальше делят на 1000, 60, 60, 24, чтобы получить секунды, минуты, часы или дни. Даты приводятся к числу автоматически.
Как красиво отформатировать дату?
Метод toLocaleDateString и toLocaleString с указанием локали и опций: date.toLocaleDateString(‘ru-RU’). Он сам подставит правильный порядок и разделители. Ручная сборка через getDate/getMonth хрупкая.
Что такое timestamp?
Число миллисекунд, прошедших с 1 января 1970 года (эпоха Unix). Date.now() возвращает текущий timestamp, date.getTime() — для конкретной даты. Удобно хранить и сравнивать даты как обычные числа.
Стоит ли работать с датами вручную?
Для простых операций встроенного Date хватает. Для сложной работы с таймзонами, разбором форматов и арифметикой берут библиотеку вроде date-fns или новый стандарт Temporal, который решает исторические проблемы Date.