Гайды

Как посчитать количество дней между датами в JavaScript — и почему днём получается на один меньше

01

Иллюстрация: настенный календарь с чистыми листами, между двумя листами натянута нить на булавках

Ты делаешь счётчик «до отпуска осталось N дней». Отпуск 20 октября, сегодня 14-е. На пальцах — шесть дней. Код из первого ответа поисковика говорит: пять.

Ты обновляешь страницу утром, днём, вечером — всё время пять. Весь день счётчик на единицу меньше календаря. И только ровно в полночь на миг показывает шесть.

Всё дело в том, что Date в JavaScript хранит не день, а момент: дату плюс время до миллисекунды. Когда ты вычитаешь даты, ты считаешь не «дни в календаре», а «сколько раз по 24 часа поместилось». В 15:00 14 октября до полуночи 20-го помещается 5,375 суток. Math.floor честно отрезает дробь — и получается пять.

Давай посчитаем правильно. По шагам.

Что понадобится

Браузер с консолью (F12) или Node.js. Никаких библиотек — для основного решения хватит встроенного Date.

Пошагово

  1. Реши, что ты считаешь. Есть два разных вопроса. «Сколько календарных дней до 20-го» — для отпусков, дедлайнов, стриков. «Сколько прошло полных суток» — для таймеров и подписок по часам. Почти всегда тебе нужен первый. Дальше — про него.

  2. Отрежь время. Нам нужны только год, месяц и день. Берём их у даты и собираем новую дату на полночь:

    const today = new Date()
    const y = today.getFullYear(), m = today.getMonth(), d = today.getDate()
    
  3. Собери полночь в UTC, а не в местном времени. Тут спрятана вторая ловушка. Во многих странах есть переход на летнее время. В день перехода сутки длятся 23 или 25 часов. Если вычитать местные полуночи, иногда получится 0,958 суток вместо одних. Date.UTC переходов не знает: там в сутках всегда ровно 86 400 000 миллисекунд.

    function daysBetween(a, b) {
      const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate())
      const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate())
      return Math.round((utcB - utcA) / 86_400_000)
    }
    

    Разница теперь всегда целая. Math.round здесь просто страховка.

  4. Правильно читай дату из <input type="date">. Поле отдаёт строку вида "2026-10-20". Хочется написать new Date(value). Но строка без времени по стандарту читается как полночь по UTC. В Москве это 03:00, и всё хорошо. А в Нью-Йорке это вечер 19 октября — и getDate() вернёт 19. Пользователь за океаном получит лишний день. Разбирай строку руками:

    function parseDateInput(value) {
      const [y, m, d] = value.split("-").map(Number)
      return new Date(y, m - 1, d) // месяцы в JS с нуля!
    }
    

    Да, m - 1 — не опечатка. Январь в Date — это 0, декабрь — 11. Забыл вычесть — и все даты уехали на месяц вперёд. Если в поле пусто, value будет пустой строкой, а результат — Invalid Date. Вычтешь из такой даты что угодно — получишь NaN. Проверь поле до расчёта.

  5. Собери счётчик.

    const target = parseDateInput("2026-10-20")
    const left = daysBetween(new Date(), target)
    // 6 — в любой час и в любом часовом поясе
    
  6. Если браузеры позволяют — возьми Temporal. Это новый встроенный API для дат, и там «просто дата без времени» существует сама по себе:

    Temporal.PlainDate.from("2026-10-14").until("2026-10-20").days // 6
    

    Ни часов, ни поясов, ни месяцев с нуля. Его поддерживают Chrome и Edge с версии 144 (январь 2026) и Firefox с версии 139. Safari — пока нет, поэтому для iPhone нужен полифил temporal-polyfill или @js-temporal/polyfill.

Что получится

Счётчик, который показывает одно и то же число с утра до вечера и у пользователя в Москве, и в Берлине, и в Нью-Йорке. Он меняется ровно в полночь по местному времени — как и ждёт человек.

Проверь на трёх случаях, прежде чем считать задачу сделанной:

  • даты в один день, но в разное время → должно быть 0;
  • 31 декабря → 1 января → 1;
  • если ты не в России — даты по обе стороны перехода на летнее время → без дробей.

А как прибавить к дате месяц?

Осторожно. setMonth(getMonth() + 1) от 31 января даст 3 марта: в феврале нет 31-го, и лишние дни перетекают дальше. Temporal и библиотеки вроде date-fns в таком случае прижимают дату к последнему дню месяца — 28 февраля. Обычно ты хочешь именно этого.

Почему у ИИ-кода тот же баг?

Потому что в интернете больше всего примеров Math.floor((b - a) / 86400000). Модель повторяет самый частый вариант. Попроси прямо: «считай календарные дни через Date.UTC, без учёта времени». Хорошо помогает, когда ты пишешь промпт с готовыми тестами из списка выше.

Нужна ли библиотека для дат?

Для разницы в днях — нет, хватит функции выше. Библиотека окупается, когда появляются повторяющиеся события, разные часовые пояса у пользователей и форматирование на десяти языках. До тех пор это лишний вес.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →