Работа с UTC

В библиотеке js-joda вся работа с временем опирается на строгую модель разделения между моментом времени и его представлением в конкретной временной зоне. UTC (Coordinated Universal Time) выступает как базовая система отсчёта, в которой любой момент выражается независимо от географического положения и правил перехода на летнее время.

Ключевая идея: UTC в js-joda — это не формат отображения, а фундаментальная ось времени, представленная типом Instant.


Instant как фундамент UTC

Instant представляет собой точку на временной шкале UTC с точностью до наносекундной части (в рамках возможностей JavaScript — до миллисекунд).

Он не содержит информации о временной зоне, смещении или локализации.

Создание Instant

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

const now = Instant.now();

Instant.now() всегда возвращает текущее время в UTC-координате, независимо от локальной системы.

Разбор ISO-строки

const instant = Instant.parse('2026-05-24T12:30:00Z');

Суффикс Z означает UTC (Zulu time). Это строгий индикатор нулевого смещения.

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

instant.toEpochMilli();

Возвращает количество миллисекунд с 1970-01-01T00:00:00Z.

Instant.ofEpochSecond(0);

Создаёт момент из UNIX timestamp.


ZoneOffset.UTC и ZoneId.UTC

В js-joda различаются два концептуально связанных, но разных объекта:

  • ZoneOffset.UTC — фиксированное смещение +00:00
  • ZoneId.UTC — идентификатор временной зоны UTC

ZoneOffset.UTC

Используется, когда важно фиксированное смещение без правил календаря:

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

const offset = ZoneOffset.UTC;

ZoneId.UTC

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

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

const zone = ZoneId.UTC;

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


Преобразование локального времени в UTC

Основная операция при работе с UTC — нормализация локального времени в момент.

ZonedDateTime → Instant

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

const zdt = ZonedDateTime.now(ZoneId.systemDefault());
const instant = zdt.toInstant();

toInstant() полностью убирает информацию о зоне и возвращает абсолютный момент.


Преобразование UTC в локальное время

Обратная операция требует привязки к временной зоне.

const utcInstant = Instant.now();

const localZdt = utcInstant.atZone(ZoneId.systemDefault());

Тот же момент времени интерпретируется в локальной зоне пользователя.


OffsetDateTime в контексте UTC

OffsetDateTime хранит дату и время вместе со смещением от UTC, но без полной зоны.

Создание UTC OffsetDateTime

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

const instant = Instant.now();

const utcDateTime = instant.atOffset(ZoneOffset.UTC);

Такой объект фиксирует смещение +00:00, но не содержит правил перехода времени.


ZonedDateTime и UTC-зона

При использовании ZonedDateTime с UTC поведение становится полностью детерминированным.

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

const utcZdt = ZonedDateTime.now(ZoneId.UTC);

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

  • отсутствует смещение, отличное от нуля
  • не применяются правила DST
  • поведение эквивалентно Instant, но с дополнительной структурой даты и времени

ISO-формат и UTC представление

Стандартное представление UTC в ISO-8601:

YYYY-MM-DDTHH:mm:ssZ

В js-joda форматирование выполняется через DateTimeFormatter.

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

const instant = Instant.parse('2026-05-24T10:15:30Z');

const formatted = instant.toString();

Instant.toString() всегда возвращает строку с Z.

Форматирование через DateTimeFormatter

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

const zdt = ZonedDateTime.now(ZoneId.UTC);

const formatter = DateTimeFormatter.ISO_INSTANT;

const result = formatter.format(zdt);

ISO_INSTANT гарантирует вывод в UTC-нормализации.


Арифметика времени в UTC

Операции сложения и вычитания времени в UTC всегда выполняются без влияния временных зон.

Добавление времени

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

const start = Instant.parse('2026-05-24T00:00:00Z');

const end = start.plus(Duration.ofHours(5));

Результат всегда остаётся в UTC-координате.

Вычитание

const diff = end.minusSeconds(3600);

Все операции выполняются линейно по временной шкале.


Преобразования через epoch

Одним из наиболее стабильных способов работы с UTC является использование epoch-значений.

const instant = Instant.now();

const millis = instant.toEpochMilli();

const restored = Instant.ofEpochMilli(millis);

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


Работа с системной зоной и UTC

Часто требуется сравнение локального времени и UTC-представления одного и того же момента.

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

const instant = Instant.now();

const systemZdt = instant.atZone(ZoneId.systemDefault());
const utcZdt = instant.atZone(ZoneId.UTC);

Оба объекта указывают на один и тот же момент, но имеют разное отображение календарных полей.


DST и его отсутствие в UTC

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

При использовании:

ZoneId.UTC

или

ZoneOffset.UTC

любые правила DST игнорируются.

Это делает UTC предпочтительным форматом для:

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

Парсинг входящих данных в UTC

Часто входные строки содержат либо явный UTC, либо локальное время без зоны.

Явный UTC

const instant = Instant.parse('2026-05-24T18:00:00Z');

Приведение локального времени к UTC

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

const ldt = LocalDateTime.parse('2026-05-24T18:00:00');

const instant = ldt.atZone(ZoneId.systemDefault()).toInstant();

Этот шаг критичен для устранения неоднозначностей при интерпретации данных.


Сравнение Instant

Все сравнения Instant выполняются как числовые сравнения на временной шкале UTC.

const a = Instant.parse('2026-05-24T10:00:00Z');
const b = Instant.parse('2026-05-24T12:00:00Z');

const isBefore = a.isBefore(b);

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

Потеря зоны при преобразованиях

const zdt = ZonedDateTime.now(ZoneId.systemDefault());

const wrong = zdt.toLocalDateTime(); // теряется информация о моменте

LocalDateTime не содержит UTC-координаты, что делает его непригодным для хранения абсолютного времени.


Двойная интерпретация строки

const instant1 = Instant.parse('2026-05-24T10:00:00Z');
const instant2 = Instant.parse('2026-05-24T10:00:00+00:00');

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


Использование systemDefault без нормализации

const zdt = ZonedDateTime.now(ZoneId.systemDefault());

Без последующего преобразования в Instant такие значения нельзя безопасно передавать между системами.


Нормализация данных к UTC как базовый паттерн

Во всех сценариях обмена данными применяется единая схема:

  • вход → ZonedDateTime или LocalDateTime
  • нормализация → toInstant()
  • хранение/передача → Instant
  • отображение → atZone(ZoneId...)
const instant = ZonedDateTime.now(ZoneId.systemDefault()).toInstant();

const back = instant.atZone(ZoneId.UTC);

Эта модель исключает неоднозначность временных интерпретаций и делает UTC единственным источником истины для временных вычислений.