Утилиты для бизнес-логики

Библиотека js-joda предоставляет строгую, иммутабельную модель работы с датой и временем, где ключевую роль играют типы LocalDate, LocalDateTime, ZonedDateTime, Duration и Period. Однако в реальных бизнес-сценариях этого недостаточно: поверх базовых типов формируется слой утилит, реализующих правила предметной области — расчёт сроков, дедлайнов, рабочих дней, окон доступности и SLA.

Бизнес-логика времени почти всегда опирается на три категории операций:

  • преобразование (нормализация дат под правила системы)
  • проверка (валидация временных условий)
  • вычисление (переносы, интервалы, дедлайны)

Сравнение и предикаты как основа бизнес-правил

В js-joda сравнение дат осуществляется через методы isBefore, isAfter, isEqual. На их основе строятся предикаты бизнес-логики.

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

const today = LocalDate.now();
const deadline = LocalDate.parse('2026-06-01');

const isOverdue = today.isAfter(deadline);
const isNotStarted = today.isBefore(deadline);

В бизнес-слое такие проверки редко остаются в сыром виде. Обычно они оборачиваются в функции:

const isExpired = (currentDate, expirationDate) =>
  currentDate.isAfter(expirationDate);

const isActive = (currentDate, start, end) =>
  (currentDate.isEqual(start) || currentDate.isAfter(start)) &&
  (currentDate.isBefore(end) || currentDate.isEqual(end));

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


Работа с периодами и сроками

Для бизнес-логики критичны Period и Duration. Они разделяют два подхода:

  • Period — календарные единицы (дни, месяцы, годы)
  • Duration — абсолютное время (секунды, миллисекунды)
import { LocalDate, Period } from '@js-joda/core';

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

const end = start.plus(Period.ofMonths(3));

Типовой бизнес-кейс — срок действия подписки:

const addSubscriptionPeriod = (startDate, months) =>
  startDate.plus(Period.ofMonths(months));

В отличие от арифметики на числах, Period учитывает календарную структуру, включая разную длину месяцев и високосные годы.


Нормализация дат под бизнес-правила

Многие системы требуют приведения дат к определённым границам: начало дня, конец месяца, первый рабочий день периода.

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

const toStartOfDay = (date) =>
  LocalDateTime.of(date, LocalTime.MIDNIGHT);

const toEndOfDay = (date) =>
  LocalDateTime.of(date, LocalTime.of(23, 59, 59));

Для финансовых систем часто используется нормализация к концу месяца:

const toEndOfMonth = (date) =>
  date.withDayOfMonth(date.lengthOfMonth());

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


Утилиты для дедлайнов и SLA

В бизнес-системах часто присутствуют SLA-правила: “ответить в течение N дней”, “обработать до конца рабочего дня”.

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

const addBusinessDays = (date, days) => {
  let result = date;
  let added = 0;

  while (added < days) {
    result = result.plusDays(1);

    if (result.dayOfWeek().value() < 6) {
      added++;
    }
  }

  return result;
};

Такая функция формирует базу для расчёта сроков с учётом выходных.

SLA-расчёт:

const calculateSlaDeadline = (createdAt, slaDays) =>
  addBusinessDays(createdAt, slaDays);

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

Бизнес-логика часто оперирует интервалами: окна доставки, доступность сервисов, время приёма заявок.

const isWithinWindow = (time, start, end) =>
  (time.equals(start) || time.isAfter(start)) &&
  time.isBefore(end);

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

const isServiceAvailable = (dateTime) => {
  const hour = dateTime.hour();

  const isWorkingHours = hour >= 9 && hour < 18;
  const isWeekday = dateTime.dayOfWeek().value() <= 5;

  return isWorkingHours && isWeekday;
};

Инварианты и валидация временных данных

Бизнес-логика часто требует проверки корректности временных диапазонов:

const validateRange = (start, end) => {
  if (end.isBefore(start) || end.isEqual(start)) {
    throw new Error('Некорректный временной диапазон');
  }
};

Расширенный вариант включает контроль пересечений:

const hasOverlap = (aStart, aEnd, bStart, bEnd) =>
  aStart.isBefore(bEnd) && bStart.isBefore(aEnd);

Эти проверки используются в бронировании ресурсов, планировании задач и расписаниях.


Агрегация времени через ChronoUnit

ChronoUnit позволяет измерять разницу между датами в различных единицах:

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

const start = LocalDate.parse('2026-01-01');
const end = LocalDate.parse('2026-02-15');

const daysBetween = ChronoUnit.DAYS.between(start, end);

На основе этого строятся метрики:

const isLongRunning = (start, end) =>
  ChronoUnit.DAYS.between(start, end) > 30;

Округление и приведение к бизнес-граням

В системах отчетности часто требуется округление времени до дня, недели или месяца.

const truncateToMonth = (date) =>
  date.withDayOfMonth(1);

Для недельных отчётов:

const truncateToWeek = (date) => {
  const dayOfWeek = date.dayOfWeek().value();
  return date.minusDays(dayOfWeek - 1);
};

Такие функции формируют основу для аналитических систем.


Учет зон времени в бизнес-правилах

При работе с глобальными системами критично учитывать ZonedDateTime.

import { ZonedDateTime, ZoneId } from '@js-joda/core';

const zoned = ZonedDateTime.now(ZoneId.of('Europe/Amsterdam'));

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

const isOfficeOpen = (zonedDateTime) => {
  const hour = zonedDateTime.hour();
  return hour >= 9 && hour < 17;
};

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


Комбинирование утилит в предметные правила

На практике утилиты не используются изолированно. Они формируют цепочки правил:

const isOrderValid = (orderDate, deliveryDate) => {
  validateRange(orderDate, deliveryDate);

  const isNotTooFar =
    ChronoUnit.DAYS.between(orderDate, deliveryDate) <= 30;

  const isWeekdayOrder = orderDate.dayOfWeek().value() <= 5;

  return isNotTooFar && isWeekdayOrder;
};

Здесь объединяются:

  • проверка диапазона
  • вычисление длительности
  • календарные ограничения

Композиция функций как основа расширяемости

Бизнес-утилиты удобно строить как чистые функции:

const and = (a, b) => (x) => a(x) && b(x);

const isWeekday = (date) => date.dayOfWeek().value() <= 5;
const isNotHoliday = (date) => true;

const isWorkingDay = and(isWeekday, isNotHoliday);

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


Типовые паттерны применения утилит

  • расчёт сроков выполнения задач через plusDays и Period
  • фильтрация событий по диапазонам времени
  • проверка доступности сервисов по часам и дням недели
  • контроль пересечений интервалов
  • агрегация метрик через ChronoUnit
  • нормализация дат под отчётные периоды

Построение доменного слоя поверх js-joda

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

  • функции предикатов (валидация условий)
  • функции трансформации (нормализация времени)
  • функции расчёта (дедлайны, SLA, интервалы)
  • функции композиции (сложные правила из простых блоков)

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