Создание собственных функций

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

Принципы построения пользовательских функций

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

Чистота функций

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

Иммутабельность

  • каждая операция возвращает новый объект Date
  • исходные значения не изменяются

Композиционность

  • сложные операции строятся из простых функций
  • результат одной функции становится входом другой

Детерминированность

  • результат зависит только от входных параметров
  • исключается зависимость от внешнего состояния, где это возможно

Эти принципы позволяют создавать предсказуемые и масштабируемые утилиты поверх date-fns.


Базовый слой: использование примитивов date-fns

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

  • addDays
  • subDays
  • isWeekend
  • format
  • differenceInDays
  • startOfDay, endOfDay

Пример базовой композиции:

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

function addWorkingDays(date, amount) {
  let result = new Date(date);
  let added = 0;

  while (added < amount) {
    result = addDays(result, 1);
    if (!isWeekend(result)) {
      added++;
    }
  }

  return result;
}

Функция не модифицирует входной объект и строится исключительно на примитивах библиотеки.


Обёртки над функциями (wrappers)

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

Типовой пример — нормализация входных данных:

import { parseISO, isValid } from "date-fns";

function safeParseDate(input) {
  const date = typeof input === "string" ? parseISO(input) : new Date(input);

  if (!isValid(date)) {
    throw new Error("Invalid date");
  }

  return date;
}

Обёртки часто применяются для:

  • валидации
  • приведения типов
  • обработки ошибок
  • логирования (в прикладных системах)

Функции-композиции

Композиция позволяет объединять несколько операций в единый поток преобразований.

Пример ручной композиции:

import { addDays, format } from "date-fns";

function formatNextWeek(date) {
  const nextWeek = addDays(date, 7);
  return format(nextWeek, "yyyy-MM-dd");
}

Более универсальный подход — создание собственного pipe:

function pipe(...fns) {
  return (value) => fns.reduce((acc, fn) => fn(acc), value);
}

Применение:

import { addDays, startOfDay, format } from "date-fns";

const formatStartOfNextWeek = pipe(
  (date) => addDays(date, 7),
  startOfDay,
  (date) => format(date, "yyyy-MM-dd")
);

Композиция упрощает тестирование и переиспользование логики.


Создание функций бизнес-логики

Поверх базовых операций date-fns часто строятся функции, отражающие предметную область.

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

import { addDays, isWeekend, differenceInDays } from "date-fns";

function countWorkingDays(start, end) {
  const total = differenceInDays(end, start);
  let count = 0;

  for (let i = 0; i <= total; i++) {
    const current = addDays(start, i);
    if (!isWeekend(current)) {
      count++;
    }
  }

  return count;
}

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

import { isWeekend } from "date-fns";

function isFullWeekendRange(start, end) {
  let current = new Date(start);

  while (current <= end) {
    if (!isWeekend(current)) {
      return false;
    }
    current = addDays(current, 1);
  }

  return true;
}

Такие функции абстрагируют повторяющиеся проверки над датами.


Работа с форматированием как слой представления

Форматирование часто отделяется в отдельный слой:

import { format } from "date-fns";

function formatForReport(date) {
  return format(date, "dd.MM.yyyy HH:mm");
}

Дополнительно можно строить наборы форматтеров:

const formatters = {
  short: (d) => format(d, "dd.MM.yyyy"),
  long: (d) => format(d, "EEEE, dd MMMM yyyy"),
  iso: (d) => format(d, "yyyy-MM-dd"),
};

Создание валидаторов дат

Валидация часто выделяется в отдельный слой логики:

import { isValid, parseISO } from "date-fns";

function assertValidDate(value) {
  const date = typeof value === "string" ? parseISO(value) : value;

  if (!isValid(date)) {
    throw new TypeError("Invalid date value");
  }

  return date;
}

Валидационные функции используются как защита входного слоя.


Параметризованные фабрики функций

Функции могут генерироваться динамически в зависимости от конфигурации:

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

function createWorkingDayAdder({ skipWeekends = true } = {}) {
  return function addWorkingDays(date, amount) {
    let result = new Date(date);
    let added = 0;

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

      if (!skipWeekends || !isWeekend(result)) {
        added++;
      }
    }

    return result;
  };
}

Такой подход полезен для:

  • конфигурируемых правил календаря
  • разных типов графиков работы
  • локальных бизнес-правил

Паттерны композиции: pipe и compose

Два базовых паттерна используются при построении цепочек преобразований.

Линейная композиция (pipe)

const pipe =
  (...fns) =>
  (value) =>
    fns.reduce((acc, fn) => fn(acc), value);

Обратная композиция (compose)

const compose =
  (...fns) =>
  (value) =>
    fns.reduceRight((acc, fn) => fn(acc), value);

Применение в датах:

import { addDays, startOfDay } from "date-fns";

const normalizeDate = pipe(
  startOfDay,
  (d) => addDays(d, 1)
);

Обработка крайних случаев

При работе с датами критичны граничные условия:

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

Пример защиты:

import { isValid } from "date-fns";

function safeAddDays(date, amount) {
  const result = new Date(date);

  if (!isValid(result)) {
    return null;
  }

  result.setDate(result.getDate() + amount);
  return result;
}

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

При росте кодовой базы функции над date-fns обычно группируются:

  • date/arithmetic.js — операции сложения и вычитания
  • date/range.js — работа с диапазонами
  • date/format.js — форматирование
  • date/business.js — прикладная логика

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


Производительность пользовательских функций

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

  • избегание повторных вычислений (isWeekend, differenceInDays)
  • минимизация создания объектов Date
  • кеширование результатов для повторяющихся входов

Пример оптимизации:

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

function countWorkingDaysFast(start, end) {
  let count = 0;
  let current = new Date(start);

  while (current <= end) {
    if (!isWeekend(current)) {
      count++;
    }
    current = addDays(current, 1);
  }

  return count;
}

Расширение через функциональные слои

Часто выделяются три уровня:

  1. Примитивы date-fns
  2. Технические обёртки (валидация, нормализация)
  3. Бизнес-функции (графики, отчёты, правила)

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