Until и between методы

В библиотеке js-joda метод until является универсальным инструментом для вычисления разницы между двумя временными объектами. Он работает в рамках интерфейса Temporal и его конкретных реализаций (LocalDate, LocalTime, LocalDateTime, ZonedDateTime), предоставляя как высокоуровневые результаты (например, Period), так и числовые значения в заданных единицах измерения.

Поведение метода until в разных типах данных

Метод until имеет перегрузки, поведение которых зависит от типа временного объекта и переданных аргументов.

1. until без единицы измерения (возврат Period или Duration)

Для календарных типов данных, таких как LocalDate, вызов:

date1.until(date2)

возвращает объект Period, содержащий разницу в годах, месяцах и днях.

Пример:

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

const start = LocalDate.parse('2024-01-10');
const end = LocalDate.parse('2025-03-25');

const period = start.until(end);

console.log(period.years());  // 1
console.log(period.months());  // 2
console.log(period.days());    // 15

Такой результат отражает календарную разницу, а не общее количество дней.

Для временных типов с точностью до времени суток (LocalTime, LocalDateTime) результатом может быть Duration, если используется соответствующий контекст.


2. until с единицей измерения (возврат числа)

Вызов вида:

temporal1.until(temporal2, unit)

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

Пример:

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

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

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

console.log(days); // 31

Поддерживаемые единицы берутся из ChronoUnit, например:

  • ChronoUnit.DAYS
  • ChronoUnit.MONTHS
  • ChronoUnit.YEARS
  • ChronoUnit.HOURS
  • ChronoUnit.SECONDS

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


Особенности вычисления until

Направление вычисления

Метод всегда вычисляет разницу от текущего объекта к целевому:

start.until(end)

Если порядок обратный:

end.until(start)

результат будет отрицательным (для числовых единиц) или «обратным» периодом.


Ограничения календарных вычислений

Для LocalDate метод until при возврате Period учитывает календарную природу дат:

  • месяцы разной длины
  • високосные годы
  • неполные периоды

Это означает, что результат не всегда эквивалентен простому количеству дней.


Статический метод between и класс ChronoUnit

Параллельно с until в js-joda используется более низкоуровневый механизм — ChronoUnit.between и его эквивалент в виде статического метода between у единиц времени.

ChronoUnit.between

Сигнатура:

ChronoUnit.between(temporal1, temporal2)

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

Пример:

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

const d1 = LocalDate.parse('2024-01-01');
const d2 = LocalDate.parse('2024-03-01');

const months = ChronoUnit.MONTHS.between(d1, d2);

console.log(months); // 2

Этот вариант эквивалентен:

d1.until(d2, ChronoUnit.MONTHS);

но используется как функциональный стиль через единицу измерения.


Сравнение until и ChronoUnit.between

Оба подхода решают одну задачу — вычисление разницы, но различаются уровнем абстракции.

until как метод объекта

start.until(end, ChronoUnit.DAYS);
  • читается как операция над объектом
  • удобен в объектно-ориентированном стиле
  • поддерживает возврат Period/Duration без указания единицы

ChronoUnit.between как статическая функция

ChronoUnit.DAYS.between(start, end);
  • функциональный стиль
  • подчёркивает единицу измерения как главный элемент операции
  • удобен для цепочек вычислений и утилитарного кода

Разница между календарной и линейной моделью времени

При использовании until и between важно различать два подхода:

Календарный подход (Period)

Используется в:

LocalDate.until(LocalDate)

Результат:

  • годы
  • месяцы
  • дни

Пример:

const a = LocalDate.parse('2024-01-31');
const b = LocalDate.parse('2024-03-01');

const period = a.until(b);

Такой расчёт учитывает календарные переходы, поэтому месяцы могут вести себя не как фиксированное количество дней.


Линейный подход (ChronoUnit / Duration)

Используется в:

ChronoUnit.DAYS.between(a, b)

или

a.until(b, ChronoUnit.DAYS)

Результат:

  • фиксированная числовая разница
  • без учёта календарной структуры

until для временных типов с точностью до времени

Для объектов, содержащих время суток (LocalTime, LocalDateTime), метод until работает в связке с Duration.

Пример:

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

const t1 = LocalTime.parse('10:15');
const t2 = LocalTime.parse('14:45');

const minutes = t1.until(t2, ChronoUnit.MINUTES);

console.log(minutes); // 270

При отсутствии единицы измерения:

t1.until(t2)

возвращается Duration, содержащий разницу в секундах и наносекундах.


Граничные случаи и особенности поведения

Отрицательные интервалы

Если конечный момент раньше начального:

end.until(start, ChronoUnit.DAYS);

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


Несовместимые типы

Некоторые комбинации временных типов приводят к исключениям:

  • LocalDate нельзя напрямую сравнивать с LocalTime
  • требуется согласование через LocalDateTime или ZonedDateTime

Влияние временных зон

При работе с ZonedDateTime учитываются:

  • переходы на летнее/зимнее время
  • смещения часовых поясов
  • локальные календарные правила

Это влияет на результат until, особенно при использовании ChronoUnit.HOURS и более крупных единиц времени.


Практическая разница подходов в вычислениях

Использование until для доменной логики

const age = birthDate.until(today, ChronoUnit.YEARS);
  • лаконичный стиль
  • прямое выражение бизнес-логики

Использование ChronoUnit.between для утилитарных расчётов

const diff = ChronoUnit.DAYS.between(start, end);
  • удобно в математических операциях
  • хорошо комбинируется с другими вычислениями

Взаимозаменяемость методов

Во многих случаях:

a.until(b, unit)

эквивалентно:

unit.between(a, b)

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