Работа с датой и временем осложняется переходами на летнее и зимнее время. В определённые моменты локальное время может:
Подобные ситуации возникают из-за изменения смещения
(offset) часового пояса.
Например, при переводе часов вперёд часть локального времени пропадает:
| Локальное время | Состояние |
|---|---|
| 01:59 | существует |
| 02:00 | не существует |
| 02:30 | не существует |
| 03:00 | существует |
При переводе часов назад время дублируется:
| Локальное время | Состояние |
|---|---|
| 01:30 | существует |
| 02:00 | существует |
| 02:30 | существует дважды |
| 03:00 | существует |
Luxon содержит встроенные механизмы обработки подобных ситуаций.
Несуществующее время появляется при переходе на летнее время, когда часы переводятся вперёд.
Пример для часового пояса Europe/Berlin:
import { DateTime } from "luxon";
const dt = DateTime.fromObject(
{
year: 2025,
month: 3,
day: 30,
hour: 2,
minute: 30
},
{
zone: "Europe/Berlin"
}
);
console.log(dt.toString());
В ночь перехода на летнее время часы перескакивают с
02:00 сразу на 03:00. Время 02:30
не существует.
Luxon автоматически корректирует значение:
2025-03-30T03:30:00.000+02:00
Библиотека не создаёт ошибку, а переносит время вперёд до ближайшего допустимого момента.
Luxon стремится создавать валидные объекты DateTime.
Поэтому несуществующие значения корректируются автоматически.
Пример:
const dt = DateTime.fromISO(
"2025-03-30T02:15",
{
zone: "Europe/Berlin"
}
);
console.log(dt.toISO());
Результат:
2025-03-30T03:15:00.000+02:00
Фактически Luxon говорит:
«02:15 не существует, ближайшее допустимое время — 03:15».
Для понимания происходящего полезно анализировать смещение
(offset).
const before = DateTime.fromISO(
"2025-03-30T01:30",
{ zone: "Europe/Berlin" }
);
const after = DateTime.fromISO(
"2025-03-30T03:30",
{ zone: "Europe/Berlin" }
);
console.log(before.offset);
console.log(after.offset);
Результат:
60
120
Смещение изменилось с +01:00 на +02:00.
Метод isInDST показывает, находится ли дата в летнем
времени.
console.log(before.isInDST);
console.log(after.isInDST);
Результат:
false
true
Это особенно полезно при:
Неоднозначное время появляется при переводе часов назад.
Например:
const dt = DateTime.fromObject(
{
year: 2025,
month: 10,
day: 26,
hour: 2,
minute: 30
},
{
zone: "Europe/Berlin"
}
);
console.log(dt.toString());
В этот день время 02:30 возникает дважды:
При переходе на зимнее время часы откатываются назад:
02:59 → 02:00
Из-за этого диапазон времени повторяется повторно:
02:00 - 02:59
Следовательно, локальное время уже не уникально.
Luxon выбирает один из возможных offset автоматически.
console.log(dt.offset);
Результат может быть:
120
или:
60
в зависимости от правил временной зоны и конкретной версии данных IANA timezone database.
Можно создать два разных момента времени вручную.
const first = DateTime.fromISO(
"2025-10-26T02:30:00+02:00"
);
const second = DateTime.fromISO(
"2025-10-26T02:30:00+01:00"
);
console.log(first.toUTC().toISO());
console.log(second.toUTC().toISO());
Результат:
2025-10-26T00:30:00.000Z
2025-10-26T01:30:00.000Z
Локальное время одинаковое, но реальные моменты времени отличаются на один час.
Внутри Luxon каждый объект хранит абсолютный timestamp.
console.log(first.toMillis());
console.log(second.toMillis());
Результат:
1761438600000
1761442200000
Разница подтверждает, что это разные моменты времени.
Наиболее надёжный подход — хранение времени в UTC.
const utc = DateTime.utc(
2025,
10,
26,
1,
30
);
console.log(utc.toISO());
Результат:
2025-10-26T01:30:00.000Z
UTC не содержит переходов на летнее и зимнее время, поэтому:
Локальное представление формируется только на этапе отображения.
const local = utc.setZone("Europe/Berlin");
console.log(local.toString());
Это безопаснее, чем хранение локального времени.
При добавлении часов результат может быть неожиданным.
const dt = DateTime.fromISO(
"2025-03-30T01:30",
{
zone: "Europe/Berlin"
}
);
const result = dt.plus({ hours: 1 });
console.log(result.toString());
Результат:
2025-03-30T03:30:00.000+02:00
Вместо 02:30 получилось 03:30, потому что
час 02:* отсутствует.
Luxon работает с абсолютным временем.
const plus60Minutes = dt.plus({ minutes: 60 });
console.log(plus60Minutes.toString());
Результат также будет:
03:30
Фактически произошло увеличение timestamp на 3600 секунд.
const start = DateTime.fromISO(
"2025-03-30T01:00",
{
zone: "Europe/Berlin"
}
);
const end = DateTime.fromISO(
"2025-03-30T04:00",
{
zone: "Europe/Berlin"
}
);
console.log(
end.diff(start, "hours").hours
);
Результат:
2
Хотя визуально прошло три часа, реальное время увеличилось только на два часа.
Interval корректно учитывает переходы DST.
import { Interval } from "luxon";
const interval = Interval.fromDateTimes(
start,
end
);
console.log(interval.length("hours"));
Результат:
2
Некоторые операции могут создавать невалидные значения.
const invalid = DateTime.fromObject({
month: 15
});
console.log(invalid.isValid);
Результат:
false
Информация об ошибке:
console.log(invalid.invalidReason);
console.log(invalid.invalidExplanation);
Luxon использует базу временных зон IANA.
Примеры:
"Europe/Berlin"
"Asia/Tokyo"
"America/New_York"
Нельзя полагаться на фиксированные offset:
+02:00
потому что offset может изменяться:
const winter = DateTime.fromISO(
"2025-01-15",
{
zone: "Europe/Berlin"
}
);
const summer = DateTime.fromISO(
"2025-07-15",
{
zone: "Europe/Berlin"
}
);
console.log(winter.offset);
console.log(summer.offset);
Результат:
60
120
console.log(summer.zoneName);
Результат:
Europe/Berlin
Наиболее безопасный вариант:
setZone().Для расписаний важно учитывать:
Особенно это критично для:
При вычислении длительности нельзя ориентироваться только на визуальные часы.
Следует использовать:
diff()
Interval
toMillis()
Плохой подход:
UTC+3
Правильный подход:
Europe/Moscow
Asia/Almaty
America/New_York
function createUserDateTime(
isoString,
zone
) {
const dt = DateTime.fromISO(
isoString,
{ zone }
);
if (!dt.isValid) {
throw new Error(
dt.invalidExplanation
);
}
return dt;
}
Использование:
const meeting = createUserDateTime(
"2025-10-26T02:30",
"Europe/Berlin"
);
console.log(meeting.toISO());
const ny = DateTime.fromISO(
"2025-07-01T12:00",
{
zone: "America/New_York"
}
);
const tokyo = ny.setZone(
"Asia/Tokyo"
);
console.log(ny.toString());
console.log(tokyo.toString());
Luxon сохраняет один и тот же абсолютный момент времени, изменяя только локальное представление.
toUTC()const utc = ny.toUTC();
Переводит время в UTC.
setZone()const paris = ny.setZone(
"Europe/Paris"
);
Изменяет отображаемую временную зону.
В Luxon необходимо различать два понятия:
2025-10-26 02:30
Может быть неоднозначным.
timestamp
UTC
Unix time
Всегда уникально.
Проблема заключается в том, что локальное время — это отображение, а не физическое время.
При изменении правил часового пояса:
Luxon скрывает большую часть сложности, однако понимание природы DST остаётся критически важным для корректной работы с датами и временем.