Новое Temporal API для работы с датами в JavaScript прошло третью стадию технического комитете TC39 на добавление в стандарт ECMA. Оно может быть добавлено в ближайших релизах языка. Давайте посмотрим какие отличия нас ждут от Date и как с помощью Temporal API можно решить старые задачи по-новому.
Как попробовать новое Temporal API
Чтобы запустить новое Temporal API есть три основных метода:
- самый простой это зайти на сайт документации и на любой из страниц открыть консоль браузера. Temporal API будет уже встроено в консоль.
- на странице GitHub предложения для Temporal API есть полифил, который можно скачать и запустить в специальном playground
- поставить npm пакет и импортировать его у себя в коде.
Для примеров из этой статьи я использую консоль браузера Chrome на странице документации.
Создание экземпляра
Для создания значения Date в текущей реализации языка используется следующие форматы:
new Date(milliseconds);
new Date(dateString);
new Date(year, month[, day[, hour[, minute[, second[, millisecond]]]]]);
В Temporal API вы также можете создать дату из строки:
const instant = Temporal.Instant.from('1969-07-20T20:17Z');
А вот определение значения даты через ее составляющие будет отличатся. В Temporal в метод from передается объект, описывающий дату. В отличии, от Date, где значения принимает конструктор, в порядке, который необходимо помнить, в объекте принимаемом методом from более наглядно и в любом порядке можно указать все необходимые значения:
const zonedDateTime = Temporal.ZonedDateTime.from({
timeZone: 'America/Los_Angeles',
year: 1995,
month: 12,
day: 7,
hour: 3,
minute: 24,
second: 30,
millisecond: 0,
microsecond: 3,
nanosecond: 500
});
У Temporal API уже на этапе создания даты есть свои преимущества. Передача параметров в формате объекта оберегает от случайных ошибок, позволяет не думать о порядке следования значений, при этом в коде более наглядно видно какое значение, в какой параметр передается.
Взятие основных значений
Следующее отличие, которое мы рассмотрим это взятие основных свойств из даты, таких как день, месяц, год и т.д.
У экземпляра объекта Date представлен набор методов get, таких как getDate, getFullYear и так далее для получения всех необходимых значений.
const Xmas = new Date('December 25, 1995 23:15:30');
console.log(Xmas.getDate()); // 25
console.log(Xmas.getMonth()); // 11
console.log(Xmas.getFullYear()); // 1995
В новой реализации все эти значения получаются не с помощью методов, а как свойства объекта с соответствующим названием day, month, year и т.д.:
const myDate = new Temporal.ZonedDateTime.from({ timeZone: 'Europe/Moscow', year: 2021, month: 7, day: 14 });
console.log(myDate.day); // 14
console.log(myDate.dayOfWeek); // 3
console.log(myDate.month); // 7
console.log(myDate.year); // 2021
При этом отметим, что в Temporal отсчет месяца идет с 1, а в Date с 0, поэтому номер месяца будет соответствовать номеру текущего месяца без необходимость прибавлять единицу, как при использовании Date. Важно помнить об этой разнице при использовании нового API. Также вместо метода getFullYear, в котором многие путались, теперь просто свойство year.
В целом видно, что привели в порядок недоработки Date, о которых приходилось помнить и обходить в коде. Взятие значений стало более очевидным и интуитивно понятным, что конечно приведет к меньшему числу случайных ошибок и более низкому порогу входа в работу с датами.
Автокоррекция
Еще одной фичей, которая использовалась в Date, но работает иначе в Temporal API является автокоррекция даты. Если вы передадите в Date какое-то значение больше возможного, но дата автоматически пересчитается, с учетом этого излишка. На пример, если вы укажете двенадцатый месяц, вместо 11 (например, если забыли учесть, что месяцы начинаются с нуля, как мы обсуждали выше), то вместо декабря текущего года, вы получите январь следующего:
const myDate = new Date(2020, 12, 1);
console.log(myDate) // Fri Jan 01 2021 00:00:00 GMT+0300 (Moscow Standard Time)
В отличии от этого поведения, Temporal API округлит значение, которое превышает возможное к ближайшему возможному:
const myDate = new Temporal.ZonedDateTime.from({
timeZone: 'Europe/Moscow',
year: 2020,
month: 13,
day: 101
});
console.log(myDate.toString()); // 2020-12-31T00:00:00+03:00[Europe/Moscow]
Кто умело пользовался возможностью автокоррекции, возможно расстроится такому поведению, однако это может предотвратить множество случайных и сложноотлавливаемых багов и улучшит читаемость кода.
Проверка является ли год високосным
В возможностях Date нет реализации для проверки года на високосность. Однако это можно сделать несколькими способами, такими как проверка количества дней в году и количества дней в феврале, например:
const isLeap = year => new Date(year, 1, 29).getDate() === 29;
console.log(isLeap(2020)); // true
console.log(isLeap(2021)); // false
В Temporal API есть специальное свойство для проверки является ли год високосным:
const myDate = new Temporal.ZonedDateTime.from({ timeZone: 'Europe/Moscow', year: 2020, month: 13, day: 1 });
console.log(myDate.inLeapYear); // true
Конечно это функционал, который не так часто нужен, но все же упрощает код и делает его более простым и читаемым.
Прибавление и вычитание N дней / месяцев / лет к дате
Чтобы добавить какое-то количество дней, месяцев или лет к дате с помощью Date нам нужно установить соответствующее значение, модифицируя текущее значение. Соответственно, если вам нужно, например, прибавить три года четыре месяца и шесть дней, то нужно будет делать три разных операции взятия текущих значений и три операции присвоения нового значения. Для наглядности посмотрим пример:
const date = new Date();
date.setMonth(date.getMonth() + 4);
console.log(date.toString()); // Sun Nov 14 2021 16:46:09 GMT+0300 (Moscow Standard Time)
date.setFullYear(date.getFullYear() + 3);
console.log(date.toString()); // Thu Nov 14 2024 16:46:09 GMT+0300 (Moscow Standard Time)
date.setDate(date.getDate() + 6)
console.log(date.toString()); // Wed Nov 20 2024 16:46:09 GMT+0300 (Moscow Standard Time)
Еще одним методом является прибавление к дате соответствующего количества миллисекунд, что упрощает реализацию, но вводит кучу магических чисел в код, которые по-хорошему нужно выносить в константы, при этом нужно учесть сколько в последующих месяцах дней, не високосный ли это год и так далее. В итоге вы получите еще более громоздкое решение, реализуя которое не сложно ошибиться.
В Temporal API есть специальные методы для прибавления и вычитания, в которых, с помощью объекта указываются все параметры добавления. Только в результате возвращается новый экземпляр даты, а не меняется текущий. Попробуем сделать тоже самое, что в примеры выше, но с использование метода add из Temporal API:
let myDate = Temporal.now.zonedDateTimeISO();
myDate = myDate.add({ years: 3, months: 4, days: 6 });
console.log(myDate.toString()); // 2024-11-20T00:00:00+03:00[Europe/Moscow]
В итоге мы получаем очень легкое для запоминания, короткое решение, которое ни раз пригодится каждому.
Работа с таймзонами
По умолчанию дата через конструктор Date создается в текущей таймзоне браузера. Вы можете отобразить время в другой таймзоне с помощью метода toLocaleString, например:
(new Date()).toLocaleString("en-US", {timeZone: 'Europe/Paris'}) // "7/14/2021, 4:11:50 PM"
Также вы можете работать со значениями времени по всемирному координированному времени через UTC-методы. Например вы можете брать и изменять значения по координированному времени и дата в вашей таймзоне будет меняться:
const date = new Date();
console.log(date); // Wed Jul 14 2021 17:14:54 GMT+0300 (Moscow Standard Time)
console.log(date.getUTCHours()) // 14
console.log(date.getHours()) // 17
date.setUTCHours(10)
console.log(date.getUTCHours()) // 10
console.log(date.getHours()) // 13
С помощью этих методов, вы можете работать с датой без привязки к таймзоне. Однако если вам нужно перевести дату в другую таймзону, то легко это сделать не получится. Вы можете например создать метод, который будет создавать новую дату, передавая ей строкой дату с другой таймзоной:
function convertTZ(date, timeZone) {
return new Date((typeof date === "string" ? new Date(date) : date).toLocaleString("en-US", {timeZone: timeZone}));
}
const myDate = new Date()
console.log(myDate); // Wed Jul 14 2021 17:25:26 GMT+0300 (Moscow Standard Time)
const newDate = convertTZ(myDate, 'America/Los_Angeles')
console.log(newDate2); // Wed Jul 14 2021 07:25:26 GMT+0300 (Moscow Standard Time)
Как видим время поменялось на то, которое должно быть в переданной таймзоне, но на деле ваша дата остается все равно в вашей текущей таймзоне, у меня это “GMT+0300 (Moscow Standard Time)”.
Что же меняет Temporal API в работе с таймзонами? Ну во-первых, он дает нам полноценную возможность создавать даты с указание таймзоны и работать с этими таймзонами с помощью Temporal.ZonedDateTime:
const parisDate = new Temporal.ZonedDateTime.from({ timeZone: 'Europe/Paris', year: 2020, month: 7, day: 14 });
console.log(parisDate.toString()) // 2020-07-14T00:00:00+02:00[Europe/Paris]
И при этом таймзона даты действительно та, что вы задали и вы даже можете ее поменять и работать именно с датой в нужной вам таймзоне:
const losDate = parisDate.withTimeZone('America/Los_Angeles');
console.log(losDate.toString()); // 2020-07-13T15:00:00-07:00[America/Los_Angeles]
Если же вам таймзоны вообще не нужны, то в Temporal есть специальный объект для работы с датами без таймзоны:
const dateTime = Temporal.PlainDateTime.from({
year: 1995,
month: 12,
day: 7,
hour: 15
});
console.log(dateTime.toString()); // 1995-12-07T15:00:00
В плане таймзон в Temporal API действительно сделан огромный шаг вперед. Вы конечно и раньше могли бы подключить какую-нибудь библиотеку, где реализован функционал таймзон, которых реализовано в избытке. Однако библиотека это всегда лишний код, другой синтаксис, который должны знать все члены команды, проблема поддержки этой библиотеки со временем и так далее. Конечно для большинства это все мелочи, но зачем идти даже на них, если можно прямо в языке иметь полноценное API для работы с таймзонами и использовать его.
Заключение
Конечно же это далеко не все отличия Temporal API от Date в JavaScript. Я выбрала лишь те, что лично мне показались самыми интересными и наглядными, чтобы показать, что новое API действительно переработанная версия и новый подход к работе с датами. Больше подробностей читайте в официальной документации. Если у вас есть какие-то комментарии, то обязательно делитесь ими внизу статьи, я все обязательно прочитаю и каждому отвечу. Возможно вы знаете, как что-то можно было бы сделать лучше. А если хотите продолжения с другими сравнениями, тоже пишите об этом в комментариях и я обязательно продолжу эту тему.