Календари и расписания

Работа с календарями в JavaScript часто сводится к необходимости учитывать не только момент времени, но и способ его представления: локаль, систему летоисчисления, начало недели, формат календаря и правила отображения дат в разных регионах. Luxon предоставляет слой абстракции над стандартным объектом Date, позволяя управлять календарными представлениями без ручной нормализации и сложных преобразований.

В основе Luxon лежит представление времени в виде неизменяемого объекта DateTime. Он хранит:

  • мгновение во времени (timestamp)
  • временную зону
  • локаль
  • правила отображения календаря

Важно различать два уровня:

  • реальное время (instant) — универсальная точка на временной оси
  • календарное представление — способ интерпретации этой точки

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

outputCalendar: переключение календарной системы

Одним из ключевых механизмов работы с календарями является параметр outputCalendar. Он влияет только на отображение даты, не меняя её абсолютного значения.

Пример:

import { DateTime } from "luxon";

const dt = DateTime.now();

dt.setLocale("en").toString();
dt.setLocale("ar").toString();

Более точное управление осуществляется через outputCalendar:

const dt = DateTime.now().set({
  outputCalendar: "gregory"
});

Поддерживаемые календарные системы:

  • gregory — григорианский календарь (по умолчанию)
  • buddhist — буддийский
  • islamic — исламский (Hijri)
  • hebrew — еврейский
  • indian — индийский национальный календарь
  • chinese — китайский календарь

Переключение календаря не изменяет момент времени, а изменяет отображение года, месяца и дня.

Локализация и календарные правила

Календарь тесно связан с локалью. Локаль влияет на:

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

Пример:

const dt = DateTime.now().setLocale("fr");

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

Luxon опирается на Intl API, поэтому поведение соответствует стандартам Unicode CLDR.

Неделя как элемент календарной системы

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

Основные аспекты:

  • первый день недели зависит от локали
  • номер недели вычисляется по ISO или локальным правилам
  • недели могут начинаться в разные дни

Пример получения номера недели:

const dt = DateTime.local(2026, 1, 15);

dt.weekNumber;
dt.weekYear;

ISO-неделя используется по умолчанию в большинстве сценариев серверной логики.

Календарные форматы и отображение дат

Luxon разделяет хранение данных и их отображение. Один и тот же объект можно представить по-разному:

const dt = DateTime.local(2026, 5, 23);

dt.toISO();
dt.toLocaleString();
dt.toFormat("dd LLLL yyyy");

Календарные особенности проявляются особенно ярко при использовании нестандартных систем летоисчисления:

const dt = DateTime.now().set({
  outputCalendar: "islamic"
});

dt.toLocaleString(DateTime.DATE_FULL);

В этом случае год и месяц интерпретируются в соответствии с исламским календарём, хотя внутреннее значение остаётся Unix-based.

Работа с расписаниями через интервалы

Хотя Luxon не предоставляет встроенный scheduler, календарная модель позволяет строить расписания через Interval и Duration.

Interval как основа календарного диапазона

import { DateTime, Interval } from "luxon";

const start = DateTime.local(2026, 5, 1);
const end = DateTime.local(2026, 5, 31);

const interval = Interval.fromDateTimes(start, end);

Интервалы позволяют:

  • проверять пересечения периодов
  • делить календарные диапазоны
  • моделировать расписания событий
interval.contains(DateTime.local(2026, 5, 10));

Построение календарных сеток

Типичная задача календарных приложений — генерация сетки месяца.

Алгоритм:

  1. определить первый день месяца
  2. вычислить начало первой недели
  3. заполнить 5–6 недель датами

Пример:

const start = DateTime.local(2026, 5, 1);
const firstWeek = start.startOf("week");

const days = Array.from({ length: 42 }, (_, i) =>
  firstWeek.plus({ days: i })
);

Такая структура учитывает:

  • смещение недели относительно локали
  • переходы между месяцами
  • календарное выравнивание

Сдвиги календаря: startOf и endOf

Календарные расчёты часто опираются на нормализацию дат:

const dt = DateTime.local(2026, 5, 23);

dt.startOf("day");
dt.endOf("month");
dt.startOf("week");

Поддерживаемые уровни:

  • second
  • minute
  • hour
  • day
  • week
  • month
  • year

Эти операции критичны при построении расписаний и агрегации событий.

Временные зоны и календарные смещения

Календарь невозможно отделить от временной зоны. Один и тот же календарный день может начинаться в разные моменты UTC.

const dt = DateTime.local().setZone("Europe/Paris");

Особенности:

  • смена зоны не меняет момент времени
  • меняется только локальное представление
  • календарные границы (день/неделя/месяц) пересчитываются

При переходе через DST (летнее время) календарные сутки могут содержать 23 или 25 часов, что влияет на расчёт расписаний.

Календарные преобразования и устойчивость данных

При проектировании расписаний важно различать:

  • хранение (ISO timestamp)
  • отображение (calendar system)
  • агрегацию (week/month grouping)

Рекомендуемый подход:

  • хранить данные в UTC
  • отображать через локаль пользователя
  • группировать через startOf

Пример:

const events = [
  DateTime.fromISO("2026-05-23T10:00:00Z"),
  DateTime.fromISO("2026-05-23T15:00:00Z")
];

const grouped = events.reduce((acc, dt) => {
  const key = dt.startOf("day").toISODate();
  acc[key] = (acc[key] || 0) + 1;
  return acc;
}, {});

Календарные системы и бизнес-логика расписаний

При проектировании расписаний важно учитывать:

  • региональные праздники
  • различия в неделях
  • религиозные календари
  • смещение выходных

Luxon позволяет переключать календарное представление, но бизнес-логика обычно требует дополнительного слоя данных поверх календаря.

Пример сценария:

  • события хранятся в UTC
  • отображаются в локальном календаре пользователя
  • фильтрация происходит по startOf("day")
  • недельные отчёты строятся через startOf("week")

Работа с повторяющимися расписаниями

Luxon не содержит встроенного cron-планировщика, но календарная модель позволяет строить повторения вручную:

const base = DateTime.local(2026, 5, 1);

const series = Array.from({ length: 10 }, (_, i) =>
  base.plus({ weeks: i })
);

Для более сложных случаев:

  • месячные циклы через plus({ months: 1 })
  • годовые через plus({ years: 1 })
  • комбинированные правила через фильтрацию дат

Календарные границы и нормализация расписаний

Важный аспект — корректное вычисление границ:

const monthStart = DateTime.local(2026, 5, 23).startOf("month");
const monthEnd = DateTime.local(2026, 5, 23).endOf("month");

Использование таких границ критично при:

  • построении календарных UI
  • подсчёте событий
  • агрегации статистики

Календарные локали и отображение расписаний

Локаль влияет не только на язык, но и на восприятие расписания:

  • формат времени (12/24 часа)
  • порядок даты
  • отображение дней недели
  • сокращения названий
DateTime.local().setLocale("en-GB").toLocaleString();
DateTime.local().setLocale("en-US").toLocaleString();

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

Согласование календаря и пользовательских интерфейсов

При построении календарных интерфейсов используются следующие принципы:

  • неделя строится через startOf("week")
  • месяц — через startOf("month")
  • дни формируются последовательными plus({ days: 1 })
  • события группируются по календарным ключам

Такой подход обеспечивает стабильность при смене локали и временной зоны, так как календарные вычисления базируются на DateTime, а не на сырых timestamp-значениях.