Механизм расширения в Day.js построен на функции extend,
которая внедряет дополнительное поведение в два ключевых объекта:
Каждый плагин представляет собой функцию, принимающую эти сущности и, при необходимости, опции конфигурации. Внутри плагина происходит расширение прототипа, добавление статических методов или регистрация внутренних хелперов.
Ключевая особенность модели — иммутабельность: любой метод, возвращающий дату, создаёт новый экземпляр, не изменяя исходный объект. Это влияет на дизайн плагинов, поскольку все расширения должны сохранять этот принцип.
Минимальная структура плагина Day.js выглядит следующим образом:
export default function myPlugin(option, dayjsClass, dayjsFactory) {
// расширение экземпляра
dayjsClass.prototype.myMethod = function () {
return this.format();
};
// расширение фабрики
dayjsFactory.myStaticMethod = function () {
return dayjsFactory();
};
}
// подключение
import dayjs from 'dayjs';
import myPlugin from './myPlugin';
dayjs.extend(myPlugin);
Внутри доступны три точки расширения:
dayjsClass.prototype — методы экземпляраdayjsFactory — статические методыЧастая задача — вычисление рабочих и выходных дней без внешних библиотек.
export default function businessDaysPlugin(option, dayjsClass) {
const isWeekend = (date) => {
const day = date.day();
return day === 0 || day === 6;
};
dayjsClass.prototype.isBusinessDay = function () {
return !isWeekend(this);
};
dayjsClass.prototype.addBusinessDays = function (count) {
let date = this;
let remaining = count;
while (remaining > 0) {
date = date.add(1, 'day');
if (!isWeekend(date)) {
remaining -= 1;
}
}
return date;
};
dayjsClass.prototype.subtractBusinessDays = function (count) {
let date = this;
let remaining = count;
while (remaining > 0) {
date = date.subtract(1, 'day');
if (!isWeekend(date)) {
remaining -= 1;
}
}
return date;
};
}
Day.jsВ бизнес-приложениях календарь часто отличается от стандартного январского начала года.
export default function fiscalYearPlugin(option, dayjsClass) {
const startMonth = option?.startMonth ?? 0; // 0 = январь
dayjsClass.prototype.fiscalYear = function () {
const month = this.month();
if (month >= startMonth) {
return this.year();
}
return this.year() - 1;
};
dayjsClass.prototype.startOfFiscalYear = function () {
const year = this.fiscalYear();
return this.month(startMonth).date(1).year(year);
};
dayjsClass.prototype.endOfFiscalYear = function () {
return this.startOfFiscalYear().add(1, 'year').subtract(1, 'millisecond');
};
}
Day.js поддерживает кастомизацию через расширение формата.
formatexport default function customFormatPlugin(option, dayjsClass) {
const oldFormat = dayjsClass.prototype.format;
dayjsClass.prototype.format = function (formatStr) {
const customTokens = {
'Q': () => Math.ceil((this.month() + 1) / 3),
'W': () => Math.ceil(this.date() / 7),
};
let result = formatStr;
Object.keys(customTokens).forEach((token) => {
result = result.replace(new RegExp(token, 'g'), customTokens[token].call(this));
});
return oldFormat.call(this, result);
};
}
Добавлены токены:
Q — квартал годаW — неделя месяца (условно)Перехватывается стандартный format
Сохраняется оригинальная реализация через
call
Плагины часто используются для добавления доменной логики прямо в объект даты.
export default function rangeAnalysisPlugin(option, dayjsClass) {
dayjsClass.prototype.isBetween = function (a, b) {
return this.isAfter(a) && this.isBefore(b);
};
dayjsClass.prototype.isPast = function () {
return this.isBefore(dayjsClass());
};
dayjsClass.prototype.isFuture = function () {
return this.isAfter(dayjsClass());
};
}
Иногда требуется хранить конфигурацию, доступную во всех методах плагина.
export default function statefulPlugin(option, dayjsClass) {
const config = {
locale: option?.locale || 'en',
strictMode: option?.strict || false,
};
dayjsClass.prototype.getLocaleConfig = function () {
return config.locale;
};
dayjsClass.prototype.isStrictMode = function () {
return config.strictMode;
};
}
Day.js допускает последовательное расширение функциональности, где каждый плагин накладывается поверх предыдущего.
import dayjs from 'dayjs';
import businessDaysPlugin from './businessDaysPlugin';
import fiscalYearPlugin from './fiscalYearPlugin';
import customFormatPlugin from './customFormatPlugin';
dayjs.extend(businessDaysPlugin);
dayjs.extend(fiscalYearPlugin, { startMonth: 3 });
dayjs.extend(customFormatPlugin);
При этом порядок подключения влияет на итоговое поведение:
dayjs сохраняет единое состояние
расширенийИногда требуется добавить методы, которые поддерживают сложные цепочки операций.
export default function chainPlugin(option, dayjsClass) {
dayjsClass.prototype.addDaysAndFormat = function (days, format) {
return this.add(days, 'day').format(format);
};
dayjsClass.prototype.nextWeekSameDay = function () {
return this.add(7, 'day');
};
}
Day.js
объектПлагины часто опираются на встроенные методы:
addsubtractstartOfendOfformatisBefore / isAfterИспользование этих методов вместо прямых манипуляций с
Date обеспечивает:
export default function diffPlugin(option, dayjsClass) {
dayjsClass.prototype.diffInBusinessDays = function (target) {
let count = 0;
let current = this;
const isWeekend = (d) => d.day() === 0 || d.day() === 6;
while (!current.isSame(target, 'day')) {
current = current.add(1, 'day');
if (!isWeekend(current)) {
count++;
}
}
return count;
};
}