Проверка принадлежности моменту

В библиотеке js-joda базовой единицей временной модели выступает момент (instant) и локальные представления даты и времени. Под «моментом» в строгом смысле понимается точка на временной оси, независимая от календаря и часового пояса. Для этого используется тип Instant.

import { Instant } from '@js-joda/core';

const now = Instant.now();

Instant хранит количество секунд и наносекунд относительно эпохи Unix. Это делает его удобным для точных сравнений и проверки принадлежности к временным интервалам.

Наряду с ним часто используются:

  • LocalDate — дата без времени
  • LocalDateTime — дата и время без часового пояса
  • ZonedDateTime — дата и время с часовым поясом

Однако при проверках принадлежности к моменту чаще всего всё сводится к преобразованию в Instant.

import { ZonedDateTime } from '@js-joda/core';

const zdt = ZonedDateTime.now();
const instant = zdt.toInstant();

Сравнение моментов как основа проверки принадлежности

Простейший способ определить принадлежность момента к диапазону — использовать операции сравнения:

  • isBefore
  • isAfter
  • equals
const start = Instant.parse('2024-01-01T00:00:00Z');
const end = Instant.parse('2024-12-31T23:59:59Z');
const t = Instant.parse('2024-06-01T12:00:00Z');

const isInside =
  (t.equals(start) || t.isAfter(start)) &&
  (t.equals(end) || t.isBefore(end));

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

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


Интервальная модель и js-joda-extra

Для более выразительных операций используется модуль js-joda-extra, который вводит тип Interval. Он предназначен для работы с временными диапазонами как с полноценными объектами.

import { Instant } from '@js-joda/core';
import { Interval } from '@js-joda/extra';

const start = Instant.parse('2024-01-01T00:00:00Z');
const end = Instant.parse('2024-12-31T23:59:59Z');

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

Интервал в этой модели обычно рассматривается как полуоткрытый: [start, end) — включается начало и исключается конец.


Проверка принадлежности момента через Interval.contains

Ключевой механизм проверки принадлежности реализуется методом contains.

const t = Instant.parse('2024-06-01T12:00:00Z');

interval.contains(t);

Возвращаемое значение — булево, отражающее, входит ли момент внутрь интервала.

Логика метода учитывает правила интервала:

  • начало включено
  • конец исключён
  • сравнение происходит на уровне Instant

Эквивалент вручную написанной проверки:

!t.isBefore(start) && t.isBefore(end)

Разница заключается в том, что Interval инкапсулирует поведение и исключает ошибки при работе с границами.


Работа с ZonedDateTime и LocalDateTime в проверках

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

import { ZonedDateTime } from '@js-joda/core';

const zdt = ZonedDateTime.parse('2024-06-01T12:00:00+05:00');
const instant = zdt.toInstant();

interval.contains(instant);

При использовании LocalDateTime ситуация сложнее: отсутствует информация о часовом поясе, поэтому необходимо явно задать зону:

import { ZoneId, LocalDateTime } from '@js-joda/core';

const ldt = LocalDateTime.parse('2024-06-01T12:00:00');
const zone = ZoneId.of('Europe/Moscow');

const instant = ldt.atZone(zone).toInstant();

Игнорирование временной зоны приводит к некорректной интерпретации принадлежности момента.


Полуоткрытые интервалы и пограничные случаи

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

  • момент, равный end, не принадлежит интервалу
  • момент, равный start, принадлежит интервалу
const start = Instant.parse('2024-01-01T00:00:00Z');
const end = Instant.parse('2024-01-02T00:00:00Z');

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

interval.contains(end); // false
interval.contains(start); // true

Такая модель предотвращает пересечения интервалов в точке границы.


Пересечение интервалов как расширенная проверка принадлежности

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

const a = Interval.of(
  Instant.parse('2024-01-01T00:00:00Z'),
  Instant.parse('2024-01-10T00:00:00Z')
);

const b = Interval.of(
  Instant.parse('2024-01-05T00:00:00Z'),
  Instant.parse('2024-01-15T00:00:00Z')
);

a.overlaps(b);

Метод overlaps определяет, пересекаются ли интервалы хотя бы частично. Это расширяет понятие принадлежности: момент может быть заменён на целый диапазон.


Вложенность интервалов и полная принадлежность

Для строгой проверки, что один интервал целиком входит в другой, используется encloses.

outer.encloses(inner);

Это важно в сценариях, где требуется контроль целостности временных окон, например:

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

Логически это эквивалентно проверке:

outer.contains(inner.start()) && outer.contains(inner.end().minusNanos(1))

Проверка принадлежности без Interval: ручные стратегии

Несмотря на наличие Interval, часто используется ручная логика на основе Instant.

Полное включение границ

function isBetweenInclusive(t, start, end) {
  return (t.equals(start) || t.isAfter(start)) &&
         (t.equals(end) || t.isBefore(end));
}

Полуоткрытый интервал

function isBetweenHalfOpen(t, start, end) {
  return !t.isBefore(start) && t.isBefore(end);
}

Только строгая принадлежность

function isStrictlyInside(t, start, end) {
  return t.isAfter(start) && t.isBefore(end);
}

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


Нормализация временных значений перед проверкой

Проблема принадлежности часто возникает из-за различий в точности времени. Например, Instant хранит наносекунды, тогда как внешние источники могут оперировать миллисекундами.

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

const normalized = instant.truncatedTo(ChronoUnit.MILLIS);

Это обеспечивает согласованность сравнения при хранении и вычислениях.


Сравнение принадлежности в распределённых системах

В распределённых системах важна детерминированность проверки момента. js-joda обеспечивает её за счёт:

  • неизменяемых объектов
  • строгой временной модели UTC для Instant
  • отсутствия скрытых локальных преобразований

Проверка принадлежности становится чистой функцией:

f(moment, interval) -> boolean

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


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

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

import { LocalDate } from '@js-joda/core';

const date = LocalDate.parse('2024-06-01');

const start = LocalDate.parse('2024-01-01');
const end = LocalDate.parse('2024-12-31');

const isInside =
  (!date.isBefore(start)) && date.isBefore(end.plusDays(1));

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


Ошибки при проверке принадлежности

Типичные проблемы возникают при:

  • смешивании LocalDateTime и Instant
  • игнорировании временных зон
  • использовании включительных и исключительных границ без явного соглашения
  • сравнении объектов разных типов без преобразования

Корректная модель требует унификации всех значений к одному типу перед проверкой:

const instantA = valueA.toInstant();
const instantB = valueB.toInstant();

Композиция условий принадлежности в бизнес-логике

В сложных системах проверка принадлежности становится частью составных правил:

const isValid =
  interval.contains(instant) &&
  user.isActive() &&
  !holidayInterval.contains(instant);

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


Множественные интервалы и принадлежность к набору

Иногда требуется определить принадлежность момента сразу к нескольким диапазонам:

const intervals = [i1, i2, i3];

const belongs = intervals.some(i => i.contains(instant));

Это формирует модель «объединения интервалов» без явного построения объединённого диапазона.

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