日本語

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分の期間が作成されます。引数はyears, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanosecondsの順に対応していることに注意してください。

2. ISO 8601文字列から

Temporal.Duration.from()を使用して、ISO 8601期間文字列から期間を作成することもできます:

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

これは、文字列形式で保存されている期間や、外部ソースから受け取った期間を扱う場合に特に便利です。

3. Temporal.InstantTemporal.ZonedDateTimeなどでのadd()およびsubtract()メソッドの使用

Temporal.Durationを他のTemporal型(Temporal.InstantTemporal.ZonedDateTimeなど)に加算または減算すると、それらを減算した場合に2つの時点の差を表す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

期間の正規化

期間は時々、正規化されていない形式で表現されることがあります(例:P1Y12MP2Yに単純化できる)。normalized()メソッドは、期間を最もコンパクトな形式に単純化しようとします。ただし、月の日数の変動という複雑さを処理するためには、参照日が必要です。正しく正規化するには、Temporal.PlainDateTemporal.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が2024年1月1日を基準に正規化され、1月は31日まであるためP2M1Dになります。

時間コンポーネント(時、分、秒など)のみを扱っている場合は、参照日なしで正規化できます:

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(Internet Assigned Numbers Authority)タイムゾーンデータベースに依存しています。タイムゾーン変換を正確に処理するためには、環境に最新バージョンのIANAデータベースがあることを確認してください。

ベストプラクティス

よくある落とし穴

異なる文化圏における実世界でのユースケース

Temporal APIは、タイムゾーンの違いや文化的なニュアンスが重要なグローバルアプリケーションで特に有益です。以下にいくつかの例を挙げます:

結論

Temporal.Durationは、JavaScriptで時間間隔を扱うための堅牢で直感的な方法を提供します。その機能とベストプラクティスを理解することで、アプリケーションで正確かつ信頼性の高い期間計算を自信を持って実行できます。Temporal APIを採用することで、よりクリーンで保守しやすいコードになり、従来の日付と時刻の処理に伴うエラーのリスクを低減できます。

Temporal APIをさらに深く掘り下げる際には、公式ドキュメントを参照し、さまざまなシナリオで実験して、その能力を完全に把握することを忘れないでください。そのモダンな設計と包括的な機能により、TemporalはJavaScriptでの日付、時刻、期間の扱い方を革新するものとなるでしょう。