Создание вспомогательных функций

Библиотека js-joda строится вокруг неизменяемых (immutable) объектов времени: LocalDate, LocalTime, LocalDateTime, Instant, ZonedDateTime, Duration, Period. Их прямое использование в прикладном коде быстро приводит к повторению однотипных операций: форматирование дат, конвертация между типами, проверка валидности входных значений, работа с часовыми поясами, вычисление бизнес-границ (конец дня, начало недели, последний день месяца).

Вспомогательные функции в таком контексте формируют слой абстракции между доменной логикой и низкоуровневым API библиотеки. Этот слой не изменяет поведение js-joda, но стандартизирует доступ к операциям времени и снижает связность кода.


Принципы построения вспомогательного слоя

Вспомогательные функции над js-joda обычно подчиняются нескольким устойчивым принципам:

1. Иммутабельность как основа Все функции принимают и возвращают новые экземпляры объектов времени без побочных эффектов.

2. Явное разделение ответственности Функция либо форматирует, либо парсит, либо преобразует — без смешивания задач.

3. Детерминированность Одинаковые входные данные всегда дают одинаковый результат, без скрытых зависимостей от локального состояния.

4. Изоляция внешних форматов Строки, числа Unix epoch, JSON-даты преобразуются на границе системы, а не внутри бизнес-логики.


Базовые обёртки над созданием дат

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

import { LocalDate, Instant, ZoneId, ZonedDateTime } from '@js-joda/core';

const DEFAULT_ZONE = ZoneId.of('UTC');

function nowInstant() {
  return Instant.now();
}

function nowZoned(zone = DEFAULT_ZONE) {
  return ZonedDateTime.now(zone);
}

function today(zone = DEFAULT_ZONE) {
  return LocalDate.now(zone);
}

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


Функции парсинга и безопасного преобразования

Парсинг строковых дат — один из наиболее частых источников ошибок. Вспомогательный слой обычно включает безопасные функции, возвращающие null или Result-подобные структуры вместо выбрасывания исключений.

import { LocalDate, DateTimeParseException } from '@js-joda/core';

function parseLocalDate(value) {
  try {
    return LocalDate.parse(value);
  } catch (e) {
    if (e instanceof DateTimeParseException) {
      return null;
    }
    throw e;
  }
}

Расширенный вариант с поддержкой нескольких форматов:

function parseLocalDateFlexible(value, parsers = []) {
  for (const parser of parsers) {
    try {
      return parser(value);
    } catch (_) {}
  }
  return null;
}

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

const date = parseLocalDateFlexible('2026-01-25', [
  LocalDate.parse,
  (v) => LocalDate.parse(v, DateTimeFormatter.ISO_DATE)
]);

Унификация форматирования

Форматирование дат в единый стиль особенно важно в API и UI-слое. js-joda предоставляет DateTimeFormatter, но его использование часто инкапсулируется.

import { DateTimeFormatter } from '@js-joda/core';

const ISO_DATE = DateTimeFormatter.ISO_DATE;
const ISO_DATETIME = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

function formatDate(date, formatter = ISO_DATE) {
  return date.format(formatter);
}

Дополнительный слой может фиксировать корпоративные форматы:

const FORMATTERS = {
  shortDate: DateTimeFormatter.ofPattern('dd.MM.yyyy'),
  fullDateTime: DateTimeFormatter.ofPattern('dd.MM.yyyy HH:mm')
};

function format(value, type = 'shortDate') {
  return value.format(FORMATTERS[type]);
}

Конвертация между типами времени

В js-joda разные типы времени имеют строгие границы. Вспомогательные функции устраняют необходимость повторного написания однотипных преобразований.

import { Instant, ZoneId, LocalDateTime } from '@js-joda/core';

function instantToLocalDateTime(instant, zone = ZoneId.of('UTC')) {
  return LocalDateTime.ofInstant(instant, zone);
}

function localDateTimeToInstant(localDateTime, zone = ZoneId.of('UTC')) {
  return localDateTime.atZone(zone).toInstant();
}

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


Работа с границами времени

Бизнес-логика часто требует вычисления начала и конца периода: дня, недели, месяца.

import { LocalDate, LocalTime } from '@js-joda/core';

function startOfDay(date) {
  return date.atStartOfDay();
}

function endOfDay(date) {
  return date.atTime(LocalTime.MAX);
}

Расширение для недели:

import { DayOfWeek } from '@js-joda/core';

function startOfWeek(date, startDay = DayOfWeek.MONDAY) {
  const diff = date.dayOfWeek().value() - startDay.value();
  return date.minusDays((diff + 7) % 7);
}

function endOfWeek(date, startDay = DayOfWeek.MONDAY) {
  return startOfWeek(date, startDay).plusDays(6);
}

Доменные функции поверх времени

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

Пример: расчёт возраста по дате рождения.

import { Period, LocalDate } from '@js-joda/core';

function calculateAge(birthDate, currentDate = LocalDate.now()) {
  return Period.between(birthDate, currentDate).years();
}

Пример: проверка принадлежности к диапазону дат.

function isBetween(date, start, end) {
  return !date.isBefore(start) && !date.isAfter(end);
}

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


Композиция функций и функциональный стиль

Вспомогательные функции над js-joda легко композиционно комбинируются.

function pipe(...fns) {
  return (value) => fns.reduce((acc, fn) => fn(acc), value);
}

const normalizeDate = pipe(
  parseLocalDate,
  (d) => d?.plusDays(1) ?? null
);

Композиция позволяет строить цепочки преобразований без мутации состояния.


Каррирование и частичное применение

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

function addDays(days) {
  return (date) => date.plusDays(days);
}

const addBusinessWeek = addDays(5);

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


Валидация временных значений

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

function assertValidLocalDate(date) {
  if (!date || typeof date.isBefore !== 'function') {
    throw new Error('Invalid LocalDate');
  }
  return date;
}

Более мягкий вариант:

function isValidLocalDate(date) {
  return date && typeof date.isBefore === 'function';
}

Обработка временных зон

Работа с зонами требует строгой дисциплины. Вспомогательные функции помогают централизовать правила конвертации.

import { ZoneId, ZonedDateTime, Instant } from '@js-joda/core';

function toZone(instant, zoneId = 'UTC') {
  return instant.atZone(ZoneId.of(zoneId));
}

function fromZone(zonedDateTime) {
  return zonedDateTime.toInstant();
}

Централизация этих операций снижает риск расхождений в интерпретации времени между слоями приложения.


Стандартизация Unix времени

Во многих API требуется работа с epoch-значениями.

function toEpochMillis(instant) {
  return instant.toEpochMilli();
}

function fromEpochMillis(ms) {
  return Instant.ofEpochMilli(ms);
}

Такие функции образуют границу между js-joda и внешними системами хранения и передачи данных.


Расширяемые утилиты для диапазонов

Работа с временными интервалами часто требует собственных структур.

function createDateRange(start, end) {
  return { start, end };
}

function contains(range, date) {
  return isBetween(date, range.start, range.end);
}

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


Тестируемость вспомогательных функций

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

Пример тестируемого поведения:

const d = LocalDate.of(2026, 1, 1);
const result = addDays(10)(d);

result.toString(); // 2026-01-11

Отсутствие скрытых зависимостей делает функции детерминированными и предсказуемыми.


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

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

  • date-create.js — создание дат и текущего времени
  • date-parse.js — парсинг строк
  • date-format.js — форматирование
  • date-convert.js — преобразования типов
  • date-range.js — работа с диапазонами
  • date-domain.js — бизнес-логика

Такая структура снижает когнитивную нагрузку и облегчает сопровождение кода.


Инкапсуляция сложных правил

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

Пример: определение рабочей даты (исключая выходные).

function isWeekend(date) {
  const day = date.dayOfWeek().value();
  return day === 6 || day === 7;
}

function nextBusinessDay(date) {
  let next = date.plusDays(1);
  while (isWeekend(next)) {
    next = next.plusDays(1);
  }
  return next;
}

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