Offset и tick clocks

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

Clock используется всеми основными типами времени:

  • Instant
  • LocalDateTime
  • ZonedDateTime
  • OffsetDateTime

Ключевая идея заключается в том, что время не берётся напрямую из системы, а запрашивается через объект-источник, что позволяет контролировать, фиксировать и модифицировать временную шкалу.


Системные часы

Базовый вариант — системные часы, привязанные к реальному времени операционной системы.

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

const clock = Clock.systemDefaultZone();

Также можно явно указать часовой пояс:

const clock = Clock.system(ZoneId.of('Europe/Berlin'));

Системные часы:

  • возвращают текущее реальное время
  • зависят от системного времени устройства
  • используются по умолчанию при создании дат и времени без явного Clock

Фиксированные часы

Фиксированные часы (fixed clock) позволяют зафиксировать момент времени. Это критически важно для тестирования и воспроизводимости поведения кода.

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

const fixedInstant = Instant.parse('2025-01-01T10:00:00Z');
const clock = Clock.fixed(fixedInstant, ZoneId.of('UTC'));

Особенности фиксированных часов:

  • всегда возвращают один и тот же Instant
  • полностью игнорируют системное время
  • используются для тестов, где важна детерминированность

Любые вызовы времени через такой Clock дают одинаковый результат:

clock.instant(); // всегда 2025-01-01T10:00:00Z

Offset clock: смещение времени

Offset clock применяется для сдвига текущего времени относительно базового источника.

Идея заключается в том, что можно взять любой Clock и создать поверх него новый, изменяющий момент времени на фиксированное смещение.

import { Clock, Duration } from '@js-joda/core';

const baseClock = Clock.systemUTC();
const offsetClock = Clock.offset(baseClock, Duration.ofHours(3));

В этом случае:

  • базовое время берётся из baseClock
  • ко всем значениям добавляется +3 часа
  • логически создаётся «виртуальный часовой пояс»

Поведение offset clock

Offset clock не изменяет системное время, а лишь трансформирует результат.

const nowBase = baseClock.instant();
const nowOffset = offsetClock.instant();

nowOffset.equals(nowBase.plusHours(3)); // true

Важно различать:

  • ZoneId — географический часовой пояс с правилами перехода
  • ZoneOffset — фиксированное смещение без правил
  • Clock.offset — логическое смещение времени поверх любого Clock

Tick clocks: дискретизация времени

Tick clocks ограничивают точность времени, округляя его до заданного интервала. Это используется для устранения излишней точности (например, наносекундной), когда она не требуется.

Tick по миллисекундам

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

const clock = Clock.tickMillis(Clock.systemUTC());

Такой clock:

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

Tick по секундам

const clock = Clock.tickSeconds(Clock.systemUTC());

Поведение:

  • время округляется до целых секунд
  • все доли секунды игнорируются

Tick по минутам

const clock = Clock.tickMinutes(Clock.systemUTC());

В этом случае:

  • остаются только минуты и выше
  • секунды и миллисекунды обнуляются

Принцип работы tick clock

Tick clock не создаёт новое время, а преобразует результат базового Clock.

Для каждого вызова:

  1. Берётся текущий Instant из базового clock
  2. Применяется округление вниз до заданного шага
  3. Возвращается нормализованное значение

Пример логики:

2026-01-01T12:34:56.789Z
→ tickSeconds
2026-01-01T12:34:56Z

Комбинирование offset и tick

Offset и tick clocks могут использоваться вместе, создавая составные источники времени.

const base = Clock.systemUTC();
const offset = Clock.offset(base, Duration.ofHours(2));
const ticked = Clock.tickSeconds(offset);

Поведение:

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

Практическая модель поведения Clock

Все типы clock в js-joda подчиняются общей модели:

  • systemClock — отражает реальное время
  • fixedClock — замороженное время
  • offsetClock — сдвинутое время
  • tickClock — округлённое время

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

System Clock → Offset Clock → Tick Clock → Instant

Влияние на ZonedDateTime и OffsetDateTime

При использовании Clock все высокоуровневые типы времени наследуют его поведение.

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

const zdt = ZonedDateTime.now(clock);

Результат зависит от типа clock:

  • системный clock — реальное текущее время
  • fixed clock — постоянная дата
  • offset clock — сдвинутая временная шкала
  • tick clock — усечённая точность времени

Использование ZoneOffset в связке с Clock

ZoneOffset представляет фиксированное смещение от UTC и часто используется совместно с OffsetDateTime.

import { OffsetDateTime, ZoneOffset } from '@js-joda/core';

const offset = ZoneOffset.ofHours(5);
const odt = OffsetDateTime.now(Clock.systemUTC().withZone(offset));

Хотя Clock и ZoneOffset решают разные задачи, их комбинация позволяет:

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

Стабилизация времени в тестовых сценариях

Tick и offset clocks часто комбинируются для устранения нестабильности тестов:

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

Пример стабильного источника:

const stableClock = Clock.tickSeconds(
  Clock.offset(
    Clock.fixed(Instant.parse('2025-01-01T00:00:00Z'), ZoneOffset.UTC),
    Duration.ofHours(1)
  )
);