Об'єкт Date зберігає одну мить у часі - кількість мілісекунд від 1970-01-01 UTC. Часового поясу в ньому немає: методи на кшталт getHours() показують цю мить у поясі браузера користувача, а getUTCHours() - в UTC.
Головні пастки:
1. Місяці з нуля.
new Date(2026, 0, 31); // 31 січня 2026
new Date(2026, 1, 31); // 3 березня - «переповнення» тихо переноситься
2. Розбір рядків залежить від формату.
new Date('2026-10-04'); // північ за UTC (лише дата - як UTC)
new Date('2026-10-04T10:00'); // 10:00 за місцевим часом браузера
new Date('04.10.2026'); // залежить від рушія - не використовувати
Користувач у Києві побачить new Date('2026-10-04').getDate() як 4, а в Нью-Йорку - як 3: північ UTC там ще попередній день.
3. Об'єкт змінюваний.
const start = new Date();
const end = start;
end.setDate(end.getDate() + 7); // змінили і start теж
4. Арифметика місяців. setMonth(month + 1) від 31 січня дає 3 березня (лютого 31-го не існує).
Як працювати безпечно:
- обмінюватися з сервером у ISO 8601 з поясом:
2026-10-04T07:00:00Zчи з зміщенням+03:00. Laravel за замовчуванням серіалізує дати моделей саме так (UTC зZ); - зберігати й передавати мить у UTC, а в місцевий час перетворювати лише для показу;
- показувати через
Intl.DateTimeFormat, а не збирати рядок вручну:
new Intl.DateTimeFormat('uk', {
dateStyle: 'long',
timeStyle: 'short',
timeZone: 'Europe/Kyiv',
}).format(new Date('2026-10-04T07:00:00Z'));
// '4 жовтня 2026 р. о 10:00'
- «дата без часу» (день народження, дата події) - не перетворювати на
Dateвзагалі, а тримати рядком'2026-10-04', інакше часовий пояс зсуне її на день; - відносний час -
Intl.RelativeTimeFormat('uk')(«3 дні тому»).
Що далі. Новий API Temporal розділяє поняття: Temporal.PlainDate (дата без часу й поясу), Temporal.ZonedDateTime (час у конкретному поясі), Temporal.Instant (мить) - і робить їх незмінними. Він поступово з'являється в браузерах; до повної підтримки - поліфіл або бібліотеки (date-fns, Day.js, Luxon).