Примеры кастомных плагинов

Механизм расширения в Day.js построен на функции extend, которая внедряет дополнительное поведение в два ключевых объекта:

  • dayjs class — конструктор, отвечающий за создание экземпляров
  • dayjs factory — функция-обёртка, через которую создаются новые даты

Каждый плагин представляет собой функцию, принимающую эти сущности и, при необходимости, опции конфигурации. Внутри плагина происходит расширение прототипа, добавление статических методов или регистрация внутренних хелперов.

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


Базовый шаблон плагина

Минимальная структура плагина 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 для вычислений

Плагин пользовательских токенов форматирования

Day.js поддерживает кастомизацию через расширение формата.

Расширение format

export 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 объект
  • Поддерживается цепочное построение логики
  • Минимальное вмешательство в базовую модель

Интеграция с внутренними методами Day.js

Плагины часто опираются на встроенные методы:

  • add
  • subtract
  • startOf
  • endOf
  • format
  • isBefore / 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;
  };
}

Поведение

  • вычисление происходит по календарным дням
  • выходные исключаются
  • результат — чистое число без побочных эффектов