Пошук уроків, статей та іншого контенту
З’ясуєте, як часові пояси, літній час і локальні переходи впливають на зберігання та обчислення часу.
Під час роботи з часом потрібно розрізняти щонайменше три поняття:
момент часу (instant) — конкретна точка на часовій шкалі;
часовий пояс — правила перетворення моментів часу на локальні дату й час;
локальний час (wall-clock time) — значення, яке бачить користувач на годиннику.
Наприклад, момент:
2025-07-01T12:00:00Zможе відображатися так:
у Києві — 15:00;
у Лондоні — 13:00;
у Нью-Йорку — 08:00.
Момент залишається тим самим, змінюється лише його представлення в конкретному часовому поясі.
UTC — це глобальна шкала координованого часу. Позначення Z у рядку ISO 8601 означає UTC:
2025-01-15T12:00:00ZТакож можна вказати числове зміщення:
2025-01-15T14:00:00+02:00Обидва рядки описують момент часу, але лише за умови правильного зміщення:
2025-01-15T12:00:00Z
2025-01-15T14:00:00+02:00Це один і той самий момент.
Водночас назва часового поясу та його зміщення — не те саме:
+02:00 — лише зміщення в конкретний момент;
Europe/Kyiv — набір історичних і майбутніх правил для регіону.
Зміщення може змінюватися через літній час або рішення уряду. Тому для регулярних подій і майбутніх дат краще зберігати назву часового поясу, наприклад Europe/Kyiv, а не лише +02:00.
Date у JavaScriptОб’єкт Date всередині зберігає число мілісекунд від Unix epoch:
1970-01-01T00:00:00ZТобто Date зберігає момент часу, а не часовий пояс.
const instant = new Date("2025-07-01T12:00:00Z");
console.log(instant.getTime());
console.log(instant.toISOString());
console.log(instant.toString());Метод toISOString() завжди повертає значення в UTC:
2025-07-01T12:00:00.000ZМетод toString() використовує локальний часовий пояс середовища, у якому виконується код. Тому його результат може відрізнятися на комп’ютері розробника, сервері та в браузері користувача.
Найбезпечніший формат для моменту часу — ISO-рядок із Z або явним зміщенням:
const first = new Date("2025-07-01T12:00:00Z");
const second = new Date("2025-07-01T14:00:00+02:00");
console.log(first.getTime() === second.getTime()); // trueНеоднозначні рядки створюють проблеми:
const dateOnly = new Date("2025-07-01");
const localDateTime = new Date("2025-07-01T12:00:00");У JavaScript:
рядок лише з датою, "2025-07-01", трактується як UTC;
дата й час без зміщення, "2025-07-01T12:00:00", трактується як локальний час середовища.
Це означає, що однаковий рядок може дати різний момент залежно від часового поясу машини:
const value = new Date("2025-07-01T12:00:00");
console.log(value.toISOString());Для даних API краще явно вказувати часову зону:
const utcValue = new Date("2025-07-01T12:00:00Z");
const offsetValue = new Date("2025-07-01T12:00:00+03:00");Для форматування використовуйте Intl.DateTimeFormat. Він підтримує часові пояси з бази IANA, наприклад Europe/Kyiv, America/New_York або Asia/Tokyo.
const instant = new Date("2025-07-01T12:00:00Z");
const zones = [
"UTC",
"Europe/Kyiv",
"Europe/London",
"America/New_York",
"Asia/Tokyo",
];
for (const timeZone of zones) {
const formatter = new Intl.DateTimeFormat("uk-UA", {
timeZone,
dateStyle: "full",
timeStyle: "long",
});
console.log(`${timeZone}: ${formatter.format(instant)}`);
}Важливо: timeZone визначає лише спосіб відображення. Він не змінює сам Date.
Зручно зберігати момент у UTC, а форматувати його безпосередньо перед показом користувачеві:
function formatInTimeZone(isoString, timeZone) {
const date = new Date(isoString);
if (Number.isNaN(date.getTime())) {
throw new Error("Некоректний момент часу");
}
return new Intl.DateTimeFormat("uk-UA", {
timeZone,
dateStyle: "medium",
timeStyle: "short",
}).format(date);
}
console.log(
formatInTimeZone("2025-07-01T12:00:00Z", "Europe/Kyiv")
);Літній час — це сезонна зміна локального зміщення. Наприклад, у певний період часовий пояс може мати зміщення UTC+02:00, а в інший — UTC+03:00.
Це впливає на:
відображення моментів;
тривалість проміжків;
створення локальних дат;
повторювані події;
розклад зустрічей;
дедлайни.
Часовий пояс Europe/Kyiv містить правила, за якими JavaScript визначає потрібне зміщення для конкретної дати:
const winter = new Date("2025-01-15T12:00:00Z");
const summer = new Date("2025-07-15T12:00:00Z");
const formatter = new Intl.DateTimeFormat("uk-UA", {
timeZone: "Europe/Kyiv",
dateStyle: "medium",
timeStyle: "long",
});
console.log(formatter.format(winter));
console.log(formatter.format(summer));Не варто вручну додавати 2 або 3 години. Таке рішення не враховує:
літній час;
історичні зміни правил;
різні правила в інших регіонах;
майбутні зміни законодавства.
Під час переходу на літній час годинник зазвичай переводять уперед. Частина локального часу зникає.
Наприклад, після 01:59:59 може настати 03:00:00. Локальний час 02:30 у такому разі не існує.
Під час переходу на зимовий час годинник переводять назад. Частина часу повторюється:
01:59:59
01:00:00Тому локальний час 01:30 може відповідати двом різним моментам. Рядок без часового поясу не містить достатньо інформації, щоб однозначно визначити момент.
Це одна з причин, чому такі значення небезпечно передавати між системами:
2025-10-26 01:30Краще передавати момент із часовою зоною:
2025-10-26T01:30:00+03:00або зберігати окремо:
{
"localDateTime": "2025-10-26T01:30:00",
"timeZone": "Europe/Kyiv"
}Але для повтореного часу може знадобитися додаткова інформація про те, яке саме входження обрано.
Доба не завжди дорівнює 24 годинам у локальному календарі. У день переходу на літній час локальний день може тривати 23 години, а під час повернення до стандартного часу — 25 годин.
Розгляньмо різницю між додаванням мілісекунд і зміною календарної дати:
const start = new Date("2025-03-29T12:00:00+01:00");
const after24Hours = new Date(start.getTime() + 24 * 60 * 60 * 1000);
const nextCalendarDay = new Date(start);
nextCalendarDay.setDate(nextCalendarDay.getDate() + 1);
console.log("Початок:", start.toISOString());
console.log("Через 24 години:", after24Hours.toISOString());
console.log("Наступний календарний день:", nextCalendarDay.toISOString());Ці операції мають різний зміст:
getTime() + 24 * 60 * 60 * 1000 додає рівно 24 години до моменту;
setDate(getDate() + 1) змінює локальну календарну дату в часовому поясі середовища.
Тому потрібно спочатку визначити вимогу:
«через рівно 24 години» — працюємо з тривалістю;
«завтра о тій самій локальній годині» — працюємо з календарною датою та часовим поясом.
Період дії підписки, таймер або максимальний час очікування зазвичай є тривалістю:
const startedAt = Date.now();
const timeout = 30 * 60 * 1000;
const expiresAt = startedAt + timeout;
console.log(new Date(expiresAt).toISOString());Для таких обчислень зручно використовувати UTC-моменти або мілісекунди. Літній час не повинен змінювати тривалість сесії на 30 хвилин чи термін дії токена.
Натомість подія «щодня о 09:00 за київським часом» є календарним правилом. Для неї важливо зберегти:
локальний час 09:00;
часовий пояс Europe/Kyiv;
правила повторення.
Якщо зберегти лише один UTC-момент і додавати по 24 години, подія може почати відбуватися о 10:00 або 08:00 за локальним часом після переходу на літній чи зимовий час.
Вибір формату залежить від змісту даних.
Наприклад:
час створення замовлення;
час входу користувача;
час надсилання повідомлення.
Зберігайте момент у UTC:
2025-07-01T12:00:00.000ZНаприклад:
зустріч о 09:00 за київським часом;
рейс у локальному часі аеропорту;
щоденне відкриття магазину.
Зберігайте локальну дату й час разом із назвою часового поясу:
{
"localDateTime": "2025-07-01T09:00:00",
"timeZone": "Europe/Kyiv"
}Наприклад:
день народження;
дата завершення навчання;
святковий день.
Це не момент часу. Зберігайте значення як дату без перетворення в UTC:
2025-07-01Якщо перетворити таку дату на Date, у користувача в іншому часовому поясі вона може відобразитися як попередній або наступний день.
У браузері можна отримати часовий пояс за замовчуванням:
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(userTimeZone);Результат може бути, наприклад:
Europe/KyivОднак цей параметр не слід використовувати як єдине джерело правди для бізнес-логіки:
користувач може змінити часовий пояс операційної системи;
сервер і браузер можуть перебувати в різних зонах;
часовий пояс користувача не завжди збігається з часовим поясом події.
Зазвичай сервер зберігає момент у UTC, а клієнт форматує його в часовому поясі користувача.
new Date() не викидає помилку для всіх некоректних значень. Потрібно перевіряти getTime():
function parseDate(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
throw new TypeError(`Некоректна дата: ${value}`);
}
return date;
}
const date = parseDate("2025-07-01T12:00:00Z");
console.log(date.toISOString());Для зовнішніх даних краще мати чіткий контракт формату, а не покладатися на довільні рядки, які браузери можуть інтерпретувати по-різному.
Правила часових поясів надаються реалізацією JavaScript через міжнародні можливості середовища та базу часових зон. Тому варто тестувати:
браузери, які підтримує застосунок;
версію Node.js на сервері;
серверне середовище виконання;
дати до і після переходів на літній час;
часові пояси користувачів.
Для критичних систем також потрібно враховувати оновлення бази часових зон. Політичні рішення можуть змінити правила для регіону.
Рядок:
2025-07-01T09:00:00не містить часового поясу. Не можна без додаткового контексту вважати його UTC.
Значення +02:00 описує зміщення в конкретний момент, але не назву регіону та його правила. Для майбутніх повторюваних подій цього недостатньо.
Код на кшталт:
const localTime = new Date(Date.now() + 2 * 60 * 60 * 1000);не є надійним способом конвертації в часовий пояс. Він не враховує переходи на літній час і не працює для інших регіонів.
toString() для APIРезультат toString() залежить від локального середовища. Для обміну даними використовуйте стабільний формат:
date.toISOString();24 години після події не завжди означають ту саму локальну годину наступного календарного дня.
DateЗначення 2025-07-01 — це календарна дата, а не обов’язково момент опівночі в UTC. Не перетворюйте його на момент без чіткої бізнес-вимоги.
Код може працювати в UTC і помилятися в America/New_York, або працювати взимку й помилятися під час переходу на літній час. Тести мають охоплювати різні часові пояси та граничні дати.
Визначте, що саме описує значення:
момент;
тривалість;
локальну дату й час;
дату без часу.
Для моментів використовуйте UTC або ISO-рядок зі зміщенням.
Для локальних правил зберігайте назву часового поясу IANA.
Для дат без часу не додавайте часовий пояс штучно.
Для відображення використовуйте Intl.DateTimeFormat.
Не додавайте години вручну.
Перевіряйте переходи на літній і зимовий час.
Тестуйте код у кількох часових поясах.
Date зберігає момент часу, а не часовий пояс.
toISOString() повертає момент у UTC.
ISO-рядки для моментів повинні містити Z або явне зміщення.
Назва Europe/Kyiv містить правила часового поясу, а +02:00 — лише зміщення.
Літній час може створювати пропущені або повторені локальні години.
Додавання 24 годин і перехід на наступний календарний день — різні операції.
Події, що вже відбулися, зазвичай зберігають як UTC-моменти.
Повторювані локальні події потребують локального часу та назви часового поясу.
Дати без часу потрібно обробляти окремо від моментів.
Для форматування часових поясів у JavaScript використовуйте Intl.DateTimeFormat.