Конвертация из js-joda в Date

Библиотека js-joda работает с иммутабельными типами даты и времени, полностью отделяя календарные вычисления от представления в виде системного времени JavaScript. В отличие от встроенного объекта Date, который хранит момент времени в миллисекундах UTC, js-joda оперирует строго типизированными сущностями: LocalDate, LocalTime, LocalDateTime, Instant, ZonedDateTime.

Конвертация в Date всегда сводится к одному ключевому моменту: получение абсолютного времени (Instant) и его преобразование в миллисекунды эпохи Unix.


Instant как базовая точка преобразования

Instant — это универсальное представление точки на временной шкале в UTC без привязки к часовому поясу.

Именно Instant является центральным звеном при конвертации в Date.

Прямое преобразование

const jsDate = instant.toDate();

Метод toDate() доступен для Instant (и некоторых других типов через зависимости js-joda) и возвращает стандартный Date.

Через миллисекунды

const jsDate = new Date(instant.toEpochMilli());

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


ZonedDateTime → Date

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

Прямое преобразование

const jsDate = zonedDateTime.toDate();

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

Через Instant

const jsDate = new Date(zonedDateTime.toInstant().toEpochMilli());

Логика преобразования:

  • локальное время + зона → момент времени
  • момент времени → миллисекунды UTC
  • миллисекунды → Date

LocalDateTime → Date

LocalDateTime не содержит информации о временной зоне, поэтому прямого однозначного преобразования в Date не существует.

Необходима явная привязка к зоне:

const jsDate = localDateTime
  .atZone(zoneId)
  .toInstant()
  .toDate();

Важный момент

Без указания зоны результат будет зависеть от выбранного ZoneId, например:

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

const zone = ZoneId.of('Europe/Berlin');

LocalDate → Date

LocalDate содержит только календарную дату без времени.

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

const jsDate = localDate
  .atStartOfDay(zoneId)
  .toInstant()
  .toDate();

Альтернативный вариант

Можно задать конкретное время:

const jsDate = localDate
  .atTime(12, 0)
  .atZone(zoneId)
  .toInstant()
  .toDate();

LocalTime → Date

LocalTime не содержит даты, поэтому преобразование требует фиктивной даты:

const jsDate = localTime
  .atDate(localDate)
  .atZone(zoneId)
  .toInstant()
  .toDate();

Без комбинирования с датой объект Date получить невозможно.


Epoch-based преобразования

Во всех типах, которые могут быть сведены к Instant, доступны методы работы с эпохой Unix.

Миллисекунды

const millis = instant.toEpochMilli();
const date = new Date(millis);

Секунды

const seconds = instant.getEpochSecond();
const date = new Date(seconds * 1000);

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


Обратная конвертация Date → js-joda

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

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

const instant = Instant.ofEpochMilli(date.getTime());

Дальнейшее преобразование:

const zoned = instant.atZone(zoneId);
const localDateTime = zoned.toLocalDateTime();
const localDate = zoned.toLocalDate();

Работа с временными зонами при конвертации

Ошибки при преобразовании чаще всего возникают из-за игнорирования временной зоны.

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

  • Instant — всегда UTC, не зависит от зоны
  • Date — также хранит UTC, но отображается в локальной зоне среды выполнения
  • LocalDate/LocalDateTime — не содержат информации о зоне

Корректная цепочка преобразования

LocalDate / LocalDateTime
        ↓ + ZoneId
   ZonedDateTime
        ↓
     Instant
        ↓
       Date

Потеря информации при конвертации

При переходе из js-joda в Date возможна потеря данных:

  • LocalDate теряет информацию о времени
  • LocalTime теряет дату
  • ZonedDateTime теряет исходную зону (остается только момент времени)
  • Date не хранит структурированную календарную модель

Типовые сценарии преобразования

Сохранение даты без времени

const date = localDate
  .atStartOfDay(zoneId)
  .toInstant()
  .toDate();

Сохранение точного момента события

const date = zonedDateTime.toInstant().toDate();

Работа с пользовательским вводом

const date = LocalDateTime.parse(input)
  .atZone(ZoneId.of('UTC'))
  .toInstant()
  .toDate();

Частые ошибки при конвертации

Игнорирование временной зоны

// некорректно
new Date(localDateTime.toString());

Попытка напрямую преобразовать LocalDate

// некорректно
localDate.toDate(); // не существует

Потеря точности при ручном пересчёте

new Date(instant.getEpochSecond()); // ошибка: секунды вместо миллисекунд

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

  • Использование Instant как промежуточного слоя минимизирует ошибки
  • Явное указание ZoneId делает поведение предсказуемым
  • Прямой вызов .toDate() предпочтителен там, где он доступен
  • Любая дата без зоны должна быть явно нормализована перед конвертацией