В библиотеке 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();
Простейший способ определить принадлежность момента к диапазону — использовать операции сравнения:
isBeforeisAfterequalsconst 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, который вводит тип
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) — включается начало и исключается конец.
Ключевой механизм проверки принадлежности реализуется методом
contains.
const t = Instant.parse('2024-06-01T12:00:00Z');
interval.contains(t);
Возвращаемое значение — булево, отражающее, входит ли момент внутрь интервала.
Логика метода учитывает правила интервала:
InstantЭквивалент вручную написанной проверки:
!t.isBefore(start) && t.isBefore(end)
Разница заключается в том, что Interval инкапсулирует
поведение и исключает ошибки при работе с границами.
Часто данные поступают в виде локального времени с часовым поясом. В
таких случаях необходимо нормализовать значение до
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, часто используется ручная
логика на основе 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 обеспечивает её за счёт:
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));
Это формирует модель «объединения интервалов» без явного построения объединённого диапазона.
При необходимости строгой проверки используется пересечение или объединение через последовательную агрегацию.