Библиотека Luxon построена вокруг неизменяемых структур данных и
строго определённых типов: DateTime, Duration,
Interval, Info, Settings. Каждый
из этих типов реализует ограниченный, хорошо инкапсулированный набор
операций, ориентированных на безопасную работу с датами и временем.
Ключевая особенность архитектуры заключается в том, что экземпляры объектов immutable. Любая операция возвращает новый объект, не изменяя исходный. Это напрямую влияет на способы расширения функциональности: классическое наследование используется редко и считается вторичным подходом, тогда как композиция и обёртки становятся основными инструментами.
Luxon не проектировался как библиотека, предполагающая активное
наследование классов. Попытка расширить DateTime через
extends формально возможна, но приводит к ряду проблем:
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());
Этот подход часто используется в функциональных архитектурах, где важна предсказуемость и отсутствие классовой иерархии.
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 и исключает побочные эффекты.
В прикладных системах часто создаются доменные типы поверх Luxon:
InvoiceDateBookingDateDeadlineScheduleTimeКаждый из них инкапсулирует 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 часто используется для представления интервалов
времени, и его расширение связано с добавлением бизнес-логики
расчётов.
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 представляет диапазон времени и часто требует
доменных методов проверки пересечений и сегментации.
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 обычно включаются в слои:
Deadline, Schedule)Luxon остаётся изолированным инструментом работы со временем, а расширения формируют доменную надстройку.
Расширение типов Luxon связано с рядом системных ограничений:
По этой причине наиболее устойчивыми считаются композиционные и функциональные подходы, тогда как модификация прототипов используется только в контролируемых средах.