Привет, друзья!
В этой статье я хочу рассказать вам о Temporal
, новом API
для работы с датой и временем в JS
.
Источником вдохновения для меня послужила эта замечательная статья.
Обратите внимание: предложение находится на 3 стадии рассмотрения и может подвергнуться некоторым изменениям, так что воздержитесь от его использования в продакшне до официального утверждения (вероятно, это произойдет где-то в конце текущего года).
Если вам это интересно, прошу под кат.
Сегодня у нас имеется 2 механизма для работы с датой и временем в JS
:
- объект
Date
- конструктор
Intl.DateTimeFormat
Недостатки Date
:
- манипуляции с датой/временем являются сложными;
- поддерживается только
UTC
и время на компьютере пользователя; - поддерживается только григорианский календарь;
- разбор строк в даты подвержен ошибкам;
- объекты
Date
являются мутабельными, т.е. изменяемыми, например:
const today = new Date() const tomorrow = new Date(today.setDate(today.getDate() + 1)) console.log(tomorrow) // завтрашняя дата console.log(today) // тоже завтрашняя дата!
Это и многое другое вынуждает разработчиков использовать библиотеки вроде moment.js
(поддержка прекращена) или ее современные альтернативы типа dayjs
или date-fns
при интенсивной работе с датой/временем.
Intl.DateTimeFormat
предназначен исключительно для чувствительного к языку (локали) форматирования даты и времени.
Подробнее о нем и, в целом, об объекте Intl
можно почитать здесь.
Temporal
Вот что можно сделать для того, чтобы поиграть с Temporal
:
- создаем шаблон
JS-проекта
с помощьюcreate-snowpack-app
:
# temporal-test - название проекта # --template @snowpack/app-template-minimal - используемый шаблон # --use-yarn - использовать yarn для установки зависимостей # --no-git - не инициализировать Git-репозиторий yarn create snowpack-app temporal-test --template @snowpack/app-template-minimal --use-yarn --no-git # or npx create-snowpack-app ... # переходим в директорию cd temporal-test # открываем директорию в редакторе кода # требуется предварительная настройка code .
- устанавливаем полифил
@js-temporal/polyfill
yarn add @js-temporal/polyfill # or npm i @js-temporal/polyfill
- импортируем объекты и расширяем прототип
Date
вindex.js
:
import { Temporal, Intl, toTemporalInstant } from '@js-temporal/polyfill' Date.prototype.toTemporalInstant = toTemporalInstant
Temporal
предоставляет следующие возможности.
Текущие дата и время
Объект Temporal.Now
возвращает текущие дату и время:
// время (UTC) с начала эпохи, т.е. с 00:00:00 1 января 1970 года // в секундах Temporal.Now.instant().epochSeconds // в миллисекундах Temporal.Now.instant().epochMilliseconds // new Date().getTime() // текущая временная зона Temporal.Now.timeZone() // Asia/Yekaterinburg // дата и время в текущей локации с учетом временной зоны Temporal.Now.zonedDateTimeISO() // 2022-01-06T13:39:44.178384177+05:00[Asia/Yekaterinburg] // дата и время в другой временной зоне Temporal.Now.zonedDateTimeISO('Europe/London') // 2022-01-06T08:40:22.249422248+00:00[Europe/London] // дата и время в текущей локации без учета временной зоны Temporal.Now.plainDateTimeISO() // 2022-01-06T13:52:19.54213954 // дата в текущей локации Temporal.Now.plainDateISO() // время в текущей локации Temporal.Now.plainTimeISO()
«Мгновенные» дата и время
Объект Temporal.Instant
возвращает объект, представляющий фиксированную позицию во времени с точностью до наносекунд. Позиция (строка) форматируется согласно ISO 8601
следующим образом:
Temporal.Instant.from('2022-03-04T05:06+07:00') // 2022-03-03T22:06:00Z // 1 млрд. секунд с начала эпохи Temporal.Instant.fromEpochSeconds(1.0e9) // 2001-09-09T01:46:40Z
«Зонированные» дата и время
Объект Temporal.ZonedDateTime
возвращает объект, представляющий фиксированную позицию во времени с точностью до наносекунд с учетом временной зоны и календарной системы:
new Temporal.ZonedDateTime( 123456789000000000n, // наносекунды с начала эпохи (bigint) Temporal.TimeZone.from('Asia/Yekaterinburg'), // временная зона Temporal.Calendar.from('iso8601') // дефолтный календарь ) // 1973-11-30T02:33:09+05:00[Asia/Yekaterinburg] Temporal.ZonedDateTime.from('2025-09-05T02:55:00+01:00[Europe/London]') // 2025-09-05T02:55:00+01:00[Europe/London] Temporal.ZonedDateTime.from({ timeZone: 'America/New_York', year: 2025, month: 2, day: 28, hour: 10, minute: 15, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0 }) // 2025-02-28T10:15:00-05:00[America/New_York]
«Обычные» дата и время
Обычные (plain) дата и время возвращают значения (год, месяц, день, час, минута, секунда и т.д.) без учета временной зоны:
Temporal.PlainDate
— дата:
// 2022-01-31 new Temporal.PlainDate(2022, 1, 31) Temporal.PlainDate.from('2022-01-31')
Temporal.PlainTime
— время:
// 12:00:00 new Temporal.PlainTime(12, 0, 0) Temporal.PlainTime.from('12:00:00')
Temporal.PlainDateTime
— дата и время:
// 2022-01-31T12:00:00 new Temporal.PlainDateTime(2022, 1, 31, 12, 0, 0) Temporal.PlainDateTime.from('2022-01-31T12:00:00')
Temporal.PlainYearMonth
— месяц и год:
// июнь 2022 года // 2022-06 new Temporal.PlainYearMonth(2022, 6) Temporal.PlainYearMonth.from('2022-06')
Temporal.PlainMonthDay
— месяц и день:
// 4 мая // 05-04 new Temporal.PlainMonthDay(5, 4) Temporal.PlainMonthDay.from('05-04')
Значение даты и времени
Объект Temporal
содержит ряд полезных свойств/геттеров:
const date = Temporal.ZonedDateTime.from( '2022-01-31T12:13:14+05:00[Asia/Yekaterinburg]' ) date.year // 2022 date.month // 1 date.day // 31 date.hour // 12 date.minute // 13 date.second // 14 date.millisecond // 0 // и т.д.
Другие свойства:
dayOfWeek
— от1
для понедельника до7
для воскресенья;dayOfYear
— от1
до365
или366
в високосный год;weekOfYear
— от1
до52
или53
;daysInMonth
—28
,29
,30
или31
;daysInYear
—365
или366
;inLeapYear
—true
для високосного года.
Сравнение и сортировка даты и времени
Все объекты Temporal
содержат метод compare
, который возвращает:
0
, когдаdate1
иdate2
равны;1
, когдаdate1
«больше» (наступит или наступила позже), чемdate2
;-1
, когдаdate1
«меньше» (наступит или наступила раньше), чемdate2
.
const date1 = Temporal.Now.plainDateISO() const date2 = Temporal.PlainDate.from('2022-04-05') Temporal.PlainDateTime.compare(date1, date2) // -1
Разумеется, данный метод можно использовать для сортировки:
const sortedDates = [ '2022-01-01T00:00:00[Europe/London]', '2022-01-01T00:00:00[Asia/Yekaterinburg]', '2022-01-01T00:00:00[America/New_York]' ] .map((d) => Temporal.ZonedDateTime.from(d)) .sort(Temporal.ZonedDateTime.compare) console.log(sortedDates) /* [ '2022-01-01T00:00:00+05:00[Asia/Yekaterinburg]', '2022-01-01T00:00:00+00:00[Europe/London]', '2022-01-01T00:00:00-05:00[America/New_York]', ] */
Вычисление даты и времени
Все объекты Temporal
содержат методы add
, subtract
и round
для продолжительности (duration).
Продолжительность можно определить с помощью объекта Temporal.Duration
, передав ему years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
, а также знак (sign) -1
для отрицательной и 1
для положительной продолжительности. Вместе с тем, стоит отметить, что указанные методы принимают любые подобные продолжительности (duration-like) значения без необходимости создания специального объекта.
// 2022-01-06 const today = new Temporal.PlainDate(2022, 1, 6) // прибавляем 1 день const tomorrow = today.add({ days: 1 }) // или // прибавляем 2 дня const dayAfterTomorrow = today.add(Temporal.Duration.from({ days: 2 })) // вычитаем 1 день const yesterday = today.subtract({ days: 1 }) console.log(tomorrow) // 2022-01-07 console.log(dayAfterTomorrow) // 2022-01-08 console.log(yesterday) // 2022-01-05 // сегодняшняя дата осталась неизменной // объекты `Temporal` являются иммутабельными (неизменяемыми) console.log(today) // 2022-01-06 const duration = Temporal.Duration.from({ days: 2, hours: 12 }) const durationInDays = duration.round({ smallestUnit: 'days' }) // 3 дня // https://day.js.org/docs/en/durations/as-iso-string console.log(durationInDays) // P3D
Предположим, что у нас имеется такой инпут:
<input type="date" class="calendar-input" />
Вот как можно установить «недельное» ограничение на выбор даты с помощью Temporal
и отформатировать вывод с помощью Intl.DateTimeFormat
:
const today = Temporal.Now.plainDateISO() const afterWeek = today.add({ days: 7 }) const calendarInput = document.querySelector('.calendar-input') calendarInput.min = today calendarInput.max = afterWeek calendarInput.value = today const dateFormatter = new Intl.DateTimeFormat([], { dateStyle: 'long' }) calendarInput.onchange = ({ target: { value } }) => { const date = Temporal.PlainDate.from(value) const formattedDate = dateFormatter.format(date) console.log(formattedDate) // например, 14 января 2022 г. }
Методы until
и since
возвращают объект Temporal.Duration
, описывающий время до или после указанных даты и времени на основе текущей даты/времени:
// количество месяцев, оставшихся до d1 d1.until().months // дней до d2 d2.until().days // недель, прошедших с d3 d3.since().weeks
Метод equals
предназначен для определения идентичности (равенства) даты/времени:
const d1 = Temporal.PlainDate.from('2022-01-31') const d2 = d1.add({ days: 1 }).subtract({ hours: 24 }) console.log( d1.equals(d2) ) // true console.log( Temporal.PlainDate.compare(d1, d2) ) // 0
Строковые значения даты и времени
Все объекты Temporal
содержат метод toString
, который возвращает строковое представление даты/времени:
Temporal.Now.zonedDateTimeISO().toString() // 2022-01-06T16:30:51.380651378+05:00[Asia/Yekaterinburg] Temporal.Now.plainDateTimeISO().toString() // 2022-01-06T16:32:47.870767866 Temporal.Now.plainDateISO().toString() // 2022-01-06
Для форматирования даты/времени можно использовать объекты Intl
или Date
:
// объект `Temporal`, не строка const d1 = Temporal.Now.plainDateISO() // ok new Intl.DateTimeFormat('ru-RU').format(d1) // 06.01.2022 new Intl.DateTimeFormat('en-US').format(d1) // 1/6/2022 new Intl.DateTimeFormat('de-DE').format(d1) // 6.1.2022 // не ok new Date(d1).toLocaleDateString() // error // строка const d2 = Temporal.Now.plainDateISO().toString() // ok new Date(d2).toLocaleDateString() // 06.01.2022 // не ok new Intl.DateTimeFormat().format(d2) // error // но new Intl.DateTimeFormat().format(new Date(d2)) // ok
Для преобразования объекта Date
в объект Temporal.Instant
предназначен метод toTemporalInstant
объекта Date
. Для обратного преобразования используется свойство epochMilliseconds
объектов Temporal.Instant
и Temporal.ZonedDateTime
:
// туда const legacyDate1 = new Date() const temporalInstant = legacyDate1.toTemporalInstant() // сюда const legacyDate2 = new Date(temporalInstant.epochMilliseconds)
Вместо заключения
Появление в JS
нового API
для работы с датой/временем — это, конечно, хорошо, но:
- интерфейс
Temporal
сложно назвать user friendly, в отличие отAPI
, предоставляемогоdayjs
и другими популярнымиJS-библиотеками
для работы с датой/временем; - излишне многословный синтаксис: например, зачем использовать
Temporal.Now.plainDateTimeISO(Temporal.Now.timeZone()).toString()
, который все равно придется форматировать, когда естьnew Date().toLocaleString()
; - отсутствие возможности форматирования даты/времени;
- для интеграции с
Date
иIntl
требуются дополнительные преобразования и т.д.
Вывод:
- если в приложении ведется или планируется активная работа с датой/временем, используйте библиотеку;
// yarn add dayjs import dayjs from 'dayjs' // 6.8K! // plugins import isLeapYear from 'dayjs/plugin/isLeapYear' import utc from 'dayjs/plugin/utc' import dayOfYear from 'dayjs/plugin/dayOfYear' import weekday from 'dayjs/plugin/weekday' import isoWeeksInYear from 'dayjs/plugin/isoWeeksInYear' import minMax from 'dayjs/plugin/minMax' import duration from 'dayjs/plugin/duration' import relativeTime from 'dayjs/plugin/relativeTime' import isBetween from 'dayjs/plugin/isBetween' import advancedFormat from 'dayjs/plugin/advancedFormat' import localizedFormat from 'dayjs/plugin/localizedFormat' import timezone from 'dayjs/plugin/timezone' import updateLocale from 'dayjs/plugin/updateLocale' import toArray from 'dayjs/plugin/toArray' import toObject from 'dayjs/plugin/toObject' import arraySupport from 'dayjs/plugin/arraySupport' import objectSupport from 'dayjs/plugin/objectSupport' // locale import 'dayjs/locale/ru' // plugins dayjs.extend(isLeapYear) dayjs.extend(utc) dayjs.extend(weekday) dayjs.extend(dayOfYear) dayjs.extend(isoWeeksInYear) dayjs.extend(minMax) dayjs.extend(duration) dayjs.extend(relativeTime) dayjs.extend(isBetween) dayjs.extend(advancedFormat) dayjs.extend(localizedFormat) dayjs.extend(timezone) dayjs.extend(updateLocale) // toArray() dayjs.extend(toArray) // toObject() dayjs.extend(toObject) // dayjs(array) dayjs.extend(arraySupport) // dayjs(object) dayjs.extend(objectSupport) // locale // global dayjs.locale('ru') // instance // dayjs().locale('ru').format() dayjs.updateLocale('ru', { ordinal: (n) => `${n}ое` }) const localeDateTime1 = dayjs().format('DD.MM.YYYY, HH:mm:ss') console.log(localeDateTime1) // 06.01.2022, 18:55:38 // native way // new Date().toLocaleString() /* new Intl.DateTimeFormat([], { dateStyle: 'short', timeStyle: 'medium' // with seconds }).format() */ const localeDateTime2 = dayjs().format('D MMMM YYYY г., HH:mm:ss') console.log(localeDateTime2) // 6 января 2022 г., 19:01:01 // native way /* new Intl.DateTimeFormat([], { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }).format() */ // utc plugin const utcDateTime = dayjs.utc().format('DD.MM.YYYY, HH:mm') console.log(utcDateTime) // 06.01.2022, 14:11 // dayjs(date).isValid() // weekday plugin const dateFormat = 'DD.MM.YYYY' const nextMonday = dayjs().weekday(7).format(dateFormat) console.log(nextMonday) // 10.01.2022 // setters/getters // millisecond, second, minute, hour, // date, day (0 indexed), month (0 indexed), year // and some special units // or // get('year') | get('y') // set('date', 1) | set('D', 1) // dayOfYear and isLeapYear plugins const halfOfYearDate = dayjs() .dayOfYear(dayjs().isLeapYear() ? 366 / 2 : 365 / 2) .format(dateFormat) console.log(halfOfYearDate) // 02.07.2022 // isoWeeksInYear plugin // isLeapYear plugin is required const weeksInThisYear = dayjs().isoWeeksInYear() console.log(weeksInThisYear) // 52 // minMax plugin // max(date1, date2, ...dateN) | max(date[]) const maxDate = dayjs .max(dayjs(), dayjs('2021-12-31'), dayjs('2022-05-03')) .format(dateFormat) console.log(maxDate) // 03.05.2022 // calculations // subtract(value: number, unit?: string) | subtract({ unit: value }) const today = dayjs() const yesterday = today.subtract(1, 'day').format(dateFormat) // or using duration // duration plugin is required // duration(value: number, unit?: string) | duration({ unit: value }) const anotherYesterday = today .subtract(dayjs.duration({ day: 1 })) .format(dateFormat) // native way /* const today = new Date() const yesterday = new Date( today.setDate(today.getDate() - 1) ).toLocaleDateString() */ const dayAfterTomorrow = today.add(2, 'days').format(dateFormat) console.log(yesterday) // 05.01.2022 console.log(dayAfterTomorrow) // 08.01.2022 const lastMonday = dayjs().startOf('week').format(dateFormat) console.log(lastMonday) // 03.01.2022 const lastDayOfCurrentMonth = dayjs().endOf('month').format('dddd') console.log(lastDayOfCurrentMonth) // понедельник const timeFormat = 'HH:mm' // get UTC offset in minutes // convert minutes to hours const myUtcOffset = dayjs().utcOffset() / 60 console.log(myUtcOffset) // 5 // set UTC offset in hours (from -16 to 16) or minutes const localTimeFromUtc = dayjs.utc().utcOffset(myUtcOffset).format(timeFormat) console.log(localTimeFromUtc) // 19:55 // relativeTime plugin const d1 = '1380-09-08' // fromNow(withoutSuffix?: boolean) // from(date, withoutSuffix?) const fromNow = dayjs(d1).fromNow() console.log(fromNow) // 641 год назад const d2 = '2022-07-02' // toNow(withoutSuffix?: boolean) // to(date, withoutSuffix?) const toDate = dayjs().to(d2) console.log(toDate) // через 6 месяцев // difference const d3 = dayjs('2021-07-01') const d4 = dayjs('2022-01-01') // date1.diff(date2, unit?: string, withoutTruncate?: boolean) const diffInMonths = d4.diff(d3, 'month') console.log(diffInMonths) // 6 // unix() - in seconds const unixTimestampInMs = dayjs(d3).valueOf() console.log(unixTimestampInMs) // 1625079600000 const daysInCurrentMonth = dayjs().daysInMonth() console.log(daysInCurrentMonth) // 31 // toJSON() const currentDateInIso = dayjs().toISOString() console.log(currentDateInIso) // 2022-01-07T12:49:51.238Z // query // default unit is ms // isBefore(date, unit?: string) // isSame(date, unit?) // isAfter(date, unit?) // isSameOrBefore | isSameOrAfter plugins are required // isSameOrBefore(date, unit?) | isSameOrAfter(date, unit?) // isBetween plugin is required // [ - inclusion of date, ( - exclusion // isBetween(date1, date2, unit?: string, inclusivity?: string) const isTodayStillWinter = dayjs().isBetween( '2021-12-01', '2022-03-01', 'day', '[)' ) console.log(isTodayStillWinter) // true const myTimezone = dayjs.tz.guess() console.log(myTimezone) // Asia/Yekaterinburg const currentDateTimeAdvanced = dayjs().format( 'Do MMMM YYYY г., HH часов mm минут (z)' ) console.log(currentDateTimeAdvanced) // 7ое январь 2022 г., 18 часов 13 минут (GMT+5) const currentDateTimeLocalized = dayjs().format('LLLL (zzz)') console.log(currentDateTimeLocalized) // пятница, 7 января 2022 г., 18:44 (Yekaterinburg Standard Time) // humanize(withSuffix?: boolean) const inADay = dayjs.duration(1, 'day').humanize(true) console.log(inADay) // через день
- если манипуляции с датой/временем простые, немногочисленные и не предполагают разного формата вывода, используйте
Temporal
+Date
илиIntl.DateTimeFormat
.
Пожалуй, это все, чем я хотел поделиться с вами в данной статье.
Благодарю за внимание и happy coding!
ссылка на оригинал статьи https://habr.com/ru/company/timeweb/blog/645075/
Добавить комментарий