В основе работы с датами в JavaScript лежит тип Date,
который хранит время как количество миллисекунд, прошедших с 1 января
1970 года по UTC. Такая модель обеспечивает высокую вычислительную
предсказуемость на уровне машинного времени, но накладывает ограничения
на семантику календарных операций.
Ключевой момент: вся точность ограничена миллисекундами. Более мелкие единицы (микро- и наносекунды) отсутствуют, а календарные единицы (дни, месяцы, годы) не имеют фиксированной длительности.
Библиотека Date-fns строится поверх этой модели, разделяя:
Точные вычисления в Date-fns опираются на фиксированную шкалу времени, где любая операция сводится к арифметике над миллисекундами.
Функции вида differenceIn* для малых единиц времени дают
строго детерминированный результат:
differenceInMillisecondsdifferenceInSecondsdifferenceInMinutesdifferenceInHoursВсе они работают через разницу таймстампов:
const diff = dateA.getTime() - dateB.getTime();
Затем применяется целочисленное преобразование.
Пример поведения:
differenceInSeconds,
округление вниз)Таким образом, точность сохраняется, но информация о дробной части отбрасывается.
Во всех точных функциях Date-fns используется строго определённая стратегия округления:
Math.trunc или эквивалент усечения к нулюЭто критически важно для предсказуемости:
differenceInSeconds(new Date(2000), new Date(0)) // 2
differenceInSeconds(new Date(1999), new Date(0)) // 1
Здесь видно, что граница определяется не «математической близостью», а фактом превышения порога единицы измерения.
Календарные единицы — дни, месяцы, годы — не имеют фиксированной длительности:
Поэтому Date-fns использует календарную модель приближения, где результат зависит от правил календаря, а не от абсолютной шкалы времени.
Функция differenceInDays не просто делит миллисекунды на
86 400 000, а учитывает границы календарных дней.
Это приводит к особенностям:
Пример логики:
Таким образом, differenceInDays является
приближённым календарным оператором, а не физическим
измерением времени.
Месяцы в Date-fns обрабатываются через календарную проекцию:
Это приводит к важному эффекту:
differenceInMonths(new Date(2024, 2, 1), new Date(2024, 0, 31))
Результат зависит от того, как интерпретируется «полный месяц» — как календарная единица, а не фиксированный интервал.
Функции add и sub работают по календарным
правилам:
addDays — прибавляет фиксированное количество суток
(через миллисекунды)addMonths — изменяет календарный месяцaddYears — изменяет годовой компонентКлючевой эффект: перенос дат при переполнении месяца
add(new Date(2024, 0, 31), { months: 1 })
// результат: 29 февраля или 28 февраля (в зависимости от года)
Это не арифметика времени, а календарная нормализация.
При добавлении месяцев возникает правило:
Это поведение делает операции предсказуемыми с точки зрения календаря, но непредсказуемыми с точки зрения фиксированных интервалов.
Функция intervalToDuration возвращает структуру:
Она не опирается на единый масштаб времени, а последовательно «вычитает» компоненты календаря.
Алгоритм:
Это делает результат удобным для отображения, но не пригодным для математических операций.
В Date-fns существует фундаментальное разделение:
Точные (absolute):
Приближённые (calendar-based):
Проблема возникает при смешивании подходов:
Типичные ошибки:
getTime() для календарных расчётовПример проблемного подхода:
const days = (end - start) / 86400000;
Этот способ корректен только в идеальных условиях без переходов времени и DST.
differenceInSeconds — для логики событийdifferenceInDays — для пользовательского
отображенияdifferenceInMonths — для подписок и биллингаeachDayOfInterval({ start, end })
Этот подход избегает ручного расчёта и учитывает календарные особенности.
Date-fns использует строгие правила включения границ:
Это важно при:
Ошибки возникают, когда ожидается симметричное поведение границ.
Хотя Date-fns не является time-zone библиотекой, поведение зависит от локальной системы:
startOfDayПоэтому календарные вычисления всегда контекстны.
Наиболее критическая ошибка — комбинирование моделей:
Корректный подход:
Результаты Date-fns следует интерпретировать как:
Различие между ними определяет корректность всей логики работы с датами в приложениях, где время участвует как бизнес-единица, а не как физическая величина.