Сравнение временных значений

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

В отличие от стандартного объекта Date, сравнение в Js-joda построено на неизменяемых типах и строгой типизации временных сущностей.


Основные типы временных значений

Перед сравнением необходимо понимать, какие типы данных участвуют в операции.

Тип Назначение
LocalDate Только дата
LocalTime Только время
LocalDateTime Дата и время без часового пояса
ZonedDateTime Дата и время с часовым поясом
Instant Момент времени в UTC
Duration Интервал времени
Period Интервал дат

Каждый тип поддерживает собственные методы сравнения.


Сравнение дат через equals

Метод equals() проверяет полное совпадение объектов.

const { LocalDate } = require('@js-joda/core');

const d1 = LocalDate.parse('2025-03-10');
const d2 = LocalDate.parse('2025-03-10');

console.log(d1.equals(d2)); // true

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

Метод учитывает:

  • тип объекта;
  • все внутренние поля;
  • идентичность значения.
const { LocalDate, LocalDateTime } = require('@js-joda/core');

const date = LocalDate.parse('2025-03-10');
const dateTime = LocalDateTime.parse('2025-03-10T00:00');

console.log(date.equals(dateTime)); // false

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


Проверка порядка дат

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

  • isBefore()
  • isAfter()
  • isEqual()

Метод isBefore

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

console.log(start.isBefore(end)); // true

Метод возвращает:

  • true, если объект расположен раньше;
  • false в остальных случаях.

Метод isAfter

console.log(end.isAfter(start)); // true

Метод isEqual

const a = LocalDate.parse('2025-05-01');
const b = LocalDate.parse('2025-05-01');

console.log(a.isEqual(b)); // true

Разница между equals и isEqual

Для LocalDate результат обычно одинаков.

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


Сравнение ZonedDateTime

const {
    ZonedDateTime,
    ZoneId
} = require('@js-joda/core');

const moscow = ZonedDateTime.now(ZoneId.of('Europe/Moscow'));
const london = ZonedDateTime.now(ZoneId.of('Europe/London'));

equals

equals() сравнивает:

  • локальное время;
  • дату;
  • часовой пояс;
  • смещение.
console.log(moscow.equals(london));

Результат почти всегда будет false.


Сравнение по временной линии

Метод isEqual() сравнивает реальные моменты времени.

const a = ZonedDateTime.parse(
    '2025-05-01T12:00+03:00[Europe/Moscow]'
);

const b = ZonedDateTime.parse(
    '2025-05-01T10:00+01:00[Europe/Paris]'
);

console.log(a.isEqual(b)); // true

Несмотря на разное локальное время, оба объекта указывают на один и тот же момент.


Сравнение через compareTo

Метод compareTo() возвращает число:

Значение Смысл
< 0 объект меньше
0 объекты равны
> 0 объект больше

Пример compareTo

const d1 = LocalDate.parse('2025-01-01');
const d2 = LocalDate.parse('2025-06-01');

console.log(d1.compareTo(d2)); // отрицательное число
console.log(d2.compareTo(d1)); // положительное число
console.log(d1.compareTo(d1)); // 0

Сортировка дат

compareTo() особенно полезен при сортировке массивов.

const dates = [
    LocalDate.parse('2025-12-01'),
    LocalDate.parse('2025-01-01'),
    LocalDate.parse('2025-06-01')
];

dates.sort((a, b) => a.compareTo(b));

console.log(dates);

Сравнение времени

Тип LocalTime поддерживает те же методы.

const { LocalTime } = require('@js-joda/core');

const t1 = LocalTime.parse('09:30');
const t2 = LocalTime.parse('18:45');

console.log(t1.isBefore(t2)); // true

Сравнение LocalDateTime

const { LocalDateTime } = require('@js-joda/core');

const a = LocalDateTime.parse('2025-05-10T12:00');
const b = LocalDateTime.parse('2025-05-10T14:00');

console.log(a.isBefore(b)); // true

Сравнение выполняется последовательно:

  1. дата;
  2. часы;
  3. минуты;
  4. секунды;
  5. наносекунды.

Сравнение Instant

Тип Instant предназначен для точного сравнения моментов времени.

const { Instant } = require('@js-joda/core');

const i1 = Instant.parse('2025-05-01T10:00:00Z');
const i2 = Instant.parse('2025-05-01T10:00:01Z');

console.log(i1.isBefore(i2)); // true

Почему Instant важен

Instant не зависит:

  • от локального времени;
  • от региональных настроек;
  • от переходов на летнее время;
  • от часовых поясов.

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


Преобразование к Instant

const zoned = ZonedDateTime.parse(
    '2025-05-01T12:00+03:00[Europe/Moscow]'
);

const instant = zoned.toInstant();

console.log(instant);

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

Частая задача — определить, попадает ли дата в диапазон.


Включённый диапазон

function isBetween(date, start, end) {
    return (
        !date.isBefore(start) &&
        !date.isAfter(end)
    );
}

Использование:

const date = LocalDate.parse('2025-06-15');
const start = LocalDate.parse('2025-01-01');
const end = LocalDate.parse('2025-12-31');

console.log(isBetween(date, start, end));

Исключающий диапазон

function isStrictlyBetween(date, start, end) {
    return (
        date.isAfter(start) &&
        date.isBefore(end)
    );
}

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

function overlaps(start1, end1, start2, end2) {
    return (
        !end1.isBefore(start2) &&
        !start1.isAfter(end2)
    );
}

Пример проверки пересечения

const s1 = LocalDate.parse('2025-01-01');
const e1 = LocalDate.parse('2025-03-01');

const s2 = LocalDate.parse('2025-02-01');
const e2 = LocalDate.parse('2025-04-01');

console.log(
    overlaps(s1, e1, s2, e2)
); // true

Сравнение длительностей

Тип Duration представляет временной интервал.

const { Duration } = require('@js-joda/core');

const d1 = Duration.ofHours(5);
const d2 = Duration.ofMinutes(200);

console.log(d1.compareTo(d2));

Сравнение Period

Period работает с календарными единицами.

const { Period } = require('@js-joda/core');

const p1 = Period.ofDays(10);
const p2 = Period.ofWeeks(2);

console.log(p1.equals(p2)); // false

Важная особенность

Period не нормализует значения автоматически.

Period.ofWeeks(1)
Period.ofDays(7)

Это разные объекты.


Нормализация интервалов

const period = Period.ofMonths(15);

console.log(period.normalized());

Результат:

P1Y3M

Сравнение с текущим временем

const today = LocalDate.now();
const future = LocalDate.parse('2026-01-01');

console.log(future.isAfter(today));

Проверка просроченности

function isExpired(expirationDate) {
    return expirationDate.isBefore(LocalDate.now());
}

Сравнение с учётом часовых поясов

Потенциальная ошибка

const a = LocalDateTime.parse('2025-05-01T12:00');
const b = LocalDateTime.parse('2025-05-01T12:00');

Эти объекты не содержат информации о часовом поясе.

Для распределённых систем это опасно.


Корректный подход

const utc = ZonedDateTime.parse(
    '2025-05-01T09:00Z[UTC]'
);

const berlin = ZonedDateTime.parse(
    '2025-05-01T11:00+02:00[Europe/Berlin]'
);

console.log(utc.isEqual(berlin)); // true

Сравнение наносекунд

Js-joda поддерживает наносекундную точность.

const a = LocalTime.parse('10:00:00.000000001');
const b = LocalTime.parse('10:00:00.000000002');

console.log(a.isBefore(b)); // true

Проверка совпадения даты

Иногда необходимо сравнить только календарную часть.

const a = LocalDateTime.parse('2025-05-01T10:00');
const b = LocalDateTime.parse('2025-05-01T22:00');

console.log(
    a.toLocalDate().equals(
        b.toLocalDate()
    )
);

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

console.log(
    a.toLocalTime().equals(
        b.toLocalTime()
    )
);

Использование ChronoUnit

Модуль ChronoUnit позволяет вычислять разницу между значениями.

const {
    LocalDate,
    ChronoUnit
} = require('@js-joda/core');

const start = LocalDate.parse('2025-01-01');
const end = LocalDate.parse('2025-01-15');

const days = ChronoUnit.DAYS.between(start, end);

console.log(days);

Сравнение через разницу

if (ChronoUnit.DAYS.between(a, b) > 30) {
    console.log('Больше месяца');
}

Проверка одинакового месяца

function sameMonth(a, b) {
    return (
        a.year() === b.year() &&
        a.monthValue() === b.monthValue()
    );
}

Проверка одинакового года

function sameYear(a, b) {
    return a.year() === b.year();
}

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

Js-joda хорошо работает с Map и Set.

const set = new Set();

const date = LocalDate.parse('2025-05-01');

set.add(date);

console.log(
    set.has(LocalDate.parse('2025-05-01'))
);

Особенность JavaScript

В обычном Set сравнение объектов выполняется по ссылке.

Поэтому пример выше вернёт:

false

Корректное хранение ключей

const map = new Map();

const date = LocalDate.parse('2025-05-01');

map.set(date.toString(), 'Holiday');

console.log(
    map.get('2025-05-01')
);

Типичные ошибки при сравнении

Сравнение через ==

date1 == date2

Нельзя использовать для временных объектов.


Сравнение через ===

date1 === date2

Сравниваются ссылки, а не значения.


Неправильное сравнение строк

'01.02.2025' > '31.01.2025'

Строковое сравнение может давать неверный результат.


Правильный подход

const a = LocalDate.parse('2025-02-01');
const b = LocalDate.parse('2025-01-31');

console.log(a.isAfter(b));

Производительность сравнения

Методы Js-joda:

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

Практический пример: фильтрация будущих событий

const events = [
    LocalDate.parse('2025-01-01'),
    LocalDate.parse('2026-01-01'),
    LocalDate.parse('2024-01-01')
];

const today = LocalDate.now();

const futureEvents = events.filter(
    event => event.isAfter(today)
);

console.log(futureEvents);

Практический пример: сортировка логов

const logs = [
    Instant.parse('2025-01-01T10:00:00Z'),
    Instant.parse('2025-01-01T09:00:00Z'),
    Instant.parse('2025-01-01T11:00:00Z')
];

logs.sort((a, b) => a.compareTo(b));

Практический пример: определение ближайшей даты

function nearest(target, dates) {
    return dates.reduce((closest, current) => {
        const currentDiff = Math.abs(
            ChronoUnit.DAYS.between(target, current)
        );

        const closestDiff = Math.abs(
            ChronoUnit.DAYS.between(target, closest)
        );

        return currentDiff < closestDiff
            ? current
            : closest;
    });
}

Рекомендации по сравнению временных значений

Для абсолютного времени

Использовать:

Instant

или:

ZonedDateTime

Для бизнес-даты

Использовать:

LocalDate

Для времени суток

Использовать:

LocalTime

Для интервалов

Использовать:

Duration

или:

Period

в зависимости от задачи.


Безопасные правила сравнения

  • не сравнивать объекты через == и ===;
  • использовать isBefore, isAfter, isEqual;
  • учитывать часовые пояса;
  • хранить абсолютное время через Instant;
  • не сравнивать даты строками;
  • применять compareTo для сортировки;
  • использовать ChronoUnit для вычисления разницы;
  • разделять календарные и абсолютные интервалы времени.