Переход на летнее время (DST, Daylight Saving Time) — одна из самых сложных тем при работе с датами и временем. Во многих странах часы переводятся вперёд или назад в определённый день года. Из-за этого возникают:
Библиотека Luxon предоставляет встроенные механизмы для корректной работы с такими ситуациями.
Во время перехода на летнее время локальное время изменяется неравномерно.
Например:
01:59 сразу на
03:00;02:59 снова наступает
02:00.Из-за этого некоторые локальные значения времени:
В зоне 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:30 отсутствует, потому что часы были
переведены вперёд.
Luxon автоматически корректирует значение.
Все DST-операции в Luxon основаны на IANA Time Zone Database.
Примеры зон:
Europe/BerlinAmerica/New_YorkAsia/AlmatyUTCСоздание даты с указанием зоны:
const dt = DateTime.now().setZone("America/New_York");
Без указания зоны используется системная временная зона.
Во время DST смещение относительно UTC изменяется.
Получение offset:
const dt = DateTime.now().setZone("Europe/London");
console.log(dt.offset);
Результат:
0
или:
60
Значение указывается в минутах.
Свойство isInDST показывает, действует ли DST для
текущего объекта.
const dt = DateTime.now().setZone("Europe/Paris");
console.log(dt.isInDST);
Возможные значения:
true
false
UTC не содержит DST.
Поэтому хранение времени в UTC считается наиболее безопасным подходом.
const local = DateTime.local();
const utc = DateTime.utc();
const dt = DateTime.local()
.setZone("UTC");
console.log(dt.toISO());
Весной часы переводятся вперёд.
Например:
01:59 → 03:00
Интервал между 02:00 и 02:59
отсутствует.
const dt = DateTime.fromISO(
"2025-03-30T02:15",
{ zone: "Europe/Berlin" }
);
console.log(dt.toISO());
Luxon нормализует время автоматически.
Некоторые комбинации даты и времени могут быть некорректными.
Проверка:
if (!dt.isValid) {
console.log(dt.invalidReason);
}
Возможные причины:
Осенью часы переводятся назад.
Пример:
02:59 → 02:00
Время между 02:00 и 02:59 возникает
дважды.
Это создаёт неоднозначность.
Рассмотрим:
const dt = DateTime.fromISO(
"2025-10-26T02:30",
{
zone: "Europe/Berlin"
}
);
console.log(dt.toISO());
Такое время может относиться:
Luxon выбирает один из вариантов согласно внутренним правилам зоны.
Наиболее надёжный способ — указывать UTC offset явно.
const dt = DateTime.fromISO(
"2025-10-26T02:30+02:00"
);
console.log(dt.toISO());
или:
const dt = DateTime.fromISO(
"2025-10-26T02:30+01:00"
);
Теперь момент времени определяется однозначно.
DST особенно опасен при сложении часов.
const dt = DateTime.fromISO(
"2025-03-30T00:00",
{ zone: "Europe/Berlin" }
);
const result = dt.plus({ hours: 24 });
console.log(result.toISO());
Из-за перехода итог может отличаться от ожидаемого локального времени.
plus({ hours: 24 }) и plus({ days: 1 })Это принципиально разные операции.
dt.plus({ hours: 24 });
Добавляет фиксированное количество времени.
dt.plus({ days: 1 });
Сохраняет календарную дату.
Пример:
const dt = DateTime.fromISO(
"2025-03-29T12:00",
{ zone: "Europe/Berlin" }
);
console.log(
dt.plus({ hours: 24 }).toISO()
);
console.log(
dt.plus({ days: 1 }).toISO()
);
Результаты могут различаться на один час.
hoursПодходит для:
daysПодходит для:
Luxon предоставляет класс Interval.
import { Interval } from "luxon";
const start = DateTime.fromISO(
"2025-03-30T00:00",
{ zone: "Europe/Berlin" }
);
const end = start.plus({ days: 1 });
const interval = Interval.fromDateTimes(
start,
end
);
console.log(interval.length("hours"));
Продолжительность суток может быть:
DST изменяет длину локальных суток.
23 часа
25 часов
Поэтому нельзя предполагать, что:
1 day === 24 hours
Наиболее безопасная стратегия:
const stored = DateTime.utc();
const local = stored.setZone(
"America/New_York"
);
Такой подход уменьшает количество DST-ошибок.
ISO-формат желательно хранить вместе со смещением.
2025-10-26T02:30:00+02:00
2025-10-26T02:30:00
Во втором случае зона неизвестна.
toUTCconst dt = DateTime.local()
.toUTC();
console.log(dt.toISO());
Результат:
2025-05-23T12:00:00.000Z
Суффикс Z означает UTC.
const utc = DateTime.utc();
const berlin = utc.setZone(
"Europe/Berlin"
);
console.log(berlin.toString());
DST особенно критичен для:
2025-10-26 02:30
Невозможно определить точный момент времени.
Оптимальная схема:
{
"utc": "2025-10-26T00:30:00Z",
"zone": "Europe/Berlin"
}
const a = DateTime.fromISO(
"2025-10-26T02:30+02:00"
);
const b = DateTime.fromISO(
"2025-10-26T02:30+01:00"
);
console.log(a.equals(b));
Результат:
false
Локальное время одинаковое, но реальные моменты разные.
Unix timestamp не зависит от DST.
const ts = Date.now();
const dt = DateTime.fromMillis(ts);
Timestamp представляет абсолютный момент времени.
setZone и toUTCsetZoneМеняет представление времени.
dt.setZone("Asia/Tokyo");
toUTCПереводит объект в UTC.
dt.toUTC();
keepLocalTimeИногда требуется сохранить локальные часы при смене зоны.
const dt = DateTime.local();
const changed = dt.setZone(
"UTC",
{ keepLocalTime: true }
);
Эта операция опасна при DST, потому что может создать неоднозначное время.
DST можно обнаружить через изменение offset.
const before = DateTime.fromISO(
"2025-03-29",
{ zone: "Europe/Berlin" }
);
const after = before.plus({ days: 2 });
console.log(before.offset);
console.log(after.offset);
const dt = DateTime.now();
console.log(dt.zoneName);
Пример:
Europe/Berlin
const dt = DateTime.now()
.setZone("America/New_York");
console.log(
dt.toFormat(
"yyyy-MM-dd HH:mm ZZZZ"
)
);
Пример результата:
2025-05-23 18:20 EDT
При запуске задач по локальному времени возможны проблемы:
02:30 every day
Во время весеннего перехода такого времени не существует.
dt.setZone(userZone)
.toLocaleString(
DateTime.DATETIME_FULL
);
2025-10-26 02:30
без timezone.
const ny = DateTime.now()
.setZone("America/New_York");
const london = DateTime.now()
.setZone("Europe/London");
console.log(ny.offset);
console.log(london.offset);
DST начинается в странах в разные даты, поэтому offset может изменяться независимо.
Durationconst duration = Duration.fromObject({
hours: 24
});
DST влияет на результат применения duration к дате.
const meeting = DateTime.fromObject(
{
year: 2025,
month: 10,
day: 26,
hour: 9
},
{
zone: "Europe/Berlin"
}
);
const nextWeek = meeting.plus({
weeks: 1
});
Календарные операции через weeks, days,
months работают корректнее для расписаний.
const dt = DateTime.now()
.setZone("Invalid/Zone");
console.log(dt.isValid);
InfoLuxon содержит вспомогательный класс Info.
import { Info } from "luxon";
console.log(
Info.hasDST("Europe/Berlin")
);
Результат:
true
Некоторые зоны никогда не используют переходы.
Например:
UTCAsia/DubaiAfrica/NairobiПроверка:
Info.hasDST("UTC");
const start = DateTime.fromISO(
"2025-03-29T12:00",
{ zone: "Europe/Berlin" }
);
const end = DateTime.fromISO(
"2025-03-30T12:00",
{ zone: "Europe/Berlin" }
);
console.log(
end.diff(start, "hours").hours
);
Результат может быть:
23
Используется:
Используется только для отображения интерфейса.
1 day = 24 hours
2025-10-26 02:30
без offset и zone.
86400000
не всегда соответствует суткам.
setZone;