Определение рабочих дней

Рабочие дни в JavaScript-проектах обычно трактуются как календарные дни без выходных (суббота и воскресенье) и, в более сложных сценариях, без государственных праздников. В библиотеке Date-fns базовая поддержка рабочих дней реализована через набор функций, ориентированных на вычисления дат с исключением выходных.

Ключевая идея заключается в том, что операции добавления, вычитания и сравнения дат могут учитывать только те дни, которые удовлетворяют условию «не выходной».


Определение выходных дней

Основой для вычисления рабочих дней служит функция определения выходных:

import { isWeekend } from 'date-fns';

isWeekend(new Date(2026, 0, 10));

Функция isWeekend(date) возвращает true, если дата попадает на субботу или воскресенье.

Логика внутри основана на значении дня недели:

  • 0 — воскресенье
  • 6 — суббота

Таким образом, любой алгоритм определения рабочих дней обычно строится как отрицание этой функции.

const isBusinessDay = (date) => !isWeekend(date);

Такой подход используется как базовый слой для более сложных операций.


Добавление рабочих дней

Для корректного смещения даты с учётом только рабочих дней используется функция:

import { addBusinessDays } from 'date-fns';

const result = addBusinessDays(new Date(2026, 0, 1), 5);

addBusinessDays(date, amount) последовательно увеличивает дату, пропуская выходные дни.

Особенности поведения:

  • выходные автоматически исключаются из расчёта
  • отрицательное значение amount не поддерживается (для этого используется subBusinessDays)
  • исходная дата не изменяется

Пример:

import { addBusinessDays } from 'date-fns';

const start = new Date(2026, 0, 9); // пятница
const end = addBusinessDays(start, 1); // следующий рабочий день

console.log(end);

Если следующий день — суббота, функция перескочит сразу на понедельник.


Вычитание рабочих дней

Обратная операция выполняется через subBusinessDays:

import { subBusinessDays } from 'date-fns';

const result = subBusinessDays(new Date(2026, 0, 12), 3);

Функция движется назад по календарю, игнорируя выходные.

Алгоритмически она зеркальна addBusinessDays, но направление смещения противоположное.


Разница между рабочими днями

Для вычисления количества рабочих дней между датами используется:

import { differenceInBusinessDays } from 'date-fns';

const diff = differenceInBusinessDays(
  new Date(2026, 0, 20),
  new Date(2026, 0, 1)
);

Функция возвращает число полных рабочих дней между датами.

Особенности:

  • не учитывает время суток, только календарные дни
  • исключает субботы и воскресенья
  • возвращает отрицательное значение, если вторая дата позже первой

Пример интерпретации:

differenceInBusinessDays(new Date(2026, 0, 5), new Date(2026, 0, 1));

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


Построение собственного определения рабочего дня

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

Базовая расширяемая модель:

import { isWeekend, isSameDay } from 'date-fns';

const holidays = [
  new Date(2026, 0, 1),
  new Date(2026, 4, 9)
];

const isBusinessDay = (date) => {
  const weekend = isWeekend(date);
  const holiday = holidays.some(h => isSameDay(h, date));

  return !weekend && !holiday;
};

Такой подход позволяет внедрить кастомную бизнес-логику без изменения базовых функций Date-fns.


Перебор интервала рабочих дней

Для анализа диапазона дат часто используется комбинация eachDayOfInterval и фильтрации:

import { eachDayOfInterval, isWeekend } from 'date-fns';

const range = eachDayOfInterval({
  start: new Date(2026, 0, 1),
  end: new Date(2026, 0, 15)
});

const businessDays = range.filter(date => !isWeekend(date));

Этот метод полезен, когда требуется:

  • построение графиков загрузки
  • расчет календарей задач
  • формирование расписаний

При добавлении праздников фильтрация расширяется:

const businessDays = range.filter(date =>
  !isWeekend(date) &&
  !holidays.some(h => isSameDay(h, date))
);

Математическая модель рабочих дней

Расчёт рабочих дней можно рассматривать как функцию:

B(d_1, d_2) = _{d=d_1}^{d_2} f(d)

где:

  • ( f(d) = 1 ), если день является рабочим
  • ( f(d) = 0 ), если день является выходным или праздничным

Такая модель позволяет формализовать любую бизнес-логику, расширяя условие функции ( f(d) ).


Особенности временных зон и границ суток

Date-fns оперирует объектами Date, которые зависят от локального времени окружения выполнения.

При работе с рабочими днями возникают типичные нюансы:

  • дата с временем 23:59 может относиться к другому дню в UTC
  • сравнение через isSameDay зависит от локальной зоны
  • при серверных вычислениях возможны расхождения с клиентом

Для минимизации ошибок применяется нормализация:

const normalized = new Date(date.getFullYear(), date.getMonth(), date.getDate());

Такой подход фиксирует дату на уровне календарного дня.


Сложные сценарии вычисления рабочих дней

В прикладных системах часто требуется комбинация нескольких функций:

  • добавление рабочих дней
  • проверка диапазонов
  • исключение праздников
  • учет региональных календарей

Пример комбинированной логики:

import { addDays, isWeekend } from 'date-fns';

const addCustomBusinessDays = (date, amount, holidays = []) => {
  let result = new Date(date);
  let added = 0;

  while (added < amount) {
    result = addDays(result, 1);

    const isHoliday = holidays.some(h => isSameDay(h, result));
    if (!isWeekend(result) && !isHoliday) {
      added++;
    }
  }

  return result;
};

Такой алгоритм используется, когда стандартные функции Date-fns недостаточны.


Производительность при больших диапазонах

При обработке больших интервалов дат важно учитывать сложность операций:

  • eachDayOfInterval создаёт массив всех дней диапазона
  • фильтрация увеличивает нагрузку O(n)
  • циклическое добавление рабочих дней может быть медленным при больших значениях

Оптимизация достигается через:

  • предварительное кэширование праздников
  • использование Set вместо массива
  • минимизацию повторных вычислений isSameDay
const holidaySet = new Set(holidays.map(d => d.getTime()));

const isHoliday = (date) => holidaySet.has(date.getTime());

Итеративная модель смещения рабочих дней

Внутренний принцип большинства функций Date-fns для бизнес-дней основан на последовательной итерации:

  1. смещение даты на 1 день
  2. проверка условия выходного
  3. при необходимости — пропуск дня
  4. повтор до достижения цели

Эта модель гарантирует корректность независимо от календарных аномалий, таких как:

  • переходы месяцев
  • високосные годы
  • различная длина месяцев

Расширение модели под локальные календари

В корпоративных системах часто требуется поддержка:

  • региональных праздничных календарей
  • сокращённых рабочих недель
  • сменных графиков

Абстракция функции проверки дня позволяет расширять поведение:

const createBusinessDayChecker = (holidays, extraRules) => {
  const holidaySet = new Set(holidays.map(d => d.getTime()));

  return (date) => {
    const isHoliday = holidaySet.has(date.getTime());
    const isWeekendDay = isWeekend(date);

    return !isHoliday && !isWeekendDay && extraRules(date);
  };
};

Такой подход делает вычисление рабочих дней полностью конфигурируемым.