В библиотеке Luxon выбор единиц измерения определяется тем, как именно представляется время: как абсолютное количество миллисекунд или как календарная величина, зависящая от контекста. Архитектура работы с временем в Luxon строится вокруг двух уровней — машинного времени и календарных единиц, и различие между ними критично для корректного моделирования дат и длительностей.
В основе временных вычислений лежат абсолютные единицы, которые однозначно интерпретируются в любой системе отсчёта:
Эти единицы линейны и не зависят от календаря. Например, 1 день всегда равен 86 400 000 миллисекунд в абстрактном времени, если не учитывать переходы на летнее время.
Противоположностью являются календарные единицы:
Их ключевая особенность — переменная длительность. Один месяц может содержать 28, 29, 30 или 31 день, а год — 365 или 366 дней. Поэтому такие единицы не могут быть напрямую сведены к фиксированному числу миллисекунд без привязки к конкретной дате.
В Luxon длительности представлены через объект Duration, который позволяет явно задавать единицы измерения:
import { Duration } from 'luxon';
const d1 = Duration.fromObject({ hours: 2, minutes: 30 });
const d2 = Duration.fromObject({ days: 1, hours: 6 });
const d3 = Duration.fromObject({ months: 2, weeks: 1 });
Каждый объект хранит набор полей-единиц, а не единую числовую величину. Это фундаментальное отличие от простого хранения времени в миллисекундах.
При работе с несколькими единицами Luxon выполняет нормализацию, если требуется привести длительность к каноническому виду:
const dur = Duration.fromObject({
seconds: 90,
minutes: 2
}).normalize();
Результат нормализации:
Нормализация важна при смешивании единиц разного уровня, особенно при последующих операциях сравнения или форматирования.
Метод shiftTo позволяет пересчитать длительность в
выбранные единицы, перераспределяя значение:
const d = Duration.fromObject({ seconds: 3661 });
const shifted = d.shiftTo('hours', 'minutes', 'seconds');
Здесь происходит разложение:
Важно, что shiftTo не изменяет смысл времени, а лишь
перераспределяет его между единицами.
Для извлечения числового значения используется метод
as:
const d = Duration.fromObject({ minutes: 3 });
d.as('seconds'); // 180
d.as('hours'); // 0.05
as возвращает эквивалент в выбранной единице, приводя
все внутренние поля к единой шкале.
При переходе от крупных единиц к мелким и обратно возникает эффект округления:
Поэтому преобразования через календарные единицы всегда контекстно зависимы.
Метод diff в Luxon возвращает разницу между датами с
указанием единиц:
import { DateTime } from 'luxon';
const a = DateTime.fromISO('2024-01-01');
const b = DateTime.fromISO('2024-02-01');
b.diff(a, 'days').days;
b.diff(a, 'months').months;
Выбор единицы влияет на результат:
days — точная разница в суткахmonths — календарная разница, зависящая от структуры
месяцевПри множественных единицах:
b.diff(a, ['days', 'hours']);
формируется составная длительность.
Любая длительность в конечном счёте может быть сведена к миллисекундам:
const d = Duration.fromObject({ minutes: 1 });
d.toMillis(); // 60000
Однако обратное преобразование из миллисекунд в календарные единицы неоднозначно:
Поэтому Luxon разделяет:
При сериализации используется ISO 8601:
const d = Duration.fromObject({
hours: 1,
minutes: 15
});
d.toISO(); // PT1H15M
ISO-формат сохраняет структуру единиц, а не их числовое сведение. Это позволяет избежать неоднозначности при восстановлении объекта.
При создании длительности порядок единиц не влияет на результат:
Duration.fromObject({ minutes: 1, seconds: 30 });
Duration.fromObject({ seconds: 30, minutes: 1 });
Обе конструкции идентичны, так как данные хранятся в виде словаря единиц.
Однако при нормализации происходит перераспределение от меньших единиц к большим, если это возможно.
Календарные единицы требуют учёта даты отсчёта:
const d = Duration.fromObject({ months: 1 });
d.shiftTo('days').days;
Результат зависит от базовой даты, поскольку:
Это делает такие единицы зависимыми от временного контекста DateTime.
Выбор единиц в Luxon определяется типом задачи:
Чем более детерминированной должна быть логика, тем ближе единицы к абсолютной шкале времени.
Месяцы и годы создают неоднозначные ситуации:
В таких случаях Luxon не может использовать фиксированное преобразование, и результат зависит от начальной точки времени.
Длительности могут одновременно содержать несколько уровней:
const d = Duration.fromObject({
days: 2,
hours: 5,
minutes: 10
});
Такая структура позволяет сохранить семантику времени без потери информации при преобразованиях между единицами.
При этом внутреннее представление остаётся числовым только на уровне миллисекунд, а логическая структура сохраняется отдельно.