ChronoUnit как единицы измерения

В библиотеке js-joda работа с временными интервалами строится вокруг строгой типизации единиц времени. Центральную роль в этой модели играет перечисление ChronoUnit, задающее дискретные шаги измерения — от наносекунд до тысячелетий. Эти единицы используются для вычислений разницы между моментами времени, для прибавления и вычитания интервалов, а также для нормализации и преобразования дат.

ChronoUnit является аналогом java.time.temporal.ChronoUnit из Java Time API и сохраняет его семантику, включая поведение при граничных значениях календаря и неоднородность календарных систем.


Иерархия единиц и их природа

ChronoUnit объединяет две категории единиц:

  • точные (duration-based) — основаны на фиксированной длительности
  • календарные (date-based) — зависят от календарной системы

Точные единицы

К ним относятся:

  • NANOS
  • MICROS
  • MILLIS
  • SECONDS
  • MINUTES
  • HOURS
  • HALF_DAYS

Эти единицы имеют фиксированную продолжительность в SI-терминах (за исключением HALF_DAYS, привязанного к 12 часам). Их можно безопасно использовать для арифметики времени, не учитывающей календарные аномалии.

Календарные единицы

К ним относятся:

  • DAYS
  • WEEKS
  • MONTHS
  • YEARS
  • DECADES
  • CENTURIES
  • MILLENNIA
  • ERAS
  • FOREVER

Они зависят от календарной структуры и не имеют фиксированной длительности в миллисекундах. Например, MONTHS может быть 28, 29, 30 или 31 день.


Основные сценарии применения

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

  • вычисление разницы между датами и моментами
  • добавление или вычитание интервалов
  • проверка поддерживаемости единицы конкретным типом даты/времени
  • нормализация временных значений в заданной единице

Вычисление разницы между временными точками

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

Пример для временных меток:

import { ChronoUnit, Instant } from 'js-joda';

const start = Instant.parse('2025-01-01T00:00:00Z');
const end = Instant.parse('2025-01-02T12:00:00Z');

const hours = ChronoUnit.HOURS.between(start, end);
const minutes = ChronoUnit.MINUTES.between(start, end);

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


Работа с LocalDate и календарными единицами

При использовании дат без времени поведение ChronoUnit становится календарно-зависимым.

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

const date1 = LocalDate.of(2025, 1, 1);
const date2 = LocalDate.of(2025, 3, 1);

const months = ChronoUnit.MONTHS.between(date1, date2);
const days = ChronoUnit.DAYS.between(date1, date2);

MONTHS учитывает календарные переходы между месяцами, тогда как DAYS оперирует фактическим количеством дней.


Добавление и вычитание времени через ChronoUnit

ChronoUnit используется в методах plus и minus через числовые аргументы в сочетании с Temporal-объектами.

import { ChronoUnit, LocalDateTime } from 'js-joda';

const dt = LocalDateTime.of(2025, 1, 1, 10, 0);

const result = dt.plus(3, ChronoUnit.DAYS);

Аналогично:

const result2 = dt.minus(2, ChronoUnit.MONTHS);

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


Проверка поддерживаемости единицы

Не все временные типы поддерживают все ChronoUnit. Например, Instant не работает с MONTHS.

Метод isSupportedBy позволяет определить допустимость операции:

import { ChronoUnit, Instant } from 'js-joda';

ChronoUnit.DAYS.isSupportedBy(Instant.now());
ChronoUnit.MONTHS.isSupportedBy(Instant.now());

Результат:

  • DAYS → true
  • MONTHS → false

Это важно при построении универсальных временных функций.


HALF_DAYS и специфика 12-часового деления

HALF_DAYS представляет половину суток (12 часов). Эта единица часто используется при переходах между утренними и вечерними периодами.

ChronoUnit.HALF_DAYS.between(start, end);

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


FOREVER как особая единица

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

Обычно применяется в контекстах сравнений и специальных временных политик.


Взаимодействие с Temporal API js-joda

ChronoUnit тесно связан с интерфейсом TemporalAccessor и TemporalAmount. Он выступает в роли универсального ключа для операций:

  • Temporal.plus(amount, unit)
  • Temporal.minus(amount, unit)
  • Temporal.until(other, unit)

Пример использования until:

import { ChronoUnit, LocalTime } from 'js-joda';

const t1 = LocalTime.of(10, 0);
const t2 = LocalTime.of(15, 30);

const hours = t1.until(t2, ChronoUnit.HOURS);

Результат — количество полных часов между значениями.


Поведение при границах календаря

При работе с MONTHS и YEARS возможны эффекты “усечения”:

  • 31 января + 1 MONTH → 28 или 29 февраля
  • 31 декабря + 1 YEAR → 31 декабря следующего года (если допустимо)

ChronoUnit не интерполирует значения, а следует правилам календарной корректности, заложенным в LocalDate и LocalDateTime.


Различие между точными и календарными вычислениями

Ключевая особенность ChronoUnit заключается в различии моделей вычислений:

  • точные единицы → фиксированная длительность в наносекундах
  • календарные единицы → зависят от контекста даты

Пример различия:

ChronoUnit.DAYS.between(Instant.parse('2025-01-01T00:00:00Z'), Instant.parse('2025-01-02T00:00:00Z'));
ChronoUnit.MONTHS.between(LocalDate.of(2025,1,31), LocalDate.of(2025,2,28));

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


Использование в вычислительных алгоритмах

ChronoUnit часто применяется в:

  • расчётах SLA и дедлайнов
  • построении таймлайнов событий
  • агрегации логов по временным интервалам
  • планировании задач

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


Особенности реализации в js-joda

js-joda реализует ChronoUnit как неизменяемый набор констант. Каждая единица содержит:

  • функцию вычисления длительности
  • правила поддержки для разных Temporal типов
  • алгоритмы преобразования между единицами

Это обеспечивает предсказуемость и отсутствие скрытых преобразований между типами времени.


Семантика переполнения и усечения

При использовании ChronoUnit всегда применяется целочисленное усечение:

  • дробные результаты отбрасываются
  • отрицательные значения сохраняют знак
  • результат всегда выражается в целых единицах

Пример:

ChronoUnit.HOURS.between(start, end);

Если разница составляет 5 часов 59 минут, результат будет 5.


Роль ChronoUnit в архитектуре временной модели

ChronoUnit выполняет функцию универсального языка измерения времени в js-joda. Он связывает:

  • абсолютные моменты (Instant)
  • локальные даты и времена (LocalDate, LocalDateTime)
  • календарные правила

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