Начало и конец интервала

В Luxon интервал представляет собой непрерывный отрезок времени, заданный двумя границами: началом и концом. Внутренне это объект, который хранит две сущности типа DateTimestart и end.

Ключевая особенность модели интервалов Luxon заключается в строгой неизменяемости границ и формальной корректности: интервал считается валидным только тогда, когда начальная точка строго предшествует конечной.

Основная идея:

  • start — момент начала интервала
  • end — момент окончания интервала
  • интервал описывает промежуток [start, end), где начало включено, а конец исключён

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


Создание интервала через DateTime

Базовый способ формирования интервала — использование двух объектов DateTime.

import { DateTime, Interval } from "luxon";

const start = DateTime.local(2026, 1, 1, 10, 0);
const end = DateTime.local(2026, 1, 1, 12, 0);

const interval = Interval.fromDateTimes(start, end);

При создании через fromDateTimes Luxon выполняет проверку:

  • если start <= end — интервал валиден
  • если start > end — интервал становится невалидным

Невалидный интервал не выбрасывает исключение, а помечается как invalid.


Доступ к границам интервала

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

interval.start;
interval.end;

Эти значения возвращаются как DateTime и сохраняют всю информацию о времени, включая:

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

Важно учитывать, что start и end не копируются при изменении интервала — любой метод, модифицирующий интервал, возвращает новый объект.


Полуоткрытая модель интервала

Luxon использует полуоткрытую модель:

[start, end)

Это означает:

  • момент start входит в интервал
  • момент end не входит в интервал

Такой подход критически важен при вычислениях пересечений и объединений интервалов.

Пример:

const a = Interval.fromDateTimes(
  DateTime.fromISO("2026-01-01T10:00"),
  DateTime.fromISO("2026-01-01T11:00")
);

const b = Interval.fromDateTimes(
  DateTime.fromISO("2026-01-01T11:00"),
  DateTime.fromISO("2026-01-01T12:00")
);

Интервалы a и b не пересекаются, несмотря на общую точку 11:00.


Проверка корректности границ

Каждый интервал имеет свойство:

interval.isValid

Если границы заданы некорректно, интервал считается невалидным.

const invalid = Interval.fromDateTimes(
  DateTime.local(2026, 1, 2),
  DateTime.local(2026, 1, 1)
);

invalid.isValid; // false

Дополнительно доступна диагностика причины:

invalid.invalidReason;
invalid.invalidExplanation;

Чаще всего встречается причина:

  • end before start

Явное создание интервала через ISO-строку

Luxon поддерживает создание интервалов из ISO-формата:

const interval = Interval.fromISO("2026-01-01T10:00/2026-01-01T12:00");

Строка содержит две границы, разделённые символом /.

Особенности:

  • обе границы парсятся как DateTime
  • сохраняется временная зона (если указана)
  • при ошибке парсинга интервал становится невалидным

Иммутабельность границ и пересоздание интервала

Любое изменение интервала приводит к созданию нового объекта.

Пример изменения начала и конца:

const interval2 = interval.set({
  start: DateTime.local(2026, 1, 1, 9, 0),
  end: DateTime.local(2026, 1, 1, 13, 0)
});

Исходный интервал при этом остаётся неизменным.

Такая модель предотвращает:

  • скрытые побочные эффекты
  • неожиданное изменение временных диапазонов
  • ошибки при параллельной обработке интервалов

Связь начала и конца с временной зоной

start и end всегда являются полноценными DateTime объектами, включая таймзону.

Это означает:

  • интервал может охватывать разные часовые зоны при некорректном формировании
  • сравнение выполняется в абсолютном времени (timestamp-based)

Пример:

const start = DateTime.fromISO("2026-01-01T10:00", { zone: "UTC" });
const end = DateTime.fromISO("2026-01-01T10:00", { zone: "Europe/Moscow" });

const interval = Interval.fromDateTimes(start, end);

Фактически end может быть позже или раньше start в абсолютном времени, несмотря на одинаковое локальное отображение.


Граничные случаи начала и конца

Равные границы

const i = Interval.fromDateTimes(
  DateTime.local(2026, 1, 1, 10, 0),
  DateTime.local(2026, 1, 1, 10, 0)
);

Результат:

  • интервал невалиден
  • нулевая длина не допускается

Микроинтервалы

Luxon допускает интервалы с минимальной разницей:

const i = Interval.fromDateTimes(
  DateTime.local(2026, 1, 1, 10, 0, 0, 0),
  DateTime.local(2026, 1, 1, 10, 0, 0, 1)
);

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


Нормализация границ

При работе с внешними источниками данных часто требуется нормализация:

  • округление времени
  • приведение к началу дня
  • устранение миллисекунд

Пример приведения к началу дня:

const start = DateTime.local(2026, 1, 1).startOf("day");
const end = DateTime.local(2026, 1, 7).endOf("day");

const interval = Interval.fromDateTimes(start, end);

startOf и endOf формируют логически выровненные границы, что важно для календарных диапазонов.


Поведение при пересечении границ

Начало и конец интервала активно участвуют в операциях пересечения:

  • overlap
  • intersection
  • union

Логика всех этих операций базируется на строгом сравнении start и end как абсолютных моментов времени.


Изменение только одной границы

Luxon позволяет менять начало или конец независимо:

const shiftedStart = interval.set({ start: DateTime.local(2026, 1, 1, 8, 0) });
const shiftedEnd = interval.set({ end: DateTime.local(2026, 1, 1, 14, 0) });

При этом проверка валидности выполняется заново:

  • если новая граница нарушает порядок — интервал становится невалидным

Влияние начала и конца на длительность

Длина интервала напрямую вычисляется из разницы:

interval.length;

Она определяется как:

  • end - start в миллисекундах

Любое изменение границ автоматически влияет на длину, но не изменяет исходные DateTime объекты.


Поведение при частично отсутствующих данных

Luxon требует наличия обеих границ. Интервал без start или end не может считаться валидным объектом.

Если данные приходят частично, создаётся невалидный интервал:

Interval.fromDateTimes(DateTime.local(2026, 1, 1));

Результат:

  • isValid === false

Сравнение начала и конца с другими интервалами

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

  • один интервал полностью слева/справа
  • частичное перекрытие
  • касание границ

Пример касания:

a.end.equals(b.start);

При полуоткрытой модели это не считается пересечением.


Значение точности при определении границ

Luxon опирается на миллисекундную точность:

  • различие даже в 1 мс делает интервалы различными
  • одинаковые ISO-строки с разной зоной могут давать разные абсолютные границы

Работа с копированием границ

При передаче start и end в другие структуры следует учитывать:

  • DateTime — иммутабелен
  • можно безопасно использовать без клонирования
  • все изменения требуют создания нового DateTime

Это упрощает управление интервалами в сложных системах планирования и расписаний