Skip to main content

Temporal API в JavaScript

Temporal API це новий стандартний набір типів для роботи з датами й часом у JavaScript, який має замінити Date. Це пропозиція TC39, що дійшла до Stage 3, вже реалізована в частині рушіїв і доступна скрізь через офіційний polyfill. Головна ідея: незмінні об'єкти, чітке розділення понять «момент», «дата», «час» і «часовий пояс», плюс точна робота з базою часових поясів IANA.

Теорія

TL;DR

  • Temporal це нове вбудоване API дат і часу; воно не розширює Date, а повністю його замінює.
  • Усі об'єкти незмінні: add(), subtract(), with(), round() повертають нове значення.
  • Окремі типи під окремі задачі: Instant, PlainDate, PlainTime, PlainDateTime, ZonedDateTime, Duration, Now.
  • Часовий пояс зберігається як частина значення ([Europe/Kyiv]), а не лише як зміщення +03:00, тому переходи на літній та зимовий час обробляються правильно.
  • Некоректна дата дає RangeError (з overflow: 'reject') або явний clamp замість тихого «переносу», як у new Date(2025, 1, 30).
  • Місяці нумеруються з 1, а не з 0, як у Date.
  • Форматування виводу віддається Intl.DateTimeFormat; у старих середовищах підключають @js-temporal/polyfill.

Швидкий приклад

javascript
// Дата без часу const date = Temporal.PlainDate.from('2025-10-16'); console.log(date.year, date.month, date.day); // 2025 10 16 // Час без дати const time = Temporal.PlainTime.from('13:45:30'); console.log(time.hour); // 13 // Дата й час без часового поясу const dt = Temporal.PlainDateTime.from('2025-10-16T13:45:30'); console.log(dt.toString()); // 2025-10-16T13:45:30

Чому Date не вистачає

Date це одна з найстаріших і найпроблемніших частин мови: його API скопіювали з Java ще у 1995 році й фактично не змінювали.

ПроблемаПриклад
Неявні часові поясиnew Date('2024-01-01') парситься як UTC, а new Date('2024-01-01T00:00') як локальний час
Непередбачувані обчисленняnew Date(2024, 1, 30) дає 1 березня, бо лютого 30 не існує
Мутуючі методиsetMonth(), setHours() змінюють вихідний об'єкт, а не повертають новий
Незручна робота з UTCдоводиться вручну жонглювати toISOString() і парою getHours() / getUTCHours()
Нумерація з нулямісяці 0..11, а дні місяця 1..31
Немає типів для календарів, тривалостей і зонза будь-якою дрібницею тягнули сторонню бібліотеку: Moment, date-fns, Luxon

Саме тому Temporal не «лагодить» Date, а вводить окрему, послідовну модель часу.

Класи Temporal

КласЩо представляєПриклад
Temporal.Instantмомент на абсолютній шкалі часу (точка в UTC)"2025-10-16T21:00:00Z"
Temporal.PlainDateдата без часу"2025-10-16"
Temporal.PlainTimeчас без дати"21:00:00"
Temporal.PlainDateTimeдата й час без часового поясу"2025-10-16T21:00:00"
Temporal.ZonedDateTimeдата + час + часовий пояс"2025-10-16T21:00:00+03:00[Europe/Kyiv]"
Temporal.Durationтривалість, різниця між моментами"P3DT5H" (3 дні 5 годин)
Temporal.Nowутиліта доступу до поточного часуTemporal.Now.instant()

Різниця між PlainDateTime і ZonedDateTime ключова на співбесіді. PlainDateTime це «настінний» час без прив'язки до місця: «16 жовтня о 21:00» може означати різні миті в різних країнах. ZonedDateTime містить зону, тому однозначно вказує на конкретну мить і знає про переходи DST. Коли вам потрібна саме мить (мітка події, запис у лог), беріть Instant або ZonedDateTime; коли потрібен «день народження» чи «час відкриття офісу», беріть PlainDate / PlainTime.

Часові пояси та перехід на літній час

javascript
const zdt = Temporal.ZonedDateTime.from({ timeZone: 'Europe/Kyiv', year: 2025, month: 10, day: 16, hour: 21, }); console.log(zdt.toString()); // 2025-10-16T21:00:00+03:00[Europe/Kyiv] console.log(zdt.toInstant().toString()); // 2025-10-16T18:00:00Z (те саме в UTC)

Temporal зберігає ідентифікатор зони ([Europe/Kyiv]) як частину даних, а не лише зміщення +03:00. Завдяки цьому арифметика враховує переходи на літній та зимовий час:

javascript
const beforeDST = Temporal.ZonedDateTime.from('2025-03-30T01:30:00+01:00[Europe/Berlin]'); const afterDST = beforeDST.add({ hours: 1 }); console.log(afterDST.toString()); // 2025-03-30T03:30:00+02:00[Europe/Berlin]

Годинник «перестрибнув» з 01:30 на 03:30, бо о 02:00 стрілки перевели вперед: додали одну реальну годину, і зміщення змінилося з +01:00 на +02:00.

Поточний час беруть через Temporal.Now:

javascript
const now = Temporal.Now.instant(); console.log(now.toString()); // поточний час у UTC const kyiv = Temporal.Now.zonedDateTimeISO('Europe/Kyiv'); console.log(kyiv.toString()); // наприклад: 2025-10-16T23:50:00+03:00[Europe/Kyiv]

Обчислення, Duration і сувора валідація

Усі операції повертають нові значення, вихідний об'єкт залишається недоторканим:

javascript
const today = Temporal.PlainDate.from('2025-10-16'); const tomorrow = today.add({ days: 1 }); const lastWeek = today.subtract({ weeks: 1 }); console.log(tomorrow.toString()); // 2025-10-17 console.log(lastWeek.toString()); // 2025-10-09 console.log(today.toString()); // 2025-10-16, не змінився

Різниця між двома моментами це Temporal.Duration в ISO 8601 форматі:

javascript
const start = Temporal.PlainDateTime.from('2025-10-16T10:00'); const end = Temporal.PlainDateTime.from('2025-10-18T15:30'); const duration = end.since(start); console.log(duration.toString()); // P2DT5H30M (2 дні 5 годин 30 хвилин) console.log(duration.days, duration.hours, duration.minutes); // 2 5 30

Некоректні значення не «виправляються» тихцем. Рядок з неіснуючою датою одразу кидає помилку, а для об'єкта поведінку задає опція overflow:

javascript
// Старий Date мовчки переносить дату new Date(2025, 1, 30); // 2025-03-02 (місяць 1 це лютий, 30 лютого не існує) // Temporal: рядок з неіснуючою датою одразу помилка Temporal.PlainDate.from('2025-02-30'); // RangeError // Для об'єкта за замовчуванням overflow: 'constrain' (обрізає до межі місяця) Temporal.PlainDate.from({ year: 2025, month: 2, day: 30 }); // 2025-02-28 // Суворий режим Temporal.PlainDate.from({ year: 2025, month: 2, day: 30 }, { overflow: 'reject' }); // RangeError

Зверніть увагу: у Temporal month: 2 це лютий, бо місяці нумеруються з 1. У Date та сама позиція означала б березень.

Переваги, форматування та підтримка

ПеревагаЩо дає
Незмінністьбезпечні операції без мутацій і без випадкових побічних ефектів
Чітка модельрозділення понять «момент», «дата», «час», «зона»
Точні часові поясибаза IANA, коректний DST, зона всередині значення
Немає «магії» Dateпомилки замість неявних коригувань
Сумісність з Intlформатування для користувача без ручного склеювання рядків
ISO 8601 і Durationпроста робота з різницями та інтервалами

Форматування віддано Intl.DateTimeFormat, тому локалізація виглядає звично:

javascript
const zdt = Temporal.ZonedDateTime.from('2025-10-16T21:00:00+03:00[Europe/Kyiv]'); const fmt = new Intl.DateTimeFormat('uk-UA', { dateStyle: 'full', timeStyle: 'long' }); console.log(fmt.format(zdt)); // четвер, 16 жовтня 2025 р. о 21:00:00 за східноєвропейським літнім часом

Де вже можна користуватися:

  • Найновіші рушії постачають Temporal нативно (першим повну реалізацію включив Firefox), у решти вона в роботі.
  • У Node.js і старих браузерах підключають офіційний polyfill.
  • Перед використанням у проді перевіряйте підтримку на MDN або через typeof Temporal !== 'undefined'.
bash
npm i @js-temporal/polyfill
javascript
import { Temporal } from '@js-temporal/polyfill'; const date = Temporal.PlainDate.from('2025-10-16'); console.log(date.toString()); // 2025-10-16

Типові помилки

  • Очікувати мутації. dt.add({ days: 1 }) нічого не змінює в dt; результат треба присвоїти. Це найчастіша помилка для тих, хто звик до setDate().
  • Плутати PlainDateTime і ZonedDateTime. PlainDateTime не є миттю на шкалі часу: без зони його не можна коректно порівняти з Instant чи зберегти як мітку події.
  • Зберігати лише зміщення. +03:00 це не часовий пояс: наступного тижня після переходу на зимовий час те саме місце вже матиме +02:00. Зберігайте ідентифікатор IANA (Europe/Kyiv).
  • Нумерація місяців з нуля. Перенісши код з Date, легко залишити month: 0; у Temporal це RangeError, бо січень це 1.
  • Розраховувати на RangeError там, де діє constrain. from({...}) за замовчуванням обрізає значення до межі місяця; щоб отримати помилку, передайте { overflow: 'reject' }.
  • Змішувати Date і Temporal у розрахунках. Конвертуйте явно: date.toTemporalInstant() та instant.epochMilliseconds.
  • Тягнути важку бібліотеку задля Temporal. Сам polyfill теж важить чимало, тож для одного форматування іноді достатньо Intl.DateTimeFormat над звичайним Date.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.