中文

一篇关于使用 JavaScript Temporal API 进行精确且直观的时间间隔计算的综合指南,涵盖从创建基本时长到高级算术和格式化的所有内容。

JavaScript Temporal Duration:精通时间间隔计算

JavaScript 的 Temporal API 引入了一种现代化且强大的方式来处理日期、时间和时间间隔。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 分钟的时长。请注意,参数按以下顺序对应:yearsmonthsweeksdayshoursminutessecondsmillisecondsmicrosecondsnanoseconds

2. 从 ISO 8601 字符串创建

您也可以使用 Temporal.Duration.from() 从 ISO 8601 时长字符串创建时长:

const duration = Temporal.Duration.from("P1Y2M10DT2H30M");
console.log(duration.toString()); // 输出: P1Y2M10DT2H30M

这在处理以字符串格式存储或从外部来源接收的时长时特别有用。

3. 使用 add()subtract() 方法与 Temporal.InstantTemporal.ZonedDateTime 等结合

当您从其他 Temporal 类型(如 Temporal.InstantTemporal.ZonedDateTime)中添加或减去 Temporal.Duration 时,如果您再将它们相减,会返回一个代表两个时间点之间差异的 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.PlainDateTemporal.ZonedDateTimeTemporal.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 是相对于 2024 年 1 月 1 日进行规范化的,结果为 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} 年`);
  if (duration.months) parts.push(`${duration.months} 月`);
  if (duration.days) parts.push(`${duration.days} 天`);
  if (duration.hours) parts.push(`${duration.hours} 小时`);
  if (duration.minutes) parts.push(`${duration.minutes} 分钟`);
  if (duration.seconds) parts.push(`${duration.seconds} 秒`);

  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.PlainDateTemporal.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(互联网号码分配局)时区数据库。请确保您的环境具有最新版本的 IANA 数据库,以准确处理时区转换。

最佳实践

常见陷阱

不同文化背景下的真实世界用例

在时区差异和文化细微差别显著的全球应用程序中,Temporal API 可能特别有用。以下是一些示例:

结论

Temporal.Duration 提供了一种在 JavaScript 中处理时间间隔的强大而直观的方式。通过理解其功能和最佳实践,您可以自信地在应用程序中执行准确可靠的时长计算。拥抱 Temporal API 可以带来更清晰、更易于维护的代码,并减少与旧版日期和时间处理相关的错误风险。

当您深入研究 Temporal API 时,请记得查阅官方文档并尝试不同的场景,以充分掌握其功能。凭借其现代化的设计和全面的功能,Temporal 将彻底改变我们在 JavaScript 中处理日期、时间和时长的方式。