Типобезопасность и строгая типизация

Работа с датой и временем относится к числу наиболее сложных областей прикладного программирования. Ошибки часто возникают не из-за вычислений, а из-за смешивания различных типов временных данных:

  • даты без времени;
  • времени без даты;
  • локального времени;
  • времени с часовым поясом;
  • UTC-времени;
  • временных интервалов;
  • календарных периодов.

Стандартный объект Date в JavaScript объединяет практически всё в единую сущность, что приводит к неоднозначности и ошибкам. Библиотека Js-joda решает эту проблему через строгую модель типов, заимствованную из Java Time API.

Типобезопасность в Js-joda означает:

  • невозможность случайного смешивания несовместимых временных сущностей;
  • явное разделение понятий;
  • строгую модель преобразований;
  • предсказуемое поведение API;
  • снижение количества логических ошибок.

Разделение временных сущностей

LocalDate — только дата

LocalDate хранит:

  • год;
  • месяц;
  • день.

В объекте отсутствуют:

  • часы;
  • минуты;
  • секунды;
  • часовой пояс.
const { LocalDate } = require('@js-joda/core');

const date = LocalDate.of(2025, 3, 15);

console.log(date.toString());
// 2025-03-15

Попытка получить время невозможна:

date.hour();

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

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


LocalTime — только время

LocalTime хранит:

  • часы;
  • минуты;
  • секунды;
  • наносекунды.

Дата отсутствует полностью.

const { LocalTime } = require('@js-joda/core');

const time = LocalTime.of(14, 30);

console.log(time.toString());
// 14:30

Операции с календарными днями недоступны:

time.plusDays(1);

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


LocalDateTime — локальные дата и время

LocalDateTime объединяет:

  • дату;
  • время.

Но всё ещё не содержит:

  • часовой пояс;
  • UTC-смещение.
const { LocalDateTime } = require('@js-joda/core');

const dateTime = LocalDateTime.of(2025, 3, 15, 14, 30);

console.log(dateTime.toString());
// 2025-03-15T14:30

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


ZonedDateTime — дата, время и часовой пояс

ZonedDateTime включает:

  • дату;
  • время;
  • временную зону.
const { ZonedDateTime, ZoneId } = require('@js-joda/core');

const zoned = ZonedDateTime.now(
    ZoneId.of('Europe/Moscow')
);

console.log(zoned.toString());

Теперь объект представляет конкретный момент времени в определённой временной зоне.


Невозможность случайного смешивания типов

Одно из главных преимуществ Js-joda — предотвращение невалидных операций.

Ошибки стандартного Date

Классический JavaScript допускает опасные конструкции:

const date = new Date();

date.setHours(50);

Интерпретация зависит от внутренней логики объекта.

Js-joda избегает подобных неоднозначностей.


Явное преобразование типов

Преобразования выполняются только явно.

Преобразование даты и времени в LocalDateTime

const { LocalDate, LocalTime } = require('@js-joda/core');

const date = LocalDate.of(2025, 3, 15);
const time = LocalTime.of(10, 45);

const dateTime = date.atTime(time);

console.log(dateTime.toString());
// 2025-03-15T10:45

Невозможно случайно объединить объекты автоматически.


Явное добавление часового пояса

const { ZoneId } = require('@js-joda/core');

const zoned = dateTime.atZone(
    ZoneId.of('Asia/Almaty')
);

Разработчик обязан явно указать временную зону.


Immutable-модель как часть строгой типизации

Все объекты Js-joda неизменяемы.

Это означает:

  • операции не модифицируют исходный объект;
  • методы возвращают новый экземпляр;
  • отсутствуют скрытые побочные эффекты.

Пример неизменяемости

const { LocalDate } = require('@js-joda/core');

const original = LocalDate.of(2025, 1, 1);

const changed = original.plusDays(10);

console.log(original.toString());
// 2025-01-01

console.log(changed.toString());
// 2025-01-11

Исходный объект остаётся неизменным.


Типобезопасность арифметики

Работа с Period

Period представляет календарный период:

  • годы;
  • месяцы;
  • дни.
const { Period } = require('@js-joda/core');

const period = Period.ofMonths(2);

Добавление периода к времени невозможно:

const time = LocalTime.of(10, 0);

time.plus(period);

Это вызовет ошибку.

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


Работа с Duration

Duration представляет точную временную длительность:

  • часы;
  • минуты;
  • секунды;
  • наносекунды.
const { Duration } = require('@js-joda/core');

const duration = Duration.ofHours(5);

Duration подходит для времени, а Period — для календарных сущностей.


Строгая семантика API

Каждый тип в Js-joda имеет чёткую предметную область.

LocalDate

Поддерживает:

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

Не поддерживает:

  • секунды;
  • минуты;
  • часовые зоны.

LocalTime

Поддерживает:

  • часы;
  • минуты;
  • секунды.

Не поддерживает:

  • месяцы;
  • годы.

Instant

Instant представляет абсолютный момент времени в UTC.

const { Instant } = require('@js-joda/core');

const instant = Instant.now();

console.log(instant.toString());

У Instant отсутствует концепция локального календаря.


Предотвращение логических ошибок

Ошибка смешивания локального и абсолютного времени

В обычном JavaScript легко спутать:

  • локальное время;
  • UTC;
  • дату пользователя;
  • серверное время.

Js-joda делает такие ошибки заметными уже на уровне типов.


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

const { Instant, ZoneId } = require('@js-joda/core');

const instant = Instant.now();

const local = instant.atZone(
    ZoneId.of('Asia/Almaty')
);

console.log(local.toString());

Преобразование выполняется только через явное указание зоны.


Типы и бизнес-логика

Строгая типизация особенно важна в бизнес-приложениях.


Дата рождения

Дата рождения не должна содержать:

  • время;
  • часовой пояс.

Правильный тип:

LocalDate

Время встречи

Онлайн-встреча требует:

  • даты;
  • времени;
  • часового пояса.

Правильный тип:

ZonedDateTime

Таймер

Для таймера нужен:

Duration

Подписка на месяц

Для подписки используется:

Period

Компилятор и IDE как часть типобезопасности

При использовании TypeScript преимущества Js-joda становятся ещё более заметными.


Пример типизации

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

function formatDate(date: LocalDate): string {
    return date.toString();
}

Передача неправильного типа будет обнаружена компилятором.


Ошибка на этапе разработки

formatDate("2025-01-01");

TypeScript сообщит о несовместимости типов.


Строгость методов

plusDays существует только там, где имеет смысл

const date = LocalDate.now();

date.plusDays(5);

Но:

const time = LocalTime.now();

time.plusDays(5);

Метод отсутствует.

Такой подход устраняет целый класс ошибок.


Явность преобразований

Js-joda избегает неявной магии.


Преобразование строки в дату

const { LocalDate } = require('@js-joda/core');

const date = LocalDate.parse('2025-03-15');

Тип результата всегда предсказуем.


Преобразование LocalDateTime в Instant

Для преобразования требуется временная зона.

const { ZoneOffset } = require('@js-joda/core');

const instant = dateTime.toInstant(
    ZoneOffset.UTC
);

Без UTC-offset операция невозможна.


Защита от проблем часовых поясов

Часовые пояса — одна из главных причин ошибок в системах времени.

Js-joda делает работу с ними строго типизированной.


Невозможность «потерять» зону случайно

const zoned = ZonedDateTime.now(
    ZoneId.of('Europe/Berlin')
);

Зона становится частью объекта.


Явное преобразование зоны

const tokyo = zoned.withZoneSameInstant(
    ZoneId.of('Asia/Tokyo')
);

Метод прямо указывает:

  • сохраняется тот же момент времени;
  • меняется отображение зоны.

Типобезопасность интервалов

Period и Duration — разные сущности

Многие библиотеки смешивают:

  • «30 дней»;
  • «720 часов».

Но это не одно и то же.


Period

Period.ofMonths(1)

Календарный месяц может содержать:

  • 28 дней;
  • 29 дней;
  • 30 дней;
  • 31 день.

Duration

Duration.ofHours(24)

Точная длительность.


Строгая модель UTC

Instant как единый источник истины

Во многих распределённых системах:

  • хранение ведётся в UTC;
  • отображение — в локальной зоне.

Instant идеально подходит для хранения.

const createdAt = Instant.now();

Локализация при отображении

const display = createdAt.atZone(
    ZoneId.of('Asia/Almaty')
);

Типобезопасность сериализации

ISO-8601 по умолчанию

Все основные типы используют стандартизированный формат.

LocalDate.parse('2025-03-15');
Instant.parse('2025-03-15T10:15:30Z');

Предсказуемость API

В Js-joda отсутствует:

  • перегруженная магия;
  • автоматическое приведение типов;
  • скрытая конвертация зон;
  • изменение исходных объектов.

API остаётся:

  • строгим;
  • последовательным;
  • детерминированным.

Преимущества строгой типизации в крупных проектах

Упрощение поддержки

По типу объекта сразу видно:

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

Снижение количества багов

Большая часть ошибок времени возникает из-за:

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

Js-joda минимизирует такие ошибки архитектурно.


Повышение читаемости кода

function schedule(dateTime) {}

Неочевидно, что ожидается.


function schedule(meeting: ZonedDateTime) {}

Семантика становится ясной сразу.


Сравнение со стандартным Date

Date

const date = new Date();

Объект одновременно содержит:

  • дату;
  • время;
  • UTC;
  • локальную зону;
  • timestamp.

Модель неоднозначна.


Js-joda

Каждая сущность выделена в отдельный тип:

  • LocalDate
  • LocalTime
  • LocalDateTime
  • ZonedDateTime
  • Instant
  • Period
  • Duration

Это фундамент строгой архитектуры времени.


Типобезопасность как архитектурный принцип

Js-joda строится вокруг идеи:

каждая временная сущность должна иметь собственный строгий тип.

Именно поэтому библиотека:

  • снижает вероятность ошибок;
  • улучшает читаемость;
  • упрощает поддержку;
  • делает код предсказуемым;
  • обеспечивает корректную работу с часовыми поясами и календарными вычислениями.