Защитное программирование

Работа с датами в Luxon начинается с преобразования входных значений в экземпляры DateTime. Любое значение, поступающее извне системы — строка пользователя, ответ API, данные базы — рассматривается как потенциально некорректное.

Ключевой принцип заключается в том, что создание DateTime не гарантирует корректный результат. Даже при отсутствии исключений объект может оказаться невалидным.

import { DateTime } from "luxon";

const dt = DateTime.fromISO("невалидная строка");

console.log(dt.isValid); // false
console.log(dt.invalidReason); // "unparsable"

Защитное программирование в этом контексте требует обязательной проверки isValid после любого преобразования.

Централизованная валидация

Повторяющаяся логика проверки приводит к дублированию кода. Более устойчивый подход — создание обёртки над парсингом:

function safeFromISO(value) {
  const dt = DateTime.fromISO(value);
  if (!dt.isValid) return null;
  return dt;
}

Такая функция превращает Luxon-объекты в предсказуемый слой данных, где null однозначно сигнализирует об ошибке входа.


Явное управление временными зонами

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

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

const dt = DateTime.fromISO("2026-01-01T10:00:00");

console.log(dt.zoneName);

Защитный подход предполагает явное указание зоны:

const dt = DateTime.fromISO("2026-01-01T10:00:00", {
  zone: "utc"
});

Нормализация всех дат к единому стандарту

В системах с распределённой архитектурой используется стратегия унификации:

  • входные данные приводятся к UTC
  • внутренние вычисления выполняются в UTC
  • отображение преобразуется в локальную зону только на уровне UI
function toUtcDateTime(value) {
  const dt = DateTime.fromISO(value, { zone: "utc" });
  return dt.isValid ? dt : null;
}

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


Защита от невалидных операций над DateTime

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

const dt = DateTime.fromISO("invalid");
const result = dt.plus({ days: 1 });

console.log(result.isValid); // false

Это поведение требует строгого контроля цепочек вызовов.

Разрыв цепочки при невалидности

Защитный шаблон заключается в проверке на каждом этапе:

function addDaysSafe(value, days) {
  const dt = DateTime.fromISO(value);
  if (!dt.isValid) return null;

  const result = dt.plus({ days });
  if (!result.isValid) return null;

  return result;
}

Такой стиль исключает распространение ошибки по цепочке вычислений.


Работа с парсингом форматов

Метод fromFormat особенно чувствителен к несоответствиям входных данных. Малейшее отклонение от формата приводит к невалидному объекту.

const dt = DateTime.fromFormat("2026/01/23", "yyyy-MM-dd");

console.log(dt.isValid); // false

Стратегия строгого парсинга

Защитное программирование предполагает явную фиксацию формата и проверку результата:

function parseStrict(value, format) {
  const dt = DateTime.fromFormat(value, format);
  return dt.isValid ? dt : null;
}

Дополнительно используется контроль локали:

DateTime.fromFormat("23.01.2026", "dd.MM.yyyy", {
  locale: "ru"
});

Игнорирование локали часто приводит к скрытым ошибкам интерпретации даты.


Обработка граничных значений

Даты обладают естественными границами: переходы месяцев, високосные годы, переходы времени при смене часовых поясов.

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

Переход через конец месяца

const dt = DateTime.fromISO("2026-01-31").plus({ months: 1 });

console.log(dt.toISO()); // 2026-02-28

Если бизнес-логика требует строгого сохранения дня месяца, необходимо вводить дополнительную проверку:

function addMonthsStrict(dt, months) {
  const originalDay = dt.day;
  const result = dt.plus({ months });

  if (result.day !== originalDay) {
    return null;
  }

  return result;
}

Защита от ошибок временных зон и переходов DST

Переходы на летнее и зимнее время создают неоднозначные или несуществующие моменты времени.

const dt = DateTime.fromISO("2026-03-29T02:30:00", {
  zone: "Europe/Berlin"
});

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

Стратегия нормализации времени

Для предотвращения неоднозначности применяется конвертация в UTC:

function normalizeToUtc(value, zone) {
  const dt = DateTime.fromISO(value, { zone });
  if (!dt.isValid) return null;
  return dt.toUTC();
}

Контроль неизменяемости объектов

DateTime в Luxon является иммутабельным объектом. Каждый метод возвращает новый экземпляр.

Однако защитное программирование требует учитывать возможность потери ссылки на исходное значение.

const original = DateTime.now();
const modified = original.plus({ hours: 2 });

Ошибки возникают при предположении, что original изменяется:

original.plus({ days: 1 });
// original остаётся неизменным

Явное именование состояний

Для снижения риска логических ошибок используется разделение переменных:

const baseDate = DateTime.now();
const calculatedDate = baseDate.plus({ days: 5 });

Защита при сериализации и десериализации

Передача DateTime между слоями системы требует сериализации. Основной формат — ISO.

const json = dt.toISO();

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

function reviveDateTime(value) {
  const dt = DateTime.fromISO(value);
  return dt.isValid ? dt : null;
}

При использовании JSON-десериализации дополнительно проверяется тип:

function revive(key, value) {
  if (typeof value === "string" && value.includes("T")) {
    const dt = DateTime.fromISO(value);
    if (dt.isValid) return dt;
  }
  return value;
}

Работа с Duration и Interval в защитной модели

Duration и Interval также могут быть невалидными при некорректных входных данных.

import { Duration } from "luxon";

const d = Duration.fromObject({ hours: "invalid" });

console.log(d.isValid); // false

Проверка перед использованием

function safeDuration(obj) {
  const d = Duration.fromObject(obj);
  return d.isValid ? d : null;
}

Для интервалов важно проверять обе границы:

import { Interval, DateTime } from "luxon";

function safeInterval(start, end) {
  const s = DateTime.fromISO(start);
  const e = DateTime.fromISO(end);

  if (!s.isValid || !e.isValid) return null;

  const interval = Interval.fromDateTimes(s, e);
  return interval.isValid ? interval : null;
}

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

Защитное программирование в Luxon направлено на устранение недетерминированности. Основные источники нестабильности:

  • системное время
  • временные зоны
  • локали
  • некорректный ввод

Инъекция времени

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

function calculateExpiry(now, hours) {
  const dt = DateTime.fromISO(now);
  if (!dt.isValid) return null;

  return dt.plus({ hours });
}

Это исключает зависимость от DateTime.now() внутри логики.


Строгая модель обработки ошибок Luxon

Luxon использует модель «неисключающих ошибок», где результат всегда возвращается, но может быть невалидным. Это требует дисциплины:

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

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