Русский

Полное руководство по использованию Temporal API в JavaScript для точных и интуитивно понятных вычислений временных интервалов, от создания до продвинутой арифметики и форматирования.

JavaScript Temporal.Duration: Искусство вычисления временных интервалов

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

Что такое Temporal.Duration?

Temporal.Duration представляет собой отрезок времени, выраженный в годах, месяцах, днях, часах, минутах, секундах и долях секунды (миллисекунды, микросекунды, наносекунды). В отличие от объектов Date, которые представляют конкретный момент времени, Temporal.Duration представляет собой количество времени. Он соответствует формату продолжительности ISO 8601 (например, P1Y2M10DT2H30M означает 1 год, 2 месяца, 10 дней, 2 часа и 30 минут). Temporal API разработан так, чтобы быть более интуитивным и менее подверженным ошибкам, чем устаревший объект Date.

Создание объектов Temporal.Duration

Существует несколько способов создания объектов Temporal.Duration:

1. Из простого объекта

Вы можете создать продолжительность, передав объект с желаемыми свойствами:

const duration = new Temporal.Duration(1, 2, 10, 2, 30, 0, 0, 0);
console.log(duration.toString()); // Вывод: P1Y2M10DT2H30M

Это создает продолжительность в 1 год, 2 месяца, 10 дней, 2 часа и 30 минут. Обратите внимание, что аргументы соответствуют следующему порядку: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds.

2. Из строки формата ISO 8601

Вы также можете создать продолжительность из строки формата ISO 8601 с помощью Temporal.Duration.from():

const duration = Temporal.Duration.from("P1Y2M10DT2H30M");
console.log(duration.toString()); // Вывод: P1Y2M10DT2H30M

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

3. Использование методов add() и subtract() с Temporal.Instant, Temporal.ZonedDateTime и т.д.

Когда вы добавляете или вычитаете Temporal.Duration из других типов Temporal (таких как Temporal.Instant или Temporal.ZonedDateTime), а затем вычитаете моменты времени друг из друга, возвращается Temporal.Duration, представляющий разницу между двумя точками во времени. Например:

const now = Temporal.Now.zonedDateTimeISO();
const later = now.add({ hours: 5 });
const duration = later.since(now);
console.log(duration.toString()); // Вывод: PT5H

Доступ к компонентам продолжительности

Вы можете получить доступ к отдельным компонентам объекта Temporal.Duration, используя его свойства:

const duration = Temporal.Duration.from("P1Y2M10DT2H30M");
console.log(duration.years);      // Вывод: 1
console.log(duration.months);     // Вывод: 2
console.log(duration.days);       // Вывод: 10
console.log(duration.hours);      // Вывод: 2
console.log(duration.minutes);     // Вывод: 30
console.log(duration.seconds);     // Вывод: 0
console.log(duration.milliseconds); // Вывод: 0
console.log(duration.microseconds); // Вывод: 0
console.log(duration.nanoseconds);  // Вывод: 0

Выполнение арифметических операций с продолжительностями

Объекты Temporal.Duration поддерживают сложение и вычитание с помощью методов add() и subtract(). Эти методы возвращают новый объект Temporal.Duration, представляющий результат операции.

const duration1 = Temporal.Duration.from("P1Y2M");
const duration2 = Temporal.Duration.from("P3M4D");

const addedDuration = duration1.add(duration2);
console.log(addedDuration.toString()); // Вывод: P1Y5M4D

const subtractedDuration = duration1.subtract(duration2);
console.log(subtractedDuration.toString()); // Вывод: P10M26D

Вы также можете объединять эти методы в цепочки для более сложных вычислений:

const duration = Temporal.Duration.from("P1D").add({ hours: 12 }).subtract({ minutes: 30 });
console.log(duration.toString()); // Вывод: P1DT11H30M

Метод negated() возвращает новый объект Temporal.Duration, в котором все компоненты имеют противоположный знак:

const duration = Temporal.Duration.from("P1Y2M10DT2H30M");
const negatedDuration = duration.negated();
console.log(negatedDuration.toString()); // Вывод: -P1Y2M10DT2H30M

Метод abs() возвращает новый объект Temporal.Duration, в котором все компоненты являются положительными значениями (абсолютными значениями):

const duration = Temporal.Duration.from("-P1Y2M10DT2H30M");
const absoluteDuration = duration.abs();
console.log(absoluteDuration.toString()); // Вывод: P1Y2M10DT2H30M

Метод with() позволяет создать новый экземпляр Temporal.Duration, в котором некоторые или все свойства изменены на новые значения. Если значение не указано в объекте-аргументе, будет использовано исходное значение продолжительности. Например:

const duration = Temporal.Duration.from("P1Y2M10DT2H30M");
const newDuration = duration.with({ years: 2, days: 5 });
console.log(newDuration.toString()); // Вывод: P2Y2M5DT2H30M

Нормализация продолжительностей

Продолжительности иногда могут быть выражены в ненормализованной форме (например, P1Y12M, что можно упростить до P2Y). Метод normalized() пытается упростить продолжительность до ее наиболее компактной формы. Однако для обработки сложностей, связанных с различной длиной месяцев, ему требуется дата отсчета. Для правильной нормализации вам понадобится экземпляр Temporal.PlainDate, Temporal.ZonedDateTime или Temporal.Instant.

Например, нормализация продолжительности, включающей месяцы и дни, требует даты отсчета:

const duration = Temporal.Duration.from("P1M32D");
const referenceDate = Temporal.PlainDate.from("2024-01-01");
const normalizedDuration = duration.normalized({ relativeTo: referenceDate });
console.log(normalizedDuration.toString()); // Вывод: P2M1D

В этом примере продолжительность P1M32D нормализуется относительно 1 января 2024 года, что приводит к P2M1D, поскольку в январе 31 день.

Если вы работаете только с компонентами времени (часы, минуты, секунды и т.д.), вы можете нормализовать их без даты отсчета:

const duration = Temporal.Duration.from("PT25H61M");
const normalizedDuration = duration.normalized({ relativeTo: null }); //или опустите аргумент relativeTo
console.log(normalizedDuration.toString()); // Вывод: P1DT2H1M

Сравнение продолжительностей

Вы можете сравнивать продолжительности с помощью метода compare(). Этот метод возвращает:

const duration1 = Temporal.Duration.from("P1Y");
const duration2 = Temporal.Duration.from("P6M");

const comparisonResult = Temporal.Duration.compare(duration1, duration2);
console.log(comparisonResult); // Вывод: 1

Практические примеры

1. Вычисление времени до события

Предположим, вы хотите рассчитать время, оставшееся до определенного события. Используйте Temporal.Now.zonedDateTimeISO() для получения текущего времени и вычтите из него дату события. Если дата события уже прошла, результат будет отрицательным.

const eventDate = Temporal.ZonedDateTime.from({ timeZone: 'America/Los_Angeles', year: 2024, month: 12, day: 25, hour: 9, minute: 0, second: 0 });
const now = Temporal.Now.zonedDateTimeISO('America/Los_Angeles');

const durationUntilEvent = eventDate.since(now);

console.log(durationUntilEvent.toString()); // Вывод: например, P262DT14H30M (в зависимости от текущей даты и времени)

2. Отслеживание продолжительности задач проекта

В управлении проектами вы можете использовать Temporal.Duration для отслеживания предполагаемой или фактической продолжительности задач.

const task1EstimatedDuration = Temporal.Duration.from("PT8H"); // 8 часов
const task2EstimatedDuration = Temporal.Duration.from("PT16H"); // 16 часов

const totalEstimatedDuration = task1EstimatedDuration.add(task2EstimatedDuration);
console.log(`Общая оценочная продолжительность: ${totalEstimatedDuration.toString()}`); // Вывод: Общая оценочная продолжительность: P1DT

3. Вычисление возраста

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

const birthDate = Temporal.PlainDate.from("1990-05-15");
const currentDate = Temporal.PlainDate.from("2024-01-20");

const ageDuration = currentDate.since(birthDate, { smallestUnit: 'years' });
console.log(`Примерный возраст: ${ageDuration.years} лет`); // Вывод: Примерный возраст: 33 года

4. Отображение продолжительности в удобочитаемом формате

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

function formatDuration(duration) {
  const parts = [];
  if (duration.years) parts.push(`${duration.years} год${duration.years > 1 ? 'а' : ''}`);
  if (duration.months) parts.push(`${duration.months} месяц${duration.months > 1 ? 'а' : ''}`);
  if (duration.days) parts.push(`${duration.days} д${duration.days === 1 ? 'ень' : 'ней'}`);
  if (duration.hours) parts.push(`${duration.hours} час${duration.hours > 1 ? 'а' : ''}`);
  if (duration.minutes) parts.push(`${duration.minutes} минут${duration.minutes === 1 ? 'а' : ''}`);
  if (duration.seconds) parts.push(`${duration.seconds} секунд${duration.seconds === 1 ? 'а' : ''}`);

  return parts.join(', ');
}

const duration = Temporal.Duration.from("P1Y2M10DT2H30M");
const formattedDuration = formatDuration(duration);
console.log(formattedDuration); // Вывод: 1 год, 2 месяца, 10 дней, 2 часа, 30 минут

Продвинутое использование и важные моменты

1. Обработка часовых поясов

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

2. Наименьшая единица и округление

Методы `since()` и `until()` часто принимают опции для определения наименьшей единицы измерения для результирующей продолжительности. Например, для вычисления времени *до* события с ограничением результата до дней.

const eventDate = Temporal.PlainDate.from("2024-12-25");
const now = Temporal.PlainDate.from("2024-01-20");

const durationUntilEvent = now.until(eventDate, { smallestUnit: 'days' });

console.log(durationUntilEvent.toString()); //пример вывода PT340D

3. Секунды координации

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

4. Часовые пояса IANA

Temporal API опирается на базу данных часовых поясов IANA (Internet Assigned Numbers Authority). Убедитесь, что в вашей среде установлена актуальная версия базы данных IANA для точной обработки преобразований часовых поясов.

Лучшие практики

Распространенные ошибки

Реальные примеры использования в разных культурах

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

Заключение

Temporal.Duration предоставляет надежный и интуитивно понятный способ работы с временными интервалами в JavaScript. Понимая его возможности и лучшие практики, вы сможете уверенно выполнять точные и надежные расчеты продолжительности в своих приложениях. Использование Temporal API ведет к более чистому, поддерживаемому коду и снижает риск ошибок, связанных с устаревшей обработкой дат и времени.

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