Работа с рабочими днями

Временная модель js-joda основана на неизменяемых типах и календарной арифметике, где базовыми единицами выступают даты, время и временные интервалы без привязки к мутабельному состоянию. Рабочие дни не являются встроенной сущностью ISO-8601, поэтому их логика строится поверх LocalDate, DayOfWeek и пользовательских правил календаря.

Рабочий день определяется как дата, удовлетворяющая условиям:

  • не является выходным днём недели;
  • не входит в список праздничных дат;
  • соответствует правилам локального календаря организации или региона.

Основной инструмент работы — LocalDate, позволяющий оперировать датами без времени и временных зон.


Базовая модель дня недели и фильтрация выходных

Класс DayOfWeek задаёт стандартизированное представление дней недели от 1 (Monday) до 7 (Sunday).

import { LocalDate, DayOfWeek } from '@js-joda/core';

const date = LocalDate.parse('2026-01-24');
const dayOfWeek = date.dayOfWeek();

const isWeekend =
  dayOfWeek.equals(DayOfWeek.SATURDAY) ||
  dayOfWeek.equals(DayOfWeek.SUNDAY);

На этом уровне формируется фундамент логики рабочих дней: любая операция сводится к проверке принадлежности даты к допустимому множеству.


Проверка рабочего дня через предикат

Базовая абстракция — функция-предикат, определяющая валидность даты:

const isWorkingDay = (date) => {
  const dow = date.dayOfWeek();

  const isWeekend =
    dow.equals(DayOfWeek.SATURDAY) ||
    dow.equals(DayOfWeek.SUNDAY);

  return !isWeekend;
};

Эта функция расширяется при подключении праздничных календарей.


Расширение модели: праздничные дни

Рабочие дни в реальных системах зависят от набора фиксированных и плавающих праздников.

const holidays = new Set([
  '2026-01-01',
  '2026-03-08',
  '2026-05-01'
]);

const isHoliday = (date) =>
  holidays.has(date.toString());

const isWorkingDay = (date) =>
  !isHoliday(date) &&
  !date.dayOfWeek().equals(DayOfWeek.SATURDAY) &&
  !date.dayOfWeek().equals(DayOfWeek.SUNDAY);

Такая модель остаётся полностью детерминированной и не зависит от внешнего состояния.


Итерация по календарю: переход к следующему рабочему дню

Одной из базовых операций является поиск ближайшего рабочего дня вперёд.

const nextWorkingDay = (date) => {
  let current = date.plusDays(1);

  while (!isWorkingDay(current)) {
    current = current.plusDays(1);
  }

  return current;
};

Метод plusDays из js-joda обеспечивает неизменяемость: каждый шаг создаёт новый экземпляр LocalDate.


Обратный поиск: предыдущий рабочий день

Аналогичная логика применяется в обратном направлении:

const previousWorkingDay = (date) => {
  let current = date.minusDays(1);

  while (!isWorkingDay(current)) {
    current = current.minusDays(1);
  }

  return current;
};

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


Арифметика рабочих дней

Обычное добавление дней через plusDays не учитывает календарные ограничения. Поэтому вводится специализированная функция.

const addWorkingDays = (date, days) => {
  let current = date;
  let remaining = days;

  while (remaining > 0) {
    current = current.plusDays(1);

    if (isWorkingDay(current)) {
      remaining -= 1;
    }
  }

  return current;
};

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


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

const subtractWorkingDays = (date, days) => {
  let current = date;
  let remaining = days;

  while (remaining > 0) {
    current = current.minusDays(1);

    if (isWorkingDay(current)) {
      remaining -= 1;
    }
  }

  return current;
};

Операция симметрична добавлению, но двигается в обратном направлении по временной шкале.


Подсчёт количества рабочих дней в диапазоне

Частая задача — вычисление количества рабочих дней между двумя датами включительно или исключительно.

const countWorkingDays = (start, end) => {
  let count = 0;
  let current = start;

  while (current.isBefore(end)) {
    if (isWorkingDay(current)) {
      count += 1;
    }
    current = current.plusDays(1);
  }

  if (isWorkingDay(end)) {
    count += 1;
  }

  return count;
};

Такая модель применяется в SLA-расчётах и планировании загрузки.


Использование Temporal Adjuster в js-joda

Архитектура js-joda поддерживает концепцию TemporalAdjuster, позволяющую инкапсулировать правила преобразования дат.

const nextWorkingDayAdjuster = (date) => {
  let adjusted = date.plusDays(1);

  while (!isWorkingDay(adjusted)) {
    adjusted = adjusted.plusDays(1);
  }

  return adjusted;
};

Применение:

const result = nextWorkingDayAdjuster(LocalDate.parse('2026-01-23'));

Интеграция с js-joda-business-days

В экосистеме существует расширение, реализующее бизнес-календарь на уровне библиотеки: js-joda-business-days. Оно предоставляет готовые инструменты для работы с рабочими днями и календарными правилами.

Типовая модель включает:

  • календарь праздников;
  • правила выходных;
  • операции сложения рабочих дней;
  • временные диапазоны бизнес-операций.

Пример концептуального использования:

import { LocalDate } from '@js-joda/core';
import { BusinessDays } from 'js-joda-business-days';

const calendar = BusinessDays.of({
  holidays: ['2026-01-01', '2026-03-08'],
  weekend: [6, 7]
});

const date = LocalDate.parse('2026-01-20');

const result = calendar.plusWorkingDays(date, 5);

Абстракция библиотеки заменяет ручные циклы декларативными операциями.


Работа с диапазонами бизнес-календаря

Диапазоны используются для анализа временных интервалов.

const range = {
  start: LocalDate.parse('2026-01-01'),
  end: LocalDate.parse('2026-01-31')
};

const workingDays = [];
let current = range.start;

while (!current.isAfter(range.end)) {
  if (isWorkingDay(current)) {
    workingDays.push(current);
  }
  current = current.plusDays(1);
}

Такая структура применяется при формировании графиков работ и финансовых расчётов.


Кэширование календарных правил

При частых вычислениях рабочих дней критически важно избегать повторной проверки одинаковых дат. Распространённый подход — кэширование результата предиката.

const cache = new Map();

const isWorkingDayCached = (date) => {
  const key = date.toString();

  if (cache.has(key)) {
    return cache.get(key);
  }

  const value = isWorkingDay(date);
  cache.set(key, value);

  return value;
};

Это снижает стоимость вычислений в больших диапазонах.


Производственные сценарии применения

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

  • расчёта сроков выполнения задач;
  • финансового клиринга и settlement-процессов;
  • логистического планирования;
  • SLA-метрик и мониторинга;
  • автоматизации расписаний.

Во всех случаях js-joda обеспечивает стабильную основу временной модели, а бизнес-правила реализуются поверх LocalDate и пользовательских функций.