Создание кастомных форматов

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

Ключевой принцип заключается в том, что формат всегда задаётся строкой:

dayjs().format('YYYY-MM-DD HH:mm')

Однако в реальных проектах прямое использование строковых шаблонов быстро приводит к дублированию и снижению поддерживаемости. Поэтому возникает необходимость в создании кастомных форматов как уровня архитектуры приложения.


Базовые токены как строительный материал форматов

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

  • YYYY — полный год
  • YY — последние две цифры года
  • MM — месяц с ведущим нулём
  • M — месяц без ведущего нуля
  • DD — день месяца
  • HH — часы (24-часовой формат)
  • mm — минуты
  • ss — секунды

Кастомный формат в Day.js — это всегда комбинация этих элементов:

dayjs().format('DD/MM/YYYY')
dayjs().format('YYYY год, MM месяц')
dayjs().format('HH:mm:ss | DD.MM.YYYY')

Форматирование не ограничено фиксированными шаблонами, что позволяет строить любые представления даты.


Централизация форматов через константы

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

const DATE_FORMATS = {
  ISO_DATE: 'YYYY-MM-DD',
  READABLE_DATE: 'DD.MM.YYYY',
  FULL_DATETIME: 'YYYY-MM-DD HH:mm:ss',
  TIME_ONLY: 'HH:mm'
}

Использование:

dayjs().format(DATE_FORMATS.ISO_DATE)
dayjs().format(DATE_FORMATS.FULL_DATETIME)

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


Функции-обёртки для кастомных форматов

Следующий уровень абстракции — создание функций, инкапсулирующих форматирование. Это особенно полезно в доменно-ориентированном коде.

const formatDate = (date) => dayjs(date).format('DD.MM.YYYY')
const formatDateTime = (date) => dayjs(date).format('DD.MM.YYYY HH:mm')
const formatTime = (date) => dayjs(date).format('HH:mm')

Такой подход даёт несколько преимуществ:

  • единая точка изменения формата
  • устранение дублирования
  • семантическое выражение намерения

Пример использования:

formatDate(order.createdAt)
formatDateTime(user.lastLoginAt)

Функции становятся фактическим слоем кастомных форматов, скрывающим детали Day.js.


Кастомизация через расширение прототипа

Day.js позволяет расширять функциональность через dayjs.fn, добавляя собственные методы.

import dayjs from 'dayjs'

dayjs.fn.formatDate = function () {
  return this.format('DD.MM.YYYY')
}

dayjs.fn.formatDateTime = function () {
  return this.format('DD.MM.YYYY HH:mm')
}

Использование:

dayjs().formatDate()
dayjs().formatDateTime()

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

Расширение прототипа особенно полезно в крупных кодовых базах, где важно унифицировать стиль работы с датами.


Кастомные форматы через локализацию

Day.js поддерживает локализацию через updateLocale, что позволяет переопределять стандартные представления месяцев, дней недели и относительных выражений.

import dayjs from 'dayjs'
import 'dayjs/locale/ru'

dayjs.locale('ru')

dayjs.updateLocale('ru', {
  months: [
    'Январь', 'Февраль', 'Март', 'Апрель',
    'Май', 'Июнь', 'Июль', 'Август',
    'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
  ],
  weekdays: [
    'Воскресенье', 'Понедельник', 'Вторник',
    'Среда', 'Четверг', 'Пятница', 'Суббота'
  ]
})

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

dayjs().format('D MMMM YYYY')

Здесь кастомизация происходит на уровне языка отображения, а не структуры формата.


Расширенные форматы через плагины

Некоторые форматы становятся доступны только после подключения дополнительных плагинов, например advancedFormat.

import advancedFormat from 'dayjs/plugin/advancedFormat'
dayjs.extend(advancedFormat)

После расширения появляются дополнительные токены:

  • Qo — квартал
  • Do — день месяца с порядковым суффиксом
  • gggg — ISO-неделя года

Пример:

dayjs().format('Do MMMM YYYY')

Такая система расширяет возможности кастомных форматов без изменения ядра библиотеки.


Композиция форматов как архитектурный приём

В сложных системах форматы часто комбинируются в зависимости от контекста. Вместо одного фиксированного шаблона используются составные правила.

const buildFormat = ({ date, withTime }) => {
  if (withTime) {
    return dayjs(date).format('DD.MM.YYYY HH:mm')
  }
  return dayjs(date).format('DD.MM.YYYY')
}

Другой вариант — словарь условий:

const FORMAT_BY_TYPE = {
  short: 'DD.MM.YYYY',
  long: 'DD MMMM YYYY',
  datetime: 'DD.MM.YYYY HH:mm:ss'
}

const formatByType = (date, type) => {
  return dayjs(date).format(FORMAT_BY_TYPE[type])
}

Композиция форматов позволяет адаптировать отображение даты под сценарий использования без изменения логики библиотеки.


Условное формирование строк формата

Day.js не ограничивает динамическую сборку строк формата. Шаблон может быть сформирован во время выполнения:

const getFormat = (showSeconds) => {
  let base = 'HH:mm'
  if (showSeconds) {
    base += ':ss'
  }
  return base
}

dayjs().format(getFormat(true))

Также возможно использование параметрических форматов:

const formatDate = (date, separator = '.') => {
  const format = `DD${separator}MM${separator}YYYY`
  return dayjs(date).format(format)
}

Такая гибкость делает систему форматов полностью динамической.


Изоляция логики форматирования

В масштабируемых приложениях форматирование даты рассматривается как отдельный слой. Day.js используется только как движок преобразования, а вся логика форматов выносится наружу.

const DatePresenter = {
  iso: (d) => dayjs(d).format('YYYY-MM-DD'),
  ui: (d) => dayjs(d).format('DD.MM.YYYY'),
  full: (d) => dayjs(d).format('DD.MM.YYYY HH:mm:ss')
}

Использование:

DatePresenter.ui(invoice.date)
DatePresenter.full(event.timestamp)

Такой подход снижает связанность кода с конкретной библиотекой и упрощает замену форматтера при необходимости.


Безопасность и предсказуемость кастомных форматов

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

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

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


Унификация форматов в масштабируемых приложениях

В крупных системах форматы часто выносятся в отдельный модуль, который становится единственным источником истины:

export const FORMATS = {
  API: 'YYYY-MM-DDTHH:mm:ssZ',
  USER: 'DD.MM.YYYY',
  LOG: 'YYYY-MM-DD HH:mm:ss',
  TIME: 'HH:mm'
}

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


Абстракция над Day.js как итоговая форма кастомизации

Наиболее зрелая форма кастомных форматов заключается в полной изоляции Day.js за собственным API:

import dayjs from 'dayjs'

const DateService = {
  format(date, type = 'USER') {
    const formats = {
      USER: 'DD.MM.YYYY',
      API: 'YYYY-MM-DDTHH:mm:ssZ',
      TIME: 'HH:mm'
    }
    return dayjs(date).format(formats[type])
  }
}

Использование:

DateService.format(order.createdAt, 'USER')
DateService.format(new Date(), 'TIME')

В этой модели Day.js становится внутренним механизмом, а кастомные форматы — частью бизнес-логики приложения, а не просто строковыми шаблонами.