Расширение типов

Библиотека Luxon построена вокруг неизменяемых структур данных и строго определённых типов: DateTime, Duration, Interval, Info, Settings. Каждый из этих типов реализует ограниченный, хорошо инкапсулированный набор операций, ориентированных на безопасную работу с датами и временем.

Ключевая особенность архитектуры заключается в том, что экземпляры объектов immutable. Любая операция возвращает новый объект, не изменяя исходный. Это напрямую влияет на способы расширения функциональности: классическое наследование используется редко и считается вторичным подходом, тогда как композиция и обёртки становятся основными инструментами.

Ограничения классического наследования

Luxon не проектировался как библиотека, предполагающая активное наследование классов. Попытка расширить DateTime через extends формально возможна, но приводит к ряду проблем:

  • внутренние методы Luxon возвращают экземпляры базового класса, а не подкласса;
  • статические фабрики (fromISO, now, utc) всегда создают оригинальные типы;
  • потеря цепочки пользовательских методов при трансформациях;
  • несовместимость с внутренними проверками валидности (isValid, invalidReason).

Пример проблемного сценария:

class MyDateTime extends DateTime {
  customMethod() {
    return this.toISODate();
  }
}

const dt = MyDateTime.now();
const next = dt.plus({ days: 1 }); // вернётся DateTime, а не MyDateTime

После вызова plus тип пользовательского расширения теряется, что делает наследование неполноценным.

Композиционный подход как основа расширения

Наиболее устойчивый способ расширения Luxon — использование композиции. Вместо наследования создаётся собственный слой абстракции, который хранит внутри экземпляр Luxon-типа и делегирует ему операции.

class AppDateTime {
  constructor(dt) {
    this.dt = dt;
  }

  static now() {
    return new AppDateTime(DateTime.now());
  }

  plus(options) {
    return new AppDateTime(this.dt.plus(options));
  }

  toISO() {
    return this.dt.toISO();
  }

  isBusinessDay() {
    const weekday = this.dt.weekday;
    return weekday >= 1 && weekday <= 5;
  }
}

Такой подход решает ключевую проблему — сохранение пользовательских методов на всём протяжении цепочки преобразований.

Расширение через фабричные функции

Фабричные функции позволяют создать слой расширенной функциональности без нарушения архитектуры Luxon.

function createDateTime(dt) {
  return {
    value: dt,

    plus(days) {
      return createDateTime(dt.plus({ days }));
    },

    formatShort() {
      return dt.toFormat('dd.MM.yyyy');
    },

    isWeekend() {
      return dt.weekday === 6 || dt.weekday === 7;
    }
  };
}

const d = createDateTime(DateTime.now());

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

Расширение типов в TypeScript

Luxon активно используется в TypeScript-проектах, где расширение типов достигается через declaration merging и интерфейсные расширения.

Расширение интерфейсов

Хотя классы Luxon нельзя реально модифицировать, можно расширить их типизацию:

declare module 'luxon' {
  interface DateTime {
    isBusinessDay(): boolean;
    toAppFormat(): string;
  }
}

Далее реализуется функция-обёртка или monkey patch:

DateTime.prototype.isBusinessDay = function () {
  return this.weekday >= 1 && this.weekday <= 5;
};

DateTime.prototype.toAppFormat = function () {
  return this.toFormat('dd.MM.yyyy');
};

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

Расширение через утилитарные функции

Наиболее безопасная форма расширения — вынесение логики в отдельные функции, принимающие экземпляры Luxon-типа.

function isBusinessDay(dt) {
  return dt.weekday >= 1 && dt.weekday <= 5;
}

function addBusinessHours(dt, hours) {
  let result = dt;
  let remaining = hours;

  while (remaining > 0) {
    result = result.plus({ hours: 1 });

    if (isBusinessDay(result)) {
      remaining--;
    }
  }

  return result;
}

Этот стиль сохраняет чистоту архитектуры Luxon и исключает побочные эффекты.

Расширение DateTime через обёртки доменной модели

В прикладных системах часто создаются доменные типы поверх Luxon:

  • InvoiceDate
  • BookingDate
  • Deadline
  • ScheduleTime

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

class Deadline {
  constructor(dt) {
    this.dt = dt;
  }

  isOverdue(reference = DateTime.now()) {
    return this.dt < reference;
  }

  extend(days) {
    return new Deadline(this.dt.plus({ days }));
  }
}

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

Расширение Duration

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

class SmartDuration {
  constructor(duration) {
    this.duration = duration;
  }

  toHours() {
    return this.duration.as('hours');
  }

  isLongMeeting() {
    return this.duration.as('minutes') > 60;
  }

  add(other) {
    return new SmartDuration(this.duration.plus(other.duration));
  }
}

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

Расширение Interval

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

class SmartInterval {
  constructor(interval) {
    this.interval = interval;
  }

  overlaps(other) {
    return this.interval.overlaps(other.interval);
  }

  duration() {
    return this.interval.toDuration();
  }

  splitByHours(hours) {
    const result = [];
    let start = this.interval.start;

    while (start < this.interval.end) {
      const end = start.plus({ hours });
      result.push(new SmartInterval(Interval.fromDateTimes(start, end)));
      start = end;
    }

    return result;
  }
}

Работа с кастомными форматами как форма расширения

Расширение типов часто реализуется через стандартизацию форматов представления данных.

const AppFormats = {
  DATE: 'yyyy-MM-dd',
  DATETIME: 'yyyy-MM-dd HH:mm',
  TIME: 'HH:mm:ss'
};

function formatAppDate(dt) {
  return dt.toFormat(AppFormats.DATE);
}

Централизация форматов позволяет расширять поведение без изменения самих типов Luxon.

Интеграция расширений в архитектуру приложения

При масштабировании системы расширенные типы Luxon обычно включаются в слои:

  • domain layer — бизнес-объекты (Deadline, Schedule)
  • application layer — сценарии использования (расчёты, преобразования)
  • infrastructure layer — адаптеры к API, базе данных, внешним сервисам

Luxon остаётся изолированным инструментом работы со временем, а расширения формируют доменную надстройку.

Ограничения и потенциальные проблемы расширений

Расширение типов Luxon связано с рядом системных ограничений:

  • потеря совместимости при обновлениях библиотеки;
  • дублирование логики при использовании разных расширений;
  • риск расхождения поведения между обёртками;
  • усложнение трассировки ошибок из-за дополнительного слоя абстракции;
  • конфликт monkey patching в общих зависимостях.

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