Включающие и исключающие границы

В работе с временными интервалами в Day.js ключевую роль играет корректное определение границ диапазона. Любое сравнение дат сводится к вопросу включения или исключения граничных значений, а также к выбору уровня точности: миллисекунды, секунды, дни или более крупные единицы времени.

Временной интервал задаётся двумя точками:

  • нижняя граница (start)
  • верхняя граница (end)

Поведение сравнения определяется тем, входят ли эти границы в допустимый диапазон:

  • включающая граница (inclusive) — значение считается принадлежащим интервалу
  • исключающая граница (exclusive) — значение не считается принадлежащим интервалу

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

Базовые операции сравнения дат

В Day.js базовые методы сравнения формируют основу работы с границами:

  • isBefore
  • isAfter
  • isSame
dayjs('2026-01-10').isBefore('2026-01-20'); // true
dayjs('2026-01-20').isAfter('2026-01-10');  // true
dayjs('2026-01-20').isSame('2026-01-20');   // true

Эти методы по умолчанию работают с исключающей логикой относительно равенства: совпадающие значения не считаются «до» или «после».

Исключающие границы (exclusive boundaries)

Исключающая логика означает, что границы интервала не входят в диапазон.

Пример интервала:

(10 января, 20 января)

Значения:

  • 10 января — исключено
  • 20 января — исключено
  • 11–19 января — включено

В чистом Day.js такая логика реализуется через комбинацию isAfter и isBefore:

const isInRange = (date, start, end) =>
  dayjs(date).isAfter(start) && dayjs(date).isBefore(end);

Подобный подход даёт строгие границы, полезные в финансовых расчётах, логах событий и дедлайнах.

Включающие границы (inclusive boundaries)

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

[10 января, 20 января]

Теперь:

  • 10 января — включено
  • 20 января — включено

Для реализации используются дополнительные методы из плагинов:

  • isSameOrBefore
  • isSameOrAfter
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';

dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);

const isInRange = (date, start, end) =>
  dayjs(date).isSameOrAfter(start) && dayjs(date).isSameOrBefore(end);

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

Универсальная проверка диапазона через isBetween

Наиболее компактный способ работы с границами в Day.js реализуется через плагин isBetween.

import isBetween from 'dayjs/plugin/isBetween';

dayjs.extend(isBetween);

Синтаксис:

dayjs(date).isBetween(start, end, unit, inclusivity);

Параметр inclusivity управляет типом границ:

  • () — исключающие границы
  • [] — включающие границы
  • [) — левая включена, правая исключена
  • ()] — левая исключена, правая включена

Примеры:

dayjs('2026-01-15').isBetween('2026-01-10', '2026-01-20');
// false по умолчанию (исключающие границы)

dayjs('2026-01-10').isBetween('2026-01-10', '2026-01-20', null, '[]');
// true

dayjs('2026-01-20').isBetween('2026-01-10', '2026-01-20', null, '[)');
// false

dayjs('2026-01-10').isBetween('2026-01-10', '2026-01-20', null, '[)');
// true

Нормализация границ через startOf и endOf

Частая проблема при работе с границами — неоднозначность времени внутри суток.

dayjs('2026-01-10')

фактически содержит временную компоненту (00:00:00.000), что влияет на сравнение.

Для устранения погрешностей применяются:

dayjs('2026-01-10').startOf('day')
dayjs('2026-01-10').endOf('day')

Пример диапазона дня:

const start = dayjs('2026-01-10').startOf('day');
const end = dayjs('2026-01-10').endOf('day');

Это гарантирует корректность включающих границ при сравнении событий внутри суток.

Полуинтервалы и асимметричные границы

В практических сценариях часто используются полуоткрытые интервалы:

  • [start, end) — включающая левая, исключающая правая
  • (start, end] — исключающая левая, включающая правая

Такая модель устраняет пересечения между соседними интервалами.

Пример:

const isActive = (date, start, end) =>
  dayjs(date).isSameOrAfter(start) && dayjs(date).isBefore(end);

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

Границы с единицами измерения

Точность сравнения влияет на результат не меньше самой логики включения.

dayjs('2026-01-10 10:00').isSame('2026-01-10 12:00', 'day');

При указании единицы измерения ('day', 'month', 'year') сравнение игнорирует более мелкие компоненты времени. Это фактически расширяет границы до уровня выбранной точности.

Пример диапазона по дням:

dayjs('2026-01-10 00:00').isSame(dayjs('2026-01-10 23:59'), 'day'); // true

Фильтрация наборов дат по границам

Работа с массивами дат обычно строится через комбинацию условий включения и исключения:

const dates = [
  '2026-01-09',
  '2026-01-10',
  '2026-01-15',
  '2026-01-21'
];

const filtered = dates.filter(d =>
  dayjs(d).isSameOrAfter('2026-01-10') &&
  dayjs(d).isBefore('2026-01-20')
);

Результат:

  • 2026-01-10
  • 2026-01-15

Здесь используется полуоткрытый интервал [start, end).

Граничные ошибки и точность сравнения

Основные источники некорректного поведения:

  • различие таймзон между сравниваемыми значениями
  • наличие скрытых миллисекунд
  • несогласованность startOf/endOf
  • смешивание unit-based и full precision сравнения

Пример проблемы:

dayjs('2026-01-10').isBefore('2026-01-10 00:00:00'); // false

Хотя логически ожидается равенство, фактическое время совпадает, и поведение зависит от точности интерпретации.

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

Для устойчивой работы с границами применяется нормализация:

  • приведение начала к startOf(unit)
  • приведение конца к endOf(unit)
  • фиксация единицы измерения во всех сравнениях
const start = dayjs('2026-01-10').startOf('day');
const end = dayjs('2026-01-20').endOf('day');

const isValid = (d) =>
  dayjs(d).isBetween(start, end, null, '[]');

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