Работа с граничными случаями

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


Любая операция с датой в JavaScript может привести к состоянию Invalid Date. В date-fns это состояние не подавляется автоматически, поэтому проверка валидности становится обязательной частью логики.

Функция:

  • isValid(date)

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

Типичный источник ошибки — парсинг строк:

import { parse, isValid } from 'date-fns';

const date = parse('2024-13-40', 'yyyy-MM-dd', new Date());

isValid(date); // false

Особенность заключается в том, что parse не выбрасывает исключение, а возвращает объект даты даже при некорректном вводе. Это создаёт скрытый риск дальнейших вычислений.


Пограничные значения начала и конца интервалов

Работа с диапазонами требует строгого определения границ. В date-fns используются функции нормализации:

  • startOfDay
  • endOfDay
  • startOfMonth
  • endOfMonth
  • startOfYear
  • endOfYear

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

import { startOfDay, endOfDay } from 'date-fns';

const start = startOfDay(new Date());
const end = endOfDay(new Date());

endOfDay устанавливает время на 23:59:59.999, что важно учитывать при фильтрации данных. Ошибки часто появляются, когда сравнение выполняется через < и > без учета включительности границ.


Неоднозначность часовых поясов

JavaScript Date всегда хранит время в UTC, но отображение зависит от локального часового пояса среды выполнения. date-fns по умолчанию не управляет таймзонами, что создаёт типичную проблему:

  • одинаковая дата интерпретируется по-разному в разных окружениях
new Date('2024-01-01T00:00:00Z');

При локальном отображении значение может смещаться назад или вперёд относительно UTC.

Для работы с такими случаями важно разделять:

  • логическое время (UTC)
  • отображаемое локальное время

date-fns не вводит скрытых преобразований, поэтому смещение всегда остаётся явным.


Переходы летнего и зимнего времени

Daylight Saving Time (DST) создаёт неоднозначные часы, которые могут:

  • повторяться
  • пропадать

При использовании функций:

  • addHours
  • setHours
  • addDays

возможны неожиданные скачки времени.

import { addHours } from 'date-fns';

const date = new Date('2024-03-31T01:30:00');
const result = addHours(date, 2);

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

Ключевой принцип: date-fns оперирует календарной арифметикой, а не временными интервалами фиксированной длины.


Граничные переходы между месяцами и годами

При добавлении или вычитании единиц времени важно учитывать разную длину месяцев.

import { addMonths } from 'date-fns';

const date = new Date('2024-01-31');
addMonths(date, 1);

Февраль не содержит 31 дня, поэтому результат будет скорректирован автоматически:

  • либо на последний день месяца
  • либо на ближайшую допустимую дату

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


Проверка попадания в диапазон

Функция:

  • isWithinInterval(date, { start, end })

используется для проверки принадлежности к диапазону, но включает обе границы.

import { isWithinInterval } from 'date-fns';

isWithinInterval(new Date('2024-05-10'), {
  start: new Date('2024-05-01'),
  end: new Date('2024-05-10')
});

Результат будет true, что важно учитывать при построении фильтров, где требуется полуоткрытый интервал [start, end).


Сравнение дат и погрешности времени

Функции:

  • isBefore
  • isAfter
  • isEqual

работают на уровне миллисекунд.

import { isEqual } from 'date-fns';

isEqual(
  new Date('2024-01-01T00:00:00.000'),
  new Date('2024-01-01T00:00:00.500')
);

Результат будет false, даже если визуально даты кажутся одинаковыми при форматировании до секунд.

Для устранения таких расхождений применяется нормализация:

  • startOfSecond
  • startOfMinute

Граничные ошибки при округлении времени

При форматировании и округлении важно учитывать потерю точности.

Функции:

  • roundToNearestMinutes
  • startOfMinute
  • endOfMinute

могут приводить к смещению при работе с логами или событиями, записанными с миллисекундами.

Типичная проблема возникает при агрегации данных:

  • события, попавшие на границу минуты, распределяются в разные группы

Leap year и февральские крайние даты

Високосные годы создают дополнительный день, который влияет на арифметику дат.

import { addYears } from 'date-fns';

const date = new Date('2020-02-29');
addYears(date, 1);

Результат:

  • 2021-03-01

Такое поведение связано с отсутствием 29 февраля в невисокосных годах. При построении расписаний это приводит к накоплению смещения.


Парсинг строк и неоднозначные форматы

Функция:

  • parse

требует строгого соответствия шаблону:

parse('10-05-2024', 'dd-MM-yyyy', new Date());

Любое отклонение:

  • лишние символы
  • неправильный порядок
  • отсутствие ведущих нулей

приводит к частичному или полностью невалидному результату.

Особенность: parse не делает “догадок”, что отличает date-fns от встроенного Date.parse.


Обработка нулевых значений и отсутствующих дат

В прикладных системах часто встречаются null, undefined и пустые строки вместо дат. date-fns не выполняет автоматическое приведение типов.

Типичный сценарий:

format(null, 'yyyy-MM-dd');

Результат — ошибка выполнения.

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


Арифметика дат на границах суток

При добавлении дней:

import { addDays } from 'date-fns';

addDays(new Date('2024-03-31T23:30:00'), 1);

перенос времени сохраняет часы и минуты, что может привести к попаданию в следующий день с неожиданным временем.

Проблема усиливается при использовании данных, зависящих от бизнес-логики “календарного дня”, а не 24-часового интервала.


Работа с минимальными и максимальными значениями

При необходимости ограничения диапазона используются внешние подходы:

  • Math.min
  • Math.max
  • ручное сравнение через isBefore / isAfter

date-fns не предоставляет встроенного clamp-механизма, поэтому логика ограничения всегда реализуется вручную.


Особенности вычисления разницы между датами

Функции:

  • differenceInDays
  • differenceInHours
  • differenceInMinutes

работают с округлением вниз.

differenceInDays(
  new Date('2024-01-02T23:59:59'),
  new Date('2024-01-01T00:00:00')
);

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

Это поведение требует различения:

  • календарной разницы
  • точной временной разницы

Граничные случаи форматирования

Функция:

  • format

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

import { format } from 'date-fns';

format(new Date('2024-01-01T00:00:00Z'), 'yyyy-MM-dd');

Вывод может отличаться в зависимости от окружения выполнения, если не зафиксирован UTC-режим обработки данных на уровне приложения.