Всем привет! Меня зовут Дмитрий, и я занимаюсь веб-разработкой в IT-компании Intelsy, работая как на аутсорс-, так и на аутстафф-проектах. В своей работе я постоянно сталкиваюсь с задачами, связанными с датами и временем, и давно заметил, что стандартный объект Date в JavaScript часто доставляет много неудобств. Мне захотелось разобраться, почему так происходит и какие современные решения помогают упростить эту работу. Это привело меня к изучению нового API Temporal — перспективного инструмента для более точной и удобной работы с датой, временем и часовыми поясами.

Кому будет полезна эта статья:

  • опытным разработчикам, которые хотят глубже понять недостатки Date и познакомиться с новым подходом Temporal,

  • начинающим, чтобы быстрее разобраться в тонкостях работы с датами в JavaScript и избежать распространённых ошибок,

  • командам, разрабатывающим международные веб-приложения, для которых корректная работа с часовыми поясами и временем — ключ к стабильности и качеству продукта.

Что такое Date?

Для работы с датами в JavaScript разработчики используют Date – объект для работы со временем. Объект Date содержит число миллисекунд, прошедших с полуночи 1 января 1970 года (это также называется эпохой Unix).

JavaScript появился в 1995 году. Он разрабатывался быстро и многие решения создавались с учётом ограниченного количества времени и ресурсов. Как раз, когда потребовалось создать объект для работы с датами, Date был вдохновлён объектом Date из языка Java. Можно сказать, что Date был реализован как обёртка вокруг числа, представляющего количество миллисекунд с эпохи Unix.

И хоть со временем стали явными недостатки Date, его никто не менял, чтобы поддерживать старые веб-приложения. Для минимизации минусов Date появились такие решения, как moment.js, date-fns, dayjs, luxon и т.п. К тому же, в настоящее время ведётся разработка нового API Temporal, который призван решить многие проблемы Date.

Создание даты

С помощью Date мы можем создавать даты:

// Текущая дата и время
const currentDate = new Date();

// Конкретная дата (7 декабря 2025)
const specificDate = new Date(2025, 11, 7, 12, 0, 0);


// Дата из строки в формате ISO 8601
const stringDate = new Date('2025-12-07T12:00:00Z');

// Дата, заданная через миллисекунды
const unixDate = new Date(1700000000000);

Создав объект Date, мы можем, используя его методы, получить нужный нам компонент времени:

const date = new Date();

// Год (2025)
const year = date.getFullYear();

// Месяц от 0 (январь) до 11 (декабрь)
const month = date.getMonth();

// День месяца (1-31)
const day = date.getDate();

// День недели от 0 (воскресенье) до 6 (суббота)
const dayOfWeek = date.getDay();

// Часы (0-23)
const hours = date.getHours();

// Минуты (0-59)
const minutes = date.getMinutes();

// Секунды (0-59)
const seconds = date.getSeconds();

// Временная метка (кол-во миллисекунд с 01.01.1970)
const timestamp = date.getTime();

Существуют также UTC-варианты методов, возвращающие день, месяц, год и т.д. для зоны UTC (всемирное координированное время): getUTCFullYear, getUTCMonth, getUTCDay. Если говорить простыми словами, UTC – это основное время на планете, от которого отсчитываются все другие часовые пояса.

Помимо получения времени, Date имеет методы и для его преобразования:

const date = new Date();

date.setFullYear(2024);
date.setMonth(11);
date.setDate(25);

date.setHours(12);

date.setMinutes(30);

date.setSeconds(30);

date.setMilliseconds(50);

date.setTime(100);

Для этих методов также есть UTC-варианты, которые устанавливают время и дату в зоне UTC: setUTCFullYear, setUTCMonth, setUTCDate и т.д.

Преобразование через такие методы будет менять значение в объекте. То есть объект Date, сам по себе, является мутабельным и методы, изменяющие дату, не создают новый объект, а преобразуют имеющийся, что может быть не очень хорошо. Иммутабельность может быть полезна для предотвращения случайных изменений данных и упрощения работы с состоянием, в то время как мутабельность эти случайные изменения может породить.

Таким образом, для корректного преобразования дат через Date, например, добавление/вычитание пары дней или часов потребуется создание нового объекта даты.

const now = new Date();


const future = new Date(now);
future.setDate(future.getDate() + 25);
future.setMonth(future.getMonth() + 1);

Автокоррекция

У Date есть довольно интересный механизм – автокоррекция. Он заключается в том, что можно устанавливать числа, не существующие в реальных датах, а объект затем сам себя подкорректирует.

const date = new Date(2025, 1, 28); // 28 февраля 2025
date.setDate(date.getDate() + 5); // Добавляем 5 дней
console.log(date); // 4 марта 2025

Но такое поведение может привести к ошибкам. Как мы помним, месяц в Date нужно указывать с нуля. Допустим, не подразумевая этого, разработчик захочет в конструкторе указать декабрь месяц (12), но на выходе получит январь следующего года. Такую же ошибку можно совершить, указав в месяце большее число дней, чем в нём на самом деле.

const date = new Date(2024, 12, 1);
console.log(date); // 1 января 2025

В целом, автокоррекция, действительно, является полезной в сценариях, где нужно выполнить арифметические операции с датами. Например, добавить несколько дней или часов к текущей дате. Тем не менее, автокоррекция может стать и источником ошибок, если разработчик не учитывает её поведение и случайно указывает неверные числа в дате или если выбор даты работает на основе пользовательского ввода, где пользователь может вписать любые числа, тогда потребуется дополнительная проверка на корректность компонентов даты в коде.

Невалидная дата

Может возникнуть вопрос: а как быть, если установить невалидную дату, причём такую, что JavaScript не сможет её обработать? В таком случае Date вернёт значение Invalid Date. Такие значения следует заранее проверять, чтобы избежать ошибок при дальнейшем выполнении кода. Для невалидной даты методы получения времени будут возвращать значение NaN (ошибочное значение после неправильной операции с числами). Тогда проверить валидность даты можно таким образом:

const invalidDate = new Date('invalid date'); // Invalid Date
isNaN(invalidDate.getTime()); // true

Сравнение дат

Ко всему прочему, JavaScript позволяет сравнивать даты с помощью операторов <, <=, >, >=. Например, может пригодиться, когда мы хотим сделать сортировку по дате. Операторы сравнения работают, потому что при их использовании Date автоматически преобразуется в число (миллисекунды).

const date1 = new Date(2025, 0, 1);
const date2 = new Date(2025, 0, 2);

console.log(date1 < date2); // true
console.log(date1 > date2); // false

Операторы == и === в данном случае работать не будут, потому что Date не преобразуется в число при их использовании. Для сравнения дат на равенство можно использовать метод getTime для получения миллисекунд и их сравнения.

const date1 = new Date(2025, 0, 1);
const date2 = new Date(2025, 0, 1);

console.log(date1 === date2); // false (сравниваем объекты)
console.log(date1.getTime() === date2.getTime()); // true

Форматирование даты

Если требуется преобразовать дату в красивый формат, использовать Date становится неудобно. 

Справедливости ради, у Date есть методы toLocaleString, toLocaleDateString и toLocaleTimeString, которые упрощают работу с форматированием, но и имеют некоторые ограничения. Эти методы возвращают строковое представление даты и времени, отформатированное в соответствии с локальными настройками пользователя. 

const date = new Date(2024, 9, 15);
const formattedDate = date.toLocaleDateString('ru-RU', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

console.log(formattedDate); // "15 октября 2024 г."

const date = new Date(2024, 9, 15, 14, 30, 30);
const formattedTime = date.toLocaleTimeString('en-EN', {
  hour: '2-digit',
  minute: '2-digit'
});


console.log(formattedTime); // "02:30 PM"

Хотя этот метод работает, он может быть недостаточно гибким для более сложных сценариев. Например, toLocaleString не предоставляет опций для работы с разделителями или не может поменять местами компоненты даты и времени. К тому же поведение этих методов может отличаться в разных браузерах. Например, формат даты и времени может быть разным в разных браузерах и даже версиях, несмотря на то что указана одна и та же локаль.

Часовые пояса

Одной из самых больших проблем при работе с объектом Date в JavaScript является его зависимость от локального времени пользователя. Date работает только с 2-мя часовыми зонами: локальным временем пользователя и временем UTC. Объект Date изначально не был предназначен для работы с часовыми поясами, что приводит к определённым ограничениям.

Создать дату в зоне UTC мы можем либо через строку, либо через метод Date.UTC.

// Z в конце строки означает UTC-время

const date1 = new Date('2025-01-07T12:30:00Z');


const date2 = new Date(Date.UTC(2025, 0, 7, 12, 30, 0));

Можно даже создать дату в определённом часовом поясе. Например, создадим дату по МСК (UTC+3), будучи в UTC+4. Однако увидим, что работать мы сможем только либо с локальным, либо с UTC-временем.

const date = new Date('2025-01-07T12:00:00+03:00'); // 12:00 (UTC+3)
console.log(date.getHours()); // 13:00 (локальное время UTC+4)
console.log(date.getUTCHours()); // 09:00 (UTC+0)

Для получения компонентов даты в определённом часовом поясе придётся вручную вычислять смещение. Для этого даже есть отдельный метод getTimezoneOffset, который возвращает смещение относительно UTC в минутах. Однако даже, если мы вручную вычислим смещение, то мы всё равно не получим полную информацию о часовом поясе, так как в нём может содержаться ещё летнее время (DST).

const getDateWithOffset = (date: Date, offset: number) => {
  const localOffset = date.getTimezoneOffset();
  const offsetDifference = offset + localOffset;

  const dateWithOffset = new Date(date.getTime() + offsetDifference 60 1000);
  return dateWithOffset;
}

// 13:00:00 (UTC+4)
const localDate = new Date('2025-01-05T13:00:00');
// 12:00:00 (UTC+3)
const mscDate = getDateWithOffset(localDate, 3 * 60);

На самом деле, если не требуется никакой работы с датой в определённом часовом поясе, но требуется только показать её на экран, то есть способ проще через toLocaleString, который поддерживает параметр timeZone.

// 12:30:00

const date = new Date(2025, 0, 1, 12, 30);

// "04:30:00"
console.log(date.toLocaleString('ru-RU', {
    timeZone: 'America/New_York',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
}));

Работа с часовыми поясами в Date является сложной и неудобной из-за отсутствия явной поддержки часовых поясов и ограниченного API. Конечно, с помощью методов UTC, ручного вычисления смещения можно решить определённые задачи, однако в сложных сценариях, например, DST, лучше справиться с этим помогут различные сторонние решения.

Итог

Работа с объектом Date в JavaScript имеет ряд недостатков, которые могут осложнить разработку, особенно в больших и сложных сценариях:

  • неудобное API – месяцы и дни недели нумеруются с нуля в отличие от других компонентов даты;

  • отсутствие поддержки часовых поясов;

  • мутабельность, которая может привести к случайным изменениям;

  • автокоррекция, позволяющая вводить несуществующую дату;

  • форматирование имеет ограничения.

Intl.DateTimeFormat

Intl.DateTimeFormat – это JavaScript объект, который предоставляет инструменты для форматирования дат и времени с учётом языка и региональных настроек пользователя. Он является частью стандарта ECMAScript Internationalization API, который был добавлен в JavaScript для поддержки интернационализации (i18n).

Intl.DateTimeFormat позволяет форматировать дату и время как вместе, так и по отдельности с учётом локали пользователя и разных настроек. При этом, не все настройки являются обязательными и некоторые можно пропустить (например, убрать часы, но оставить минуты и секунды).

const date = new Date(2025, 0, 5, 14, 30); // 5 января 2025, 14:30:00

// Создаём форматтер для локали ru-RU (русский язык, Россия)
const formatter = new Intl.DateTimeFormat('ru-RU', {
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
});

console.log(formatter.format(date)); // "5 января 2025 г. 14:30:00"

Настройка Intl.DateTimeFormat похожа на метод toLocaleString с некоторым отличием в использовании: Intl.DateTimeFormat создаёт функцию-форматтер, которую затем можно вызывать с любой датой, а toLocaleString вызывается как метод уже имеющегося объекта Date и возвращает отформатированную строку. Но, даже несмотря на их схожесть, их реализация в браузерах может отличаться, так как нет строгой спецификации и каждый браузер может интерпретировать требования по-разному.

Intl.DateTimeFormat, сам по себе, является хорошим дополнением к Date, позволяющий без использования сторонних библиотек приводить даты ко многим форматам, хоть и ограничен теми моментами, о которых говорилось ранее.

Temporal

Temporal – это новый API для работы с датой и временем в JavaScript, который призван устранить многие недостатки объекта Date. На момент написания этой статьи, Temporal находится на стадии предложения (proposal) и ещё не включен в стандарт ECMAScript. Однако сейчас его можно попробовать через полифиллы (код, который добавляет функциональность, отсутствующую в некоторых браузерах или средах выполнения), пока не будет полноценной поддержки браузерами.

На данный момент есть 2 полифилла @js-temporal/polyfill – полифилл, размещённый одним из участников разработки Temporal, что может гарантировать соответствие текущей спецификации, и temporal-polyfill – полифилл от сторонних разработчиков, более легковесный и упрощённый. Хоть использование этих полифиллов почти ничем не отличается, а популярность у сообщества примерно одинаковая, я предпочту продемонстрировать в данной статье более официальный вариант. Но, в целом, основные объекты и методы в этих полифиллах совпадают.

npm install @js-temporal/polyfill

В отличие от Date, Temporal состоит из нескольких объектов, каждый из которых имеет свою уникальную реализацию, свои методы и свойства и служит для решения определённых задач. Основные объекты Temporal:

  • Temporal.Now – текущая дата и время.

  • Temporal.Instant – какая-либо точка во времени.

  • Temporal.PlainDate – дата без времени и часового пояса.

  • Temporal.PlainTime – время без даты и часового пояса.

  • Temporal.PlainDateTime – дата и время без часового пояса.

  • Temporal.PlainYearMonth – год и месяц без даты и часового пояса.

  • Temporal.PlainMonthDate – месяц и дата без года и часового пояса.

  • Temporal.ZonedDateTime – дата и время с часовым поясом.

  • Temporal.Duration – продолжительность времени.

  • Temporal.Calendar – календарь.

Temporal.Now

Объект Temporal.Now предоставляет несколько методов и свойств, которые имеют информацию о текущем времени. В целом, этот объект нужен для перевода текущего времени в другие объекты Temporal.

Например, метод instant возвращает объект Temporal.Instant с текущим системным временем. 

const instant = Temporal.Now.instant();

Отличие, которое бросается в глаза, это то, что Temporal умеет возвращать id текущего часового пояса в методе timeZoneId, что более информативно, чем обычное смещение в Date. Затем этот id можно использовать для формирования даты в определённом часовом поясе, включая DST.

const timeZoneId = Temporal.Now.timeZoneId();


console.log(timeZoneId); // Europe/Moscow

Также есть метод plainDate, который возвращает текущую дату как объект Temporal.PlainDate в исчислении определённого календаря. plainDateISO делает то же самое, только в фиксированном календаре ISO 8601 (стандартная адаптация григорианского календаря). При желании, можно указать часовой пояс, в котором вычисляется дата.

const instant = Temporal.Now.plainDate('iso8601');

const isoInstant = Temporal.Now.plainDateISO('Europe/Moscow');

Для работы с временем есть метод plainTimeISO, который возвращает текущее системное время в виде объекта Temporal.PlainTime в исчислении календаря ISO 8601 и часового пояса, который указывать необязательно.

const time = Temporal.Now.plainTimeISO('Europe/Moscow');

Похожие методы plainDateTime и plainDateTimeISO позволяют работать сразу как с датой, так и с временем через объект Temporal.PlainDateTime. 

У Temporal.Now есть методы zonedDateTime и zonedDateTimeISO, которые возвращают объект Temporal.ZonedDateTime и очень похожи по структуре и возвращаемым параметрам на Temporal.PlainDateTime, только отличие в том, что через эти методы можно также получить смещение относительно UTC и время с момента Unix-эпохи.

const date = Temporal.Now.zonedDateTime('iso8601', 'Europe/Moscow');

Temporal.Instant

Temporal.Instant представляет собой точку во времени с точностью до наносекунды. Этот объект похож на тот, который мы получили в Temporal.Now.instant(), только, на этот раз, мы можем задать необходимое нам время через метод.

const date = Temporal.Instant.fromEpochMilliseconds(Date.now());

console.log(date.toString()); // Текущее время в UTC
console.log(date.toLocaleString()); // Текущее локальное время

Можно сказать, что объект Temporal.Instant – аналог привычного new Date(). Отличие в том, что точность измерения времени у Temporal может достигать наносекунд, в отличие от миллисекунд Date. Такая высокая точность может быть очень полезна в сложных системах, завязанных на времени.

const now = Temporal.Now.instant();


// number

console.log(now.epochSeconds); // 1 700 000 000
console.log(now.epochMilliseconds); // 1 700 000 000 000
// BigInt

console.log(now.epochMicroseconds); // 1 700 000 000 000 000n
console.log(now.epochNanoseconds); // 1 700 000 000 000 000 000n

Есть возможность создать точку во времени из строки в формате ISO 8601 с помощью метода from. К тому же Temporal.Instant даёт возможность перевести дату в часовой пояс, используя известные методы toZonedDateTime и toZonedDateTimeISO.

// 01.02.2025 12:30:30 UTC+4
const date = Temporal.Instant.from('2025-02-01T12:30:30+04:00');

// 01.02.2025 11:30:30 GMT+3
console.log(date.toZonedDateTimeISO('Europe/Moscow').toLocaleString());

Temporal.PlainDate

Temporal.PlainDate можно просто представить как дату без времени и без привязки к часовому поясу. Создать такую дату можно через конструктор или метод from, принимающий строку, или объект Temporal.PlainDate.

// 05.01.2025
const date = new Temporal.PlainDate(2025, 1, 5);
const stringDate = Temporal.PlainDate.from('2025-01-05');
const objectDate = Temporal.PlainDate.from({ year: 2025, month: 1, day: 5 });

console.log(date.calendarId); // iso8601
console.log(date.year); // Год

console.log(date.inLeapYear); // Високосный год (true/false)
console.log(date.month); // Месяц (1-12)
console.log(date.day); // День месяца (1-31)
console.log(date.dayOfWeek); // День недели (1-7)
console.log(date.dayOfYear); // День в году (1-365, для високосных – 366)

Уже заметно, что день недели (dayOfWeek) и месяц (month) имеют удобный диапазон значений в отличие от Date. Также появилось поле dayOfYear для получения дня в году и много других новых полей.

Например, у некоторых календарей есть так называемые эры, и Temporal учитывает этот момент. Так можно узнать, что по буддийскому календарю сейчас эра be, год по эре – 2568, а в остальном всё совпадает с нашим календарём.

const date = Temporal.Now.plainDate('buddhist', 'Europe/Moscow');


console.log(date.era); // "be"
console.log(date.eraYear); // 2568

console.log(date.year); // 2568
console.log(date.monthsInYear); // 12
console.log(date.daysInWeek); // 7
console.log(date.daysInMonth); // 31
console.log(date.daysInYear); // 365

Полученный объект затем можно как привязать к часовому поясу через уже известный метод toZonedDateTime, так и расширить, добавив время методом toPlainDateTime.

// 05.01.2025
const date = new Temporal.PlainDate(2025, 1, 5);
// 05.01.2025 12:30:30
const dateTime = date.toPlainDateTime('12:30:30');
// 05.01.2025 12:30:30 GMT+3
const zonedDate = date.toZonedDateTime({ timeZone: 'Europe/Moscow', plainTime: '12:30:30' });

Temporal.PlainTime

Temporal.PlainTime – это просто время без привязки к часовому поясу или какой-либо дате. Создать время можно также через конструктор или метод from.

// 12:30:30
const time = new Temporal.PlainTime(12, 30, 30);
const stringTime = Temporal.PlainTime.from('12:30:30');
const objectTime = Temporal.PlainTime.from({ hour: 12, minute: 30, second: 30 });

console.log(time.hour); // 0-23
console.log(time.minute); // 0-59
console.log(time.second); // 0-59

Полученное время можно также привязать к часовому поясу или объединить с датой.

// 12:30:30
const time = new Temporal.PlainTime(12, 30, 30);
// 05.01.2025 12:30:30
const dateTime = time.toPlainDateTime('2025-01-05');
// 05.01.2025 12:30:30 GMT+3
const zonedDateTime = time.toZonedDateTime({ timeZone: 'Europe/Moscow', plainDate: '2025-01-05' });

Temporal.PlainDateTime

Temporal.PlainDateTime объединяет в себе Temporal.PlainDate и Temporal.PlainTime и представляет собой дату и время без привязки к часовому поясу. Его методы и свойства аналогичны тому, что мы видели в Temporal.PlainDate и Temporal.PlainTime.

// 05.01.2025 12:30:30
const dateTime = new Temporal.PlainDateTime(2025, 1, 5, 12, 30, 30);


const stringDateTime = Temporal.PlainDateTime.from('2025-01-05T12:30:30');


const objectDateTime = Temporal.PlainDateTime.from({ year: 2025, month: 1, day: 5, hour: 12, minute: 30, second: 30 });

Объект Temporal.PlainDateTime можно довольно просто перевести в обычную дату или время, а также привязать к ним часовой пояс.

// 05.01.2025 12:30:30
const dateTime = new Temporal.PlainDateTime(2025, 1, 5, 12, 30, 30);
// 05.01.2025
const date = dateTime.toPlainDate();
// 12:30:30
const time = dateTime.toPlainTime();
// 05.01.2025 12:30:30 GMT+3
const zonedDateTime = dateTime.toZonedDateTime('Europe/Moscow');

Temporal.ZonedDateTime

Этот объект представляет из себя тот же Temporal.PlainDateTime, совмещённый с Temporal.Instant, и с привязкой к часовому поясу, который необходимо будет указать при создании. Создание даты через конструктор требует время в наносекундах, поэтому без Temporal.Instant не обойтись.

const now = Temporal.Now.instant();
const zonedDateTime = new Temporal.ZonedDateTime(now.epochNanoseconds, 'Europe/Moscow');


const stringZonedDateTime = Temporal.ZonedDateTime.from('2025-01-05T12:30:30+03:00[Europe/Moscow]');


const objectZonedDateTime = Temporal.ZonedDateTime.from({ year: 2025, month: 1, day: 5, hour: 12, minute: 30, second: 30, timeZone: 'Europe/Moscow' });


console.log(zonedDateTime.offset); // +03:00
console.log(zonedDateTime.offsetNanoseconds); // 10 800 000 000 000
console.log(zonedDateTime.epochMilliseconds); // 1 700 000 000 000

Так как в Temporal.ZonedDateTime дата может представлять из себя точку во времени, то её можно перевести в Temporal.Instant методом toInstant. В остальном методы совпадают с Temporal.PlainDateTime.

Temporal.Duration

Temporal.Duration представляет из себя какую-либо продолжительность в различных единицах времени (год, месяц, день, час, минута, секунда и т.д.).

const duration = new Temporal.Duration(0, 1, 1, 5, 2, 30);
const objectDuration = Temporal.Duration.from({ months: 1, weeks: 1, days: 5, hours: 2, minutes: 30 });

// "1 мес. 1 нед. 5 дн. 2 ч 30 мин"
console.log(duration.toLocaleString());

Такую продолжительность времени можно использовать при расчётах с помощью методов add и subtract. Например, добавить/вычесть дни. При этом можно использовать компоненты времени даже при расчётах дат: так 24 часа суммируются в один день для Temporal.PlainDate.

const duration = Temporal.Duration.from({ months: 1, days: 1, hours: 24 });

// 05.01.2025
const date = Temporal.PlainDate.from({ year: 2025, month: 1, day: 5 });
 
// 07.02.2025
const addDate = date.add(duration);
// 03.12.2024
const substractDate = date.subtract(duration);

Такие вычисления применимы не только к датам, но и ко времени

const duration = Temporal.Duration.from({ hours: 5, minutes: 30 });
// 02:00:00
const time = Temporal.PlainTime.from({ hour: 2 });
 
// 07:30:00
const addedTime = time.add(duration);
// 20:30:00
const substractedTime = time.subtract(duration);

Методы add и subtract применимы и к Temporal.Duration – можно добавлять продолжительность времени к другой продолжительности.

Объект Temporal.Duration также можно получить при сравнении двух дат. Например, через методы since или until мы можем сравнить две даты и получить продолжительность времени между ними, что довольно удобно.

// 05.01.2025 12:30:00
const dateTime1 = Temporal.PlainDateTime.from('2025-01-05T12:30:00');
// 06.01.2025 13:30:00
const dateTime2 = Temporal.PlainDateTime.from('2025-01-06T13:30:00');

// 1 дн. 1 ч
const duration = dateTime2.since(dateTime1);
console.log(duration.days, duration.hours);

У Temporal.Duration есть несколько свойств, помимо компонентов времени, которые могут пригодиться в разработке. Свойство blank говорит о том, что длительность не является пустой и не имеет нулевых компонентов времени.

const duration = Temporal.Duration.from({ hours: 0 });
 
// true (пустая продолжительность)
console.log(duration.blank);

Метод negated позволяет полностью инвертировать продолжительность. Таким образом мы можем изменить направление длительности, при этом меняется знак sign. Это как умножение числа на -1: дважды проделанная операция не изменит знак.

const duration = Temporal.Duration.from({ days: 3, hours: 12 });
const negatedDuration = duration.negated();

// -3 дн. 12 ч
console.log(negatedDuration.toLocaleString());
// -1
console.log(negatedDuration.sign);

Используя метод total, можно быстро подсчитать общее кол-во дней, часов, минут и т.д. в продолжительности времени.

const duration = Temporal.Duration.from({ days: 1, hours: 24 });

console.log(duration.total('days')); // 2
console.log(duration.total('hours')); // 48

С помощью метода with можно заменить какие-нибудь компоненты времени, не меняя остальные.

const duration = Temporal.Duration.from({ days: 1, hours: 24 });
const newDuration = duration.with({ days: 2 });
 
// "2 дн. 24 ч"
console.log(newDuration.toLocaleString());

Иммутабельность

Как можно было заметить, в отличие от Date, Temporal предоставляет иммутабельные объекты, методы которых не меняют объект, а возвращают новый. Такое поведение является более предсказуемым и безопасным.

const date = Temporal.PlainDate.from('2025-01-05');
const futureDate = date.add({ days: 5 });
 
// 05.01.2025
console.log(date.toLocaleString());
// 10.01.2025
console.log(futureDate.toLocaleString());

Автокоррекция

Как и у Date, у Temporal тоже есть автокоррекция, но на этот раз она работает довольно ожидаемо и понятно. При создании даты/времени через объект, если установить какой-нибудь компонент времени вне пределов допустимого значения, он автоматически вернётся к максимально или минимально допустимому значению. Такое поведение может сильно упростить задачу, когда мы имеем дело с пользовательским вводом.

// 31.02.2025 (!)
const date = Temporal.PlainDate.from({ year: 2025, month: 2, day: 31 });

// 28.02.2025
console.log(date.toLocaleString());

Однако, если нас такое поведение не устраивает, автокоррекцию можно настроить через параметр overflow. При значении reject, автокоррекции не будет, зато вместо неё будет выкидываться ошибка. Значение constrain ставится по умолчанию и включает автокоррекцию из предыдущего примера.

try {
  // 31.02.2025 (!)
  const date = Temporal.PlainDate.from({ year: 2025, month: 2, day: 31 },    { overflow: 'reject' });
} catch (error) {
  // RangeError: value out of range: 1 <= 31 <= 28
  console.error(error);
}

Создание некорректной даты из строки не будет включать автокоррекцию, а сразу выведет ошибку, как в поведении при overflow, равный reject.

Когда мы хотим провести какие-нибудь вычисления, то автокоррекция тут как кстати. При переполнении какого-либо компонента времени, дата будет скорректирована на новую

// 28.02.2025
const date = Temporal.PlainDate.from({ year: 2025, month: 2, day: 28 });

// 01.03.2025
const future = date.add({ days: 1 });
console.log(future.toLocaleString());

Однако и тут есть нюансы. Например, если мы добавим 1 месяц к 31 января, то мы получим 28 февраля, а не март месяц. В целом, это логично, ведь 1 месяц – общее понятие, которое не содержит в себе чёткое количество дней. Иначе, если мы хотели бы перейти на март, то следовало бы добавлять дни, а не месяцы.

// 31.01.2025
const date = Temporal.PlainDate.from('2025-01-31');

// 28.02.2025
const futureDate = date.add({ months: 1 });
console.log(futureDate.toLocaleString());

Автокоррекция так же, как и для объектов, может быть настроена для методов add и subtract через параметр overflow.

Невалидная дата

В Temporal проверка невалидных дат проходит через механизм исключений. При попытке создать создать объект с невалидными данными, Temporal выбрасывает исключение. Таким образом, проверять валидность даты следует через блок try-catch.

const isValidDate = (date: Temporal.PlainDateLike | string) => {
  try {
    Temporal.PlainDate.from(date, { overflow: 'reject' });
    return true;
  } catch {
    return false;
  }
};

console.log(isValidDate({ year: 2025, month: 13, day: 1 })); // false
console.log(isValidDate('2025-02-31')); // false
console.log(isValidDate('2025-01-05')); // true

Но в таком случае мы проверяем только Temporal.PlainDate. Если на проекте множество объектов Temporal, то понадобится либо несколько реализаций таких функций на каждый объект, либо универсальная функция, проверяющая несколько объектов.

Сравнение дат

Temporal в своих объектах предоставляет методы для сравнения дат и времени. Например, в большинстве объектах есть метод compare, который принимает два объекта, схожих с типом в котором используется compare. Например, если мы сравниваем даты через Temporal.PlainDate.compare, то нужно, чтобы в каждом параметре были компоненты даты (год, месяц, день).

// 01.01.2025
const pastDate = Temporal.PlainDate.from('2025-01-01');
// 05.01.2025
const date = Temporal.PlainDate.from('2025-01-05');

const { compare } = Temporal.PlainDate;

console.log(compare(date, pastDate)); // 1 (больше)
console.log(compare(date, pastDate.add({ days: 4 }))); // 0 (равно)
console.log(compare(pastDate, date)); // -1 (меньше)

Также в инициализированных объектах (которые уже имеют установленную дату и время) есть метод equals, который сравнивает даты или время. Но, в отличие от compare, который сравнивает моменты времени без учёта часового пояса, equals также сравнивает и часовой пояс. Даже в одинаковых моментах времени, разные часовые пояса, меняют возвращаемое значение в equals. Таким образом, когда compare может вернуть 0 (одинаковые даты), equals может показать false. В целом, такое поведение обосновано, ведь даже при одинаковом смещении часовые пояса могут показывать разное время, так как есть ещё понятие летнего времени DST, которое у каждого пояса своё.

// 05.01.2025 12:30 UTC+3
const mscDate = Temporal.ZonedDateTime.from('2025-01-05T12:30:00+03:00[Europe/Moscow]');
// 05.01.2025 10:30 UTC+1
const prsDate = Temporal.ZonedDateTime.from('2025-01-05T10:30:00+01:00[Europe/Paris]');
 
console.log(Temporal.ZonedDateTime.compare(mscDate, prsDate)); // 0 (равно)
console.log(mscDate.equals(prsDate)); // false
console.log(mscDate.equals(prsDate.withTimeZone('Europe/Moscow'))); // true

Форматирование даты

Для форматирования дат и времени полифилл Temporal предоставляет свой объект Intl.DateTimeFormat, который имеет некоторые отличия от стандартного.

const date = Temporal.PlainDate.from('2025-01-05');

const formatter = Intl.DateTimeFormat('ru-RU', {
  dateStyle: 'full',
  calendar: 'japanese',
});

// "воскресенье, 5 января 7 г. Рэйва"
console.log(formatter.format(date));

В целом, этот объект похож на стандартный Intl.DateTimeFormat только с дополнительными опциями под Temporal, вроде календаря и т.п.

Итог

Как можно заметить, в Temporal и правда исправлены многие недостатки Date: 

  • введены иммутабельные объекты;

  • добавлена поддержка часовых поясов, а также календарей и летнего времени;

  • понятная нумерация дней недели и месяцев;

  • работа с датой и временем стала более понятной, поправлена автокоррекция.

Однако форматирование, в целом, никак не изменилось по сравнению с тем же Date. Несмотря на это, можно сказать, что Temporal API – мощный инструмент для работы с датами и временем. Хоть на данный момент разработчики настоятельно не рекомендуют использовать Temporal в качестве полифилла на продовой среде,  в будущем такой инструмент может сильно облегчить разработку.

Комментарии (1)


  1. SWATOPLUS
    28.05.2025 12:28

    Temporal действительно классная вещь, но мы его уже лет 5 ждём. Я не понимаю почему коммитет тянет. Это очень важный стандарт и нужно бросить все силы, что бы довести его до stage 4.