Работа с календарями в JavaScript требует точных операций над датами
без изменения исходных объектов и без побочных эффектов. Библиотека
Date-fns реализует этот подход через чистые функции, каждая из которых
принимает дату и возвращает новый объект Date.
Основу календарных вычислений составляют операции смещения, нормализации и сравнения дат.
Для построения календарных интерфейсов и планировщиков используются функции добавления и вычитания временных интервалов:
addDays(date, amount) — прибавление днейsubDays(date, amount) — вычитание днейaddWeeks(date, amount) — смещение по неделямaddMonths(date, amount) — смещение по месяцамТакие операции применяются для построения навигации календаря (следующий/предыдущий месяц), генерации диапазонов дат и расчёта временных окон.
Особенность календарных смещений заключается в учёте длины месяцев и переходов между годами. Например, прибавление одного месяца к 31 января корректно приводит к последнему дню февраля.
Календарные интерфейсы почти всегда оперируют периодами: неделя, месяц, год. Для их вычисления используются функции нормализации даты к началу или концу периода.
startOfDay(date) / endOfDay(date)startOfWeek(date, options) /
endOfWeek(date, options)startOfMonth(date) / endOfMonth(date)startOfYear(date) / endOfYear(date)Пример логики недели:
Параметр options.weekStartsOn позволяет управлять этим
поведением:
import { startOfWeek, endOfWeek } from 'date-fns'
startOfWeek(new Date(), { weekStartsOn: 1 })
endOfWeek(new Date(), { weekStartsOn: 1 })
Календарная сетка месяца обычно включает не только дни текущего месяца, но и дни соседних месяцев для заполнения недель.
Ключевая функция:
eachDayOfInterval({ start, end }) — генерация массива
дат между двумя границамиТипичный алгоритм построения сетки месяца:
import {
startOfMonth,
endOfMonth,
startOfWeek,
endOfWeek,
eachDayOfInterval
} from 'date-fns'
const monthStart = startOfMonth(date)
const monthEnd = endOfMonth(date)
const gridStart = startOfWeek(monthStart)
const gridEnd = endOfWeek(monthEnd)
const days = eachDayOfInterval({
start: gridStart,
end: gridEnd
})
Такой подход используется в календарных компонентах, где требуется стабильная 5–6 недельная сетка отображения.
Отображение календаря требует преобразования даты в строку. Основная функция:
format(date, formatString, options)Часто используемые форматы:
yyyy-MM-dd — машинное представлениеdd.MM.yyyy — локальное отображениеEEEE — день неделиMMMM yyyy — месяц и годimport { format } from 'date-fns'
format(new Date(), 'dd.MM.yyyy')
format(new Date(), 'MMMM yyyy')
Форматирование используется в заголовках календаря, подписях дней недели и списках событий.
Для календарей важно определять равенство дат и их принадлежность к периоду.
isSameDay(dateLeft, dateRight) — совпадение дняisSameMonth(dateLeft, dateRight) — совпадение
месяцаisSameYear(dateLeft, dateRight) — совпадение годаdifferenceInCalendarDays(dateLeft, dateRight) — разница
в днях без учёта времениimport { isSameDay, differenceInCalendarDays } from 'date-fns'
isSameDay(new Date(), anotherDate)
differenceInCalendarDays(new Date(), anotherDate)
differenceInCalendarDays особенно важна для расчёта
длительности событий и построения таймлайнов.
Для планировщиков и календарей событий применяется проверка попадания даты в интервал:
isWithinInterval(date, { start, end })import { isWithinInterval } from 'date-fns'
isWithinInterval(date, {
start: startOfMonth(date),
end: endOfMonth(date)
})
Эта операция используется при фильтрации событий по выбранному периоду.
Месячное представление строится на основе сетки недель. Каждая дата снабжается дополнительными атрибутами:
import { isSameMonth } from 'date-fns'
const enrichedDays = days.map(day => ({
date: day,
inCurrentMonth: isSameMonth(day, monthStart)
}))
Неделя строится проще:
import { startOfWeek, eachDayOfInterval, addDays } from 'date-fns'
const weekStart = startOfWeek(date)
const weekDays = eachDayOfInterval({
start: weekStart,
end: addDays(weekStart, 6)
})
Неделя используется в расписаниях, тайм-слотах и рабочих календарях.
Дневной планировщик строится на основе фиксированных шагов времени. Date-fns не предоставляет готового механизма интервалов по часам, но позволяет формировать их через добавление часов:
addHours(date, amount)startOfHour(date)import { startOfHour, addHours } from 'date-fns'
const hours = Array.from({ length: 24 }, (_, i) =>
addHours(startOfHour(day), i)
)
Событие календаря обычно имеет структуру:
Для определения пересечений используется комбинация сравнений:
const overlaps = (event, range) =>
event.start <= range.end && event.end >= range.start
Date-fns применяется для нормализации границ:
import { startOfDay, endOfDay } from 'date-fns'
const range = {
start: startOfDay(date),
end: endOfDay(date)
}
differenceInCalendarDays(end, start) — календарная
длительностьdifferenceInDays(end, start) — более точный вариант с
учётом времениimport { differenceInCalendarDays } from 'date-fns'
differenceInCalendarDays(endDate, startDate)
Это используется при отображении много-дневных событий в календарной сетке.
Date-fns не содержит встроенного механизма рецидивов, однако предоставляет базовые инструменты для их построения.
import { addDays } from 'date-fns'
const generateDaily = (start, count) =>
Array.from({ length: count }, (_, i) =>
addDays(start, i)
)
import { addWeeks } from 'date-fns'
const generateWeekly = (start, count) =>
Array.from({ length: count }, (_, i) =>
addWeeks(start, i)
)
Такие конструкции формируют основу планировщиков задач и календарей событий.
В календарных системах часто требуется привести дату к единому виду:
import { startOfDay, endOfDay, startOfHour } from 'date-fns'
startOfDay(date)
endOfDay(date)
startOfHour(date)
Эта нормализация устраняет проблемы с временными зонами и дробными значениями времени при сравнении.
Планировщики часто фильтруют события по выбранному периоду:
import { isWithinInterval } from 'date-fns'
const filterEvents = (events, range) =>
events.filter(event =>
isWithinInterval(event.start, range)
)
Для более точного охвата учитываются как начало, так и конец события:
event.start <= range.end && event.end >= range.start
Некоторые функции учитывают локаль:
import { format, startOfWeek } from 'date-fns'
import { ru } from 'date-fns/locale'
format(new Date(), 'EEEE', { locale: ru })
startOfWeek(new Date(), { locale: ru })
Локализация критична для корректного отображения календарей в разных регионах.
Архитектура календарного планировщика обычно включает:
Пример базовой структуры:
const buildPlanner = (days, events) =>
days.map(day => ({
date: day,
events: events.filter(event =>
isWithinInterval(day, {
start: startOfDay(event.start),
end: endOfDay(event.end)
})
)
}))
Такая модель используется в недельных и месячных представлениях расписаний.