Пошук уроків, статей та іншого контенту
Розглянете правила парсингу ISO 8601, неоднозначні формати та перевірку некоректних дат.
Парсинг дати — це перетворення текстового значення на структуроване значення, з яким можна виконувати операції:
порівнювати дати;
обчислювати різницю між моментами часу;
форматувати значення;
зберігати та передавати їх між клієнтом і сервером.
У JavaScript для роботи з конкретним моментом часу зазвичай використовується об’єкт Date.
const date = new Date("2025-03-15T12:30:00Z");
console.log(date);
console.log(date.getTime()); // Кількість мілісекунд від Unix EpochОб’єкт Date зберігає число мілісекунд від початку Unix Epoch — 1970-01-01T00:00:00.000Z. Часовий пояс не зберігається в самому значенні Date: він використовується лише під час відображення або отримання локальних компонентів.
Для обміну датами між системами найкраще використовувати ISO 8601. У JavaScript часто застосовують такі варіанти:
2025-03-15
2025-03-15T12:30:00
2025-03-15T12:30:00Z
2025-03-15T12:30:00+02:00
2025-03-15T12:30:00.123ZОсновні частини формату:
2025 — рік;
03 — місяць;
15 — день;
T — роздільник між датою і часом;
12:30:00 — години, хвилини та секунди;
.123 — мілісекунди;
Z — UTC;
+02:00 — зміщення часового поясу.
Рядок у форматі YYYY-MM-DD у JavaScript трактується як UTC-дата:
const date = new Date("2025-03-15");
console.log(date.toISOString());
// 2025-03-15T00:00:00.000ZЦе може бути несподівано під час виведення локальної дати:
const date = new Date("2025-03-15");
console.log(date.getUTCDate()); // 15
console.log(date.getDate()); // Локальний день, може відрізнятисяЯкщо локальний часовий пояс розташований за UTC, локальний час може бути ще попереднього дня.
Рядок із датою та часом, але без Z або зміщення, наприклад:
const date = new Date("2025-03-15T12:30:00");трактується як локальний час середовища, у якому виконується код.
Тому такі значення можуть давати різні моменти часу на комп’ютері користувача та на сервері.
Найнадійніший варіант для представлення конкретного моменту часу — явно вказати часовий пояс:
const utcDate = new Date("2025-03-15T12:30:00Z");
const offsetDate = new Date("2025-03-15T14:30:00+02:00");
console.log(utcDate.getTime() === offsetDate.getTime());
// trueОбидва рядки описують один і той самий момент: 12:30 UTC.
Date.parse() і конструктор DateМетод Date.parse() повертає кількість мілісекунд від Unix Epoch:
const timestamp = Date.parse("2025-03-15T12:30:00Z");
console.log(timestamp);
console.log(new Date(timestamp));Конструктор Date використовує аналогічний механізм:
const date = new Date("2025-03-15T12:30:00Z");Для перевірки результату потрібно використовувати Number.isNaN():
const timestamp = Date.parse("not-a-date");
if (Number.isNaN(timestamp)) {
console.log("Дата некоректна");
}Методи Date.parse() і new Date(string) гарантовано підтримують спрощений ISO-формат, але поведінка для інших форматів може залежати від браузера або середовища виконання.
Такі формати не варто використовувати для обміну даними:
03/04/2025
04.03.2025
March 4, 2025
2025/03/04Наприклад, 03/04/2025 може означати:
3 квітня 2025 року;
4 березня 2025 року.
Крім того, підтримка нестандартних форматів може відрізнятися між браузерами.
Краще використовувати:
2025-04-03
2025-04-03T00:00:00ZЯкщо застосунок отримує дату у власному форматі, її потрібно розібрати явно, а не покладатися на поведінку Date.parse().
Одна з небезпек JavaScript — автоматична нормалізація компонентів дати.
const date = new Date(2025, 1, 31);
console.log(date);У цьому прикладі 1 означає лютий, оскільки місяці в конструкторі new Date(year, month, day) нумеруються від нуля. 31 лютого не існує, тому JavaScript перенесе зайві дні на наступний місяць.
Результат буде датою в березні, а не помилкою.
Подібна проблема може виникати й під час використання рядків у нестандартному форматі. Тому перевірка лише факту створення об’єкта Date не завжди достатня.
Для дати без часу потрібно перевірити:
відповідність формату;
діапазон місяця;
максимальну кількість днів у місяці;
високосний рік.
function isLeapYear(year) {
return year % 4 === 0 &&
(year % 100 !== 0 || year % 400 === 0);
}
function daysInMonth(year, month) {
const days = [
31,
isLeapYear(year) ? 29 : 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
return days[month - 1] ?? 0;
}
function parseDateOnly(value) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
if (
month < 1 ||
month > 12 ||
day < 1 ||
day > daysInMonth(year, month)
) {
return null;
}
// Дата без часу свідомо перетворюється на опівніч UTC.
const date = new Date(`${value}T00:00:00.000Z`);
if (Number.isNaN(date.getTime())) {
return null;
}
return date;
}
const validDate = parseDateOnly("2024-02-29");
const invalidDate = parseDateOnly("2025-02-29");
console.log(validDate?.toISOString());
// 2024-02-29T00:00:00.000Z
console.log(invalidDate);
// nullУ цьому прикладі:
2024-02-29 є коректною датою, оскільки 2024 рік високосний;
2025-02-29 є некоректною датою;
функція повертає null, якщо значення не пройшло перевірку.
Для конкретних моментів часу доцільно вимагати часовий пояс. Наприклад, можна приймати лише такі значення:
2025-03-15T12:30:00Z
2025-03-15T14:30:00.123+02:00Значення без часового поясу краще відхиляти, щоб уникнути залежності від локального середовища.
function isLeapYear(year) {
return year % 4 === 0 &&
(year % 100 !== 0 || year % 400 === 0);
}
function daysInMonth(year, month) {
const days = [
31,
isLeapYear(year) ? 29 : 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
return days[month - 1] ?? 0;
}
function parseIsoDateTime(value) {
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/.exec(value);
if (!match) {
return null;
}
const year = Number(match[1]);
const month = Number(match[2]);
const day = Number(match[3]);
const hours = Number(match[4]);
const minutes = Number(match[5]);
const seconds = Number(match[6]);
const timeZone = match[8];
const offsetHours = timeZone === "Z"
? 0
: Number(timeZone.slice(1, 3));
const offsetMinutes = timeZone === "Z"
? 0
: Number(timeZone.slice(4, 6));
const validDatePart =
month >= 1 &&
month <= 12 &&
day >= 1 &&
day <= daysInMonth(year, month);
const validTimePart =
hours >= 0 &&
hours <= 23 &&
minutes >= 0 &&
minutes <= 59 &&
seconds >= 0 &&
seconds <= 59;
const validOffset =
offsetHours >= 0 &&
offsetHours <= 23 &&
offsetMinutes >= 0 &&
offsetMinutes <= 59;
if (!validDatePart || !validTimePart || !validOffset) {
return null;
}
const timestamp = Date.parse(value);
if (Number.isNaN(timestamp)) {
return null;
}
return new Date(timestamp);
}
const values = [
"2025-03-15T12:30:00Z",
"2025-03-15T14:30:00.123+02:00",
"2025-02-29T12:30:00Z",
"2025-03-15T12:30:00",
"15/03/2025"
];
for (const value of values) {
const result = parseIsoDateTime(value);
console.log(value, result?.toISOString() ?? "некоректне значення");
}Функція навмисно не приймає:
дату без часу;
час без часового поясу;
нестандартні роздільники;
значення з некоректним днем;
години, хвилини або секунди поза допустимим діапазоном.
Це приклад політики валідації. У конкретному проєкті формат можна розширити, але правила мають бути явними.
Елементи HTML-форми можуть повертати значення у визначеному форматі.
Для:
<input type="date" id="birthday">властивість value має формат YYYY-MM-DD.
const input = document.querySelector("#birthday");
input.addEventListener("change", () => {
const date = parseDateOnly(input.value);
if (date === null) {
console.log("Введено некоректну дату");
return;
}
console.log("Дата прийнята:", date.toISOString());
});Важливо відрізняти:
календарну дату — наприклад, день народження;
момент часу — наприклад, час створення замовлення.
Для дня народження часовий пояс зазвичай не має значення. Для часу створення замовлення він критично важливий.
Порівнювати об’єкти Date за допомогою === не можна:
const first = new Date("2025-03-15T12:00:00Z");
const second = new Date("2025-03-15T12:00:00Z");
console.log(first === second); // falseЦе різні об’єкти в пам’яті. Потрібно порівнювати їхні числові значення:
console.log(first.getTime() === second.getTime());
// trueТакож можна використовувати оператори порівняння:
const earlier = new Date("2025-03-15T10:00:00Z");
const later = new Date("2025-03-15T12:00:00Z");
console.log(earlier < later); // truegetTime() повертає мілісекунди, тому різницю потрібно перетворити у потрібні одиниці:
const start = new Date("2025-03-15T10:00:00Z");
const end = new Date("2025-03-15T12:30:00Z");
const differenceInMilliseconds = end.getTime() - start.getTime();
const differenceInMinutes = differenceInMilliseconds / (1000 * 60);
console.log(differenceInMinutes); // 150Такі обчислення працюють для моментів часу. Для календарної різниці між датами з урахуванням часових поясів, переходів на літній час і локальних правил можуть знадобитися спеціалізовані рішення.
Метод toISOString() повертає дату в UTC:
const date = new Date("2025-03-15T14:30:00+02:00");
console.log(date.toISOString());
// 2025-03-15T12:30:00.000ZЦе зручно для:
API;
логів;
баз даних;
передачі значень між клієнтом і сервером.
Для локального відображення використовують toLocaleString() або Intl.DateTimeFormat:
const date = new Date("2025-03-15T12:30:00Z");
const formatter = new Intl.DateTimeFormat("uk-UA", {
dateStyle: "long",
timeStyle: "short",
timeZone: "Europe/Kyiv"
});
console.log(formatter.format(date));Для зберігання і передачі краще використовувати ISO-значення, а для користувача — локалізоване форматування.
new Date("03/04/2025");Такий формат неоднозначний. Використовуйте YYYY-MM-DD або розбирайте значення вручну.
new Date("2025-03-15T12:00:00");Це локальний час, а не UTC. Для конкретного моменту додавайте Z або явне зміщення:
new Date("2025-03-15T12:00:00Z");
new Date("2025-03-15T14:00:00+02:00");instanceof Dateconst date = new Date("invalid");
console.log(date instanceof Date); // trueОб’єкт існує, але містить некоректний момент часу. Перевіряйте:
const date = new Date("invalid");
if (Number.isNaN(date.getTime())) {
console.log("Некоректна дата");
}У числовому конструкторі місяці починаються з нуля:
new Date(2025, 0, 1); // 1 січня
new Date(2025, 11, 1); // 1 грудняУ ISO-рядках місяці нумеруються звичайно:
2025-01-01
2025-12-01const date = new Date(2025, 1, 31);JavaScript не обов’язково повідомить про помилку. Він може перенести значення в наступний місяць. Для важливих даних спочатку перевіряйте компоненти дати.
===firstDate === secondDate;Порівнюйте getTime() або самі об’єкти через <, >, <=, >=.
Для обміну датами використовуйте ISO 8601.
Для конкретного моменту часу вказуйте Z або часовий зсув.
Рядок YYYY-MM-DD у JavaScript трактують як UTC-дату.
Рядок із часом без часового поясу трактують як локальний час.
Не покладайтеся на нестандартні формати на кшталт 03/04/2025.
Перевіряйте не лише формат, а й реальність календарної дати.
Пам’ятайте, що JavaScript може автоматично нормалізувати некоректні компоненти.
Для перевірки результату використовуйте Number.isNaN(date.getTime()).
Об’єкти Date порівнюйте за допомогою getTime().
Для зберігання та передачі використовуйте UTC та toISOString(), а для відображення — локалізоване форматування.