Temporal JavaScript: Дата и Время

Обработка дат и времени в JavaScript уже давно вызывает недовольство разработчиков. Встроенный объект Date, созданный на заре существования JavaScript, имеет множество ограничений и особенностей, которые усложняют работу с датами и временем.

К счастью для нас, предложение Temporal направлено на решение этих проблем путем предоставления современного, более интуитивно понятного API для манипулирования датой и временем.

В этой статье мы рассмотрим некоторые проблемы, связанные с работой с date, рассмотрим, что такое Temporal API и как он будет работать, а также то, что вы можете использовать до тех пор, пока Temporal не будет готов к использованию в рабочей среде.

Текущие проблемы с датой в JavaScript

1. Изменяемый API

Объект Date является изменяемым, что может привести к ошибкам и неожиданному поведению:

// Current Date behavior is mutable
const date = new Date('January 1, 2024');
date.setMonth(1); // Modifies the original date object
console.log(date); // February 1, 2024

// This mutability can lead to bugs when passing dates between functions
function processDate(date) {
  date.setDate(date.getDate() + 1); // Modifies the original date!return date;
}

2. Запутанная нумерация месяцев

Месяцы в дате отсчитываются от нуля (0-11), а дни - от единицы (1-31).:

// Confusing month numbering
const date = new Date(2024, 0, 1); // January 1, 2024
console.log(date.getMonth()); // 0 (January)

3. Ограниченная поддержка часовых поясов

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

// Time zone handling is system-dependent
const date = new Date('2024-01-01T00:00:00Z');
console.log(date.toString()); // Will show different results based on system timezone

Что такое Temporal API?

Temporal - это предлагаемый новый JavaScript API, который предоставляет современное решение для работы с датами, временем и временными метками. В настоящее время это предложение находится на стадии 3, что означает, что оно находится на завершающей стадии разработки, но еще не готово для использования в производственной среде.

Ключевые понятия Temporal:

  • Неизменяемый по умолчанию: Все временные объекты неизменяемы
  • Четкое разделение задач: Разные объекты для разных вариантов использования
  • Четкая обработка часовых поясов:
  • Улучшенная поддержка работы с часовыми поясами
  • Согласованная индексация: Все единицы измерения нумеруются на основе 1

Ключевые особенности Temporal

1. Различные типы для различных нужд

// PlainDate for working with calendar dates
const date = Temporal.PlainDate.from('2024-01-01');

// PlainTime for working with wall-clock time
const time = Temporal.PlainTime.from('09:00:00');

// ZonedDateTime for working with specific time zones
const zonedDateTime = Temporal.ZonedDateTime.from('2024-01-01T09:00:00[America/New_York]');

В этом примере Temporal предоставляет разные типы объектов для разных вариантов использования:

  • PlainDate используется, когда вас интересуют только календарные даты без указания времени или часового пояса. Идеально подходит для дней рождения, праздников и т.д. PlainTime определяет время независимо от даты или часового пояса. Полезно для повторяющихся событий, таких как "ежедневный подъем в 9 утра". ZonedDateTime объединяет информацию о дате, времени и часовом поясе для полной обработки временных меток. Отлично подходит для планирования совещаний в разных часовых поясах.

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

2. Неизменяемые операции

// All operations return new objects instead of modifying the original
const date = Temporal.PlainDate.from('2024-01-01');
const nextMonth = date.add({ months: 1 }); 
console.log(date.toString()); // '2024-01-01' - original unchanged
console.log(nextMonth.toString()); // '2024-02-01' - new object

Этот пример демонстрирует, как неизменяемый дизайн Temporal предотвращает случайные изменения и делает арифметику дат более предсказуемой.

В API current Date такие методы, как setMonth(), изменяют исходный объект, что может привести к ошибкам при использовании этого объекта в нескольких местах. В отличие от этого, методы Temporal всегда возвращают новые объекты, оставляя исходный нетронутым.

3. Улучшенная поддержка часовых поясов

// Explicit time zone handling
const nyDateTime = Temporal.ZonedDateTime.from({
  timeZone: 'America/New_York',
  year: 2024,
  month: 1,
  day: 1,
  hour: 9
});

const tokyoDateTime = nyDateTime.withTimeZone('Asia/Tokyo');
console.log(tokyoDateTime.toString()); // '2024-01-01T23:00:00+09:00[Asia/Tokyo]'

В отличие от текущего Date API, который часто приводит к путанице с неявными преобразованиями часовых поясов, Temporal делает операции с часовыми поясами явными и простыми:

  1. Мы создаем объект ZonedDateTime специально для часового пояса Нью-Йорка, в котором четко указаны все компоненты (год, месяц, день, час). Это явное создание предотвращает любую двусмысленность в отношении того, с каким часовым поясом мы работаем. Используя функцию withTimeZone(), мы можем легко переводить время между поясами без сложных вычислений. Перевод времени из Нью-Йорка в Токио выполняется автоматически. Результирующая строка вывода включает полное смещение часового пояса (+09:00) и название часового пояса ([Азия/Токио]), что обеспечивает полную ясность в отношении отображаемого времени.

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

Сравнение даты, международного и временного значений

Текущий подход с указанием даты и международных данных:

// Current approach using Date and Intl
const date = new Date('2024-01-01T09:00:00Z');
const formatter = new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  dateStyle: 'full',
  timeStyle: 'long'
});

console.log(formatter.format(date)); // 'Monday, January 1, 2024 at 4:00:00 AM EST'

При нынешнем подходе мы создаем временную метку UTC, используя Date, нам нужен отдельный Intl.Объект DateTimeFormat для форматирования, мы неявно обрабатываем преобразование часовых поясов во время форматирования и имеем меньший контроль над точным форматом вывода. Итоговый результат показывает 4:00 утра по восточному времени, потому что мы задали дату как 09:00 UTC и отформатировали ее в часовом поясе Нью-Йорка. Это неявное преобразование может привести к путанице и ошибкам.

Будущий подход с учетом временных:

// Future approach using Temporal
const datetime = Temporal.ZonedDateTime.from('2024-01-01T09:00:00[America/New_York]');
console.log(datetime.toLocaleString('en-US', {
  weekday: 'long',
  year: 'numeric',
  month: 'long',
  day: 'numeric',
  hour: 'numeric',
  minute: '2-digit',
  second: '2-digit',
  timeZoneName: 'short'
})); // 'Monday, January 1, 2024 at 9:00:00 AM EST'

С помощью Temporal мы создаем ZonedDateTime с явным указанием часового пояса, а параметры форматирования напрямую интегрированы в API. Время (9:00 утра) - это именно то, что мы указали для Нью-Йорка, без каких-либо неявных преобразований. Это делает поведение кода более предсказуемым и понятным.

При таком подходе арифметика также становится более интуитивно понятной.

const nextWeek = datetime.add({ weeks: 1 });
const duration = datetime.until(nextWeek);
console.log(nextWeek.toPlainDate().toString()); // '2024-01-08'
console.log(duration.toString()); // 'PT168H'

В этом примере мы можем увидеть несколько ключевых преимуществ подхода Temporal:

Явная обработка часовых поясов: создавая ZonedDateTime с помощью [America/New_York], мы явно указываем, с каким часовым поясом мы работаем.

Нет никакой двусмысленности в отношении того, является ли время UTC, местным или в другом часовом поясе. Интегрированное форматирование: метод toLocaleString() предоставляет простой и унифицированный способ форматирования дат без использования отдельного объекта formatter.

Все параметры форматирования аналогичны тем, которые вы использовали бы в Intl.DateTimeFormat, что обеспечивает удобство работы и упрощает API. Интуитивно понятная арифметика: Методы add() и until() демонстрируют, как Temporal упрощает вычисления даты и времени: add({ недели: 1 }) ясно показывает, что мы добавляем одну неделю функция until() возвращает правильный объект duration, который можно легко понять и которым можно манипулировать Результирующая длительность "PT168H" представляет собой период времени (P), равный 168 часам (T168H), в соответствии с форматом продолжительности ISO 8601

Безопасность типов: благодаря наличию различных типов, таких как ZonedDateTime и PlainDate, Temporal помогает предотвратить распространенные ошибки. Метод toPlainDate() явно преобразует представление только даты, когда нам не нужна информация о времени.

Такой подход устраняет многие ошибки и неявное поведение, которые затрудняют работу current Date API, обеспечивая при этом более мощный и гибкий способ работы с датами и временем.