Основой обратного отсчёта в Luxon служит работа с двумя сущностями:
моментом времени (DateTime) и промежутком
(Duration). Вместо ручных вычислений разницы через
Date и миллисекунды библиотека предоставляет
высокоуровневые операции, учитывающие часовые пояса, локали и корректное
поведение при переходе через границы суток, месяцев и лет.
Любой таймер обратного отсчёта сводится к вычислению разницы между текущим моментом и целевой датой:
Luxon предоставляет для этого класс DateTime:
import { DateTime } from "luxon";
const target = DateTime.fromISO("2026-12-31T23:59:59");
const now = DateTime.now();
const diff = target.diff(now);
diff возвращает объект Duration, содержащий
разницу между моментами времени в нескольких единицах измерения
одновременно.
Объект Duration можно привести к нужным единицам через
нормализацию:
const diff = target.diff(DateTime.now(), ["days", "hours", "minutes", "seconds"]);
Результат уже структурирован: дни, часы, минуты и секунды доступны напрямую:
diff.days;
diff.hours;
diff.minutes;
diff.seconds;
Однако важно учитывать, что Duration в Luxon хранит
дробные значения, поэтому для отображения обычно выполняется
округление:
const seconds = Math.floor(diff.seconds);
Обратный отсчёт в реальном времени строится на регулярном пересчёте:
import { DateTime } from "luxon";
const target = DateTime.fromISO("2026-12-31T23:59:59");
setInterval(() => {
const now = DateTime.now();
const diff = target.diff(now, ["days", "hours", "minutes", "seconds"]);
const days = Math.floor(diff.days);
const hours = Math.floor(diff.hours);
const minutes = Math.floor(diff.minutes);
const seconds = Math.floor(diff.seconds);
console.log(`${days}d ${hours}h ${minutes}m ${seconds}s`);
}, 1000);
Такая реализация уже функциональна, но имеет одну особенность:
интервал 1000 мс не гарантирует синхронизацию с системным
временем, что приводит к накоплению дрейфа.
JavaScript-таймеры не являются точными часами. Более стабильный подход — вычислять оставшееся время каждый тик строго от текущего момента до цели, не полагаясь на инкременты:
setInterval(() => {
const diff = target.diff(DateTime.now(), ["days", "hours", "minutes", "seconds"]);
const totalSeconds = Math.max(0, diff.as("seconds"));
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
console.log({ days, hours, minutes, seconds });
}, 1000);
Использование diff.as("seconds") позволяет получить
абсолютное количество секунд и избежать накопления ошибок округления
между тиками.
Если целевая дата уже прошла, diff становится
отрицательным. Luxon сохраняет это поведение, что полезно для аналитики,
но неудобно для отображения таймера.
Обычно вводится защита:
const diff = target.diff(DateTime.now(), ["days", "hours", "minutes", "seconds"]);
const total = diff.as("seconds");
if (total <= 0) {
console.log("Время истекло");
} else {
console.log(Math.floor(total));
}
Duration для форматированияDuration можно преобразовать в человекочитаемую
форму:
const diff = target.diff(DateTime.now());
const duration = diff.shiftTo("days", "hours", "minutes", "seconds");
console.log(duration.toObject());
Метод shiftTo перераспределяет значение в удобные
единицы, устраняя избыточные доли.
Пример результата:
{
days: 12,
hours: 5,
minutes: 40,
seconds: 10.532
}
Это особенно важно для UI, где требуется аккуратное отображение компонентов времени.
Luxon изначально ориентирован на работу с временными зонами через
DateTime:
const target = DateTime.fromISO("2026-12-31T23:59:59", {
zone: "Europe/Moscow"
});
Текущий момент также зависит от локальной зоны окружения:
const now = DateTime.now();
Если таймер должен быть глобально согласованным (например, сервер + клиенты), используется UTC:
const target = DateTime.fromISO("2026-12-31T23:59:59Z");
const now = DateTime.utc();
Использование UTC исключает расхождения из-за локальных смещений.
В типичной архитектуре целевая дата фиксируется один раз, а текущее время постоянно обновляется:
const target = DateTime.fromISO("2026-12-31T23:59:59Z");
function getRemaining() {
const now = DateTime.utc();
const diff = target.diff(now, ["hours", "minutes", "seconds"]);
return diff;
}
Такой подход делает таймер детерминированным: любое количество вызовов функции даёт согласованный результат.
Luxon не навязывает формат вывода, но предоставляет базовые инструменты для локализации:
const diff = target.diff(DateTime.now()).shiftTo("days", "hours", "minutes", "seconds");
const d = Math.floor(diff.days);
const h = Math.floor(diff.hours);
const m = Math.floor(diff.minutes);
const s = Math.floor(diff.seconds);
const formatted =
String(d).padStart(2, "0") + ":" +
String(h).padStart(2, "0") + ":" +
String(m).padStart(2, "0") + ":" +
String(s).padStart(2, "0");
Такой формат часто используется в интерфейсах обратного отсчёта.
until через IntervalХотя Luxon не имеет прямого метода “countdown”, логически его
заменяет Interval:
import { Interval, DateTime } from "luxon";
const start = DateTime.now();
const end = DateTime.fromISO("2026-12-31T23:59:59");
const interval = Interval.fromDateTimes(start, end);
Интервал можно пересчитывать динамически, но чаще он используется как фиксированная модель диапазона времени.
Частое обновление DOM или консоли может быть избыточным. Обычно оптимизируют:
Синхронизация с границей секунды:
const delay = 1000 - DateTime.now().millisecond;
setTimeout(() => {
setInterval(update, 1000);
}, delay);
Это снижает визуальные скачки таймера.
Luxon всегда опирается на системные часы. Если пользователь изменяет время устройства, обратный отсчёт мгновенно пересчитывается. Это не ошибка библиотеки, а отражение реальности.
Для критически точных сценариев (например, серверные таймеры) используется серверное время как источник истины:
const serverNow = DateTime.fromMillis(serverTimestamp);
const diff = target.diff(serverNow);
По умолчанию Luxon сохраняет точность до миллисекунд. Это может приводить к «дрожанию» значений при отображении:
diff.seconds
Для стабильного UI обычно используется явное округление:
Math.floor(diff.seconds);
или даже полное игнорирование миллисекунд:
const diff = target.diff(DateTime.now()).set({ milliseconds: 0 });
В реактивных системах логика таймера выносится в вычисляемое состояние:
function getCountdown(targetISO) {
const target = DateTime.fromISO(targetISO);
return function tick() {
const diff = target.diff(DateTime.now(), ["days", "hours", "minutes", "seconds"]);
return {
days: Math.floor(diff.days),
hours: Math.floor(diff.hours),
minutes: Math.floor(diff.minutes),
seconds: Math.floor(diff.seconds)
};
};
}
Такой подход позволяет абстрагировать таймер от конкретного механизма рендера.
Luxon поддерживает множество входных форматов:
DateTime.fromISO(...)
DateTime.fromRFC2822(...)
DateTime.fromHTTP(...)
DateTime.fromMillis(...)
Для обратного отсчёта важно, чтобы целевая дата была строго нормализована:
const target = DateTime.fromMillis(Date.parse("2026-12-31T23:59:59Z"));
Неправильный парсинг может сместить таймер на несколько часов из-за локальной зоны.
Концептуально корректная модель обратного отсчёта в Luxon всегда строится на трёх принципах:
DateTime;DateTime на каждом шаге;Duration через diff без
накопительных состояний.Любые попытки хранить «остаток времени» как изменяемую переменную приводят к рассинхронизации при долгом выполнении и дрейфу, который постепенно накапливается даже при идеальных интервалах.