Библиотека строится вокруг набора чистых, атомарных операций над датами. Такой подход упрощает создание собственных функций, которые комбинируют базовые примитивы в более сложные бизнес-операции без нарушения предсказуемости поведения.
Основой расширения служат несколько устойчивых принципов:
Чистота функций
Иммутабельность
DateКомпозиционность
Детерминированность
Эти принципы позволяют создавать предсказуемые и масштабируемые утилиты поверх date-fns.
Любая пользовательская функция обычно начинается с комбинации стандартных операций:
addDayssubDaysisWeekendformatdifferenceInDaysstartOfDay, 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;
}
Функция не модифицирует входной объект и строится исключительно на примитивах библиотеки.
Обёртки позволяют стандартизировать поведение и расширять функциональность без изменения исходной логики.
Типовой пример — нормализация входных данных:
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)
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;
}
Часто выделяются три уровня:
Такое разделение обеспечивает устойчивость архитектуры и предсказуемость поведения при масштабировании логики работы с датами.