Temporal入門その1 ～ Temporalクラスの使い分けと相互変換について

# Temporalとは

Temporalとは、JavaScriptに導入が予定されている(2023年時点)、Date型に代わる新しい日付処理APIです。
https://tc39.es/proposal-temporal/docs/index.html

https://tc39.es/proposal-temporal/docs/index.html

現在TC39のStage 3で、仕様策定に関する議論が行われています。 https://github.com/tc39/proposal-temporal

https://github.com/tc39/proposal-temporal

従来のDate型の問題点を解消しており、タイムゾーンやサマータイムに関する処理も行うことができるようになっています。

# Temporalのクラス

従来のDate型は、`new Date()`のようにDate型のインスタンスを作成して使用していました。
Temporalには以下の11個のクラスがあり、それぞれ用途によって使い分ける必要があります。

- `Temporal.Now`
- `Temporal.Instant`
- `Temporal.ZonedDateTime`
- `Temporal.PlainDate`
- `Temporal.PlainTime`
- `Temporal.PlainDateTime`
- `Temporal.PlainYearMonth`
- `Temporal.PlainMonthDay`
- `Temporal.Duration`
- `Temporal.TimeZone`
- `Temporal.Calendar`

### 時刻を表すクラス

Temporalで時刻を表すには、以下の3タイプのクラスを利用します。

|クラス名|役割|
|--|--|
`Temporal.Instant`|Unix Timeに相当する。カレンダー情報やタイムゾーン情報を持たない、ただの時刻。(**Exact Time**と呼ばれる)<br>単純な時刻の足し算や引き算に使う。
`Temporal.PlainDateTime`(年月日時分秒)<br>`Temporal.PlainDate`(年月日のみ)<br>`Temporal.PlainTime`(時分秒のみ)<br>`Temporal.PlainYearMonth`(年月のみ)<br>`Temporal.PlainMonthDay`(月日のみ)|カレンダー情報を扱うことができるクラス。曜日やうるう年を考慮した日付処理を行うことができる。(**Wall-clock time**と呼ばれる)
`Temporal.ZonedDateTime`|カレンダー情報とタイムゾーン情報を持っている時刻。サマータイムを考慮した処理ができる。

これらのクラスを使用するには、それぞれ以下のように`from()`メソッドでインスタンスを生成します。

```js
// Temporal.Instant型
const instant = Temporal.Instant.from('2019-03-30T00:45Z');
// or
const instant = Temporal.Instant.fromEpochSeconds(1553906700); // unix time(秒)からの変換
// or
const instant = Temporal.Instant.fromEpochMilliseconds(1553906700 * 1000); // unix time(ミリ秒)からの変換

// Temporal.PlainDateTime型
//   (その他のTemporal.PlainXXX型も同様)
const plainDateTime = Temporal.PlainDateTime.from('1995-12-07T03:24:30');
// or
const plainDateTime = Temporal.PlainDateTime.from({
  year: 1995,
  month: 12,
  day: 7,
  hour: 3,
  minute: 24,
  second: 30,
  millisecond: 0,
  microsecond: 3,
  nanosecond: 500
});

// Temporal.ZonedDateTime型
const zonedDateTime = Temporal.ZonedDateTime.from("2020-08-05T20:06:13+09:00[Asia/Tokyo][u-ca=japanese]");
// or
const zonedDateTime = Temporal.ZonedDateTime.from({
  timeZone: "Asia/Tokyo",
  year: 2023,
  month: 4,
  day: 8,
  hour: 22,
  minute: 34,
  second: 18,
  millisecond: 4,
  microsecond: 2,
  nanosecond: 1,
});
```

`Temporal.ZonedDateTime`は一番保持できる情報量が多く、他の型への変換も可能な便利なクラスです。
ただし、日付を扱う上で、全ての処理でタイムゾーンの情報が得られるわけではありません。また、そもそも処理にタイムゾーン情報が必要ない場合もあるでしょう。

どのクラスを使うべきかは、おそらく以下のような手順で判断すればいいと思います。

1. Unix Timeで表せるような単純な日付時刻（例：「`2005年6月12日14時26分`」）の和や差を求めたいだけの場合、`Temporal.Instant`を使う
1. 「翌月の日付」や「先週の同じ曜日の日付」など、カレンダーに基づいた処理を行いたい場合、`Temporal.PlainXXX`を使う
1. サマータイムや、別のタイムゾーンとの比較など、より詳細な情報が必要な場合は`Temporal.ZonedDateTime`を使う

また、`Temporal.Instant`は、従来使われていたDate型との間で相互に変換することもできます。

```js
// Date型からTemporal.Instant型に変換
const date = new Date();
date.toTemporalInstant(); //=> Temporal.Instant型

// Temporal.Instant型からDate型に変換
const instant = Temporal.Now.instant();
new Date(instant.epochMilliseconds); // Date型
```

Temporalのそれぞれのクラスは相互に変換することができます。
どのクラスがどのクラスに変換できるかを示したのが、以下の図です。

```mermaid
flowchart TB;
  subgraph Temporal
    direction TB
    B[Temporal.Instant<br>Unix Timeのような単なる時刻];
    C[Temporal.ZonedDateTime<br>タイムゾーン情報がある完全な時刻];
    B --".toZonedDateTimeISO()"--> C;
    C --".toInstant()"--> B;
    C --".toPlainXXX()"--> Temporal.PlainXXX:カレンダー情報付きの時刻;
    Temporal.PlainXXX:カレンダー情報付きの時刻 --".toZonedDateTime()"--> C;
    subgraph Temporal.PlainXXX:カレンダー情報付きの時刻
      direction TB
      T["Temporal.PlainTime (時分秒)"];
      YM["Temporal.PlainYearMonth (年月)"];
      DT["Temporal.PlainDateTime (年月日時分秒)"];
      MD["Temporal.PlainMonthDay (月日)"];
      D["Temporal.PlainDate (年月日)"];
      T --".toPlainDateTime()"--> DT;
      DT --".toPlainTime()"--> T;
      D --".toPlainDateTime"--> DT;
      DT --".toPlainDate()"--> D;
      DT --".toPlainMonthDay()"--> MD;
      D --".toPlainYearMonth"--> YM;
      YM --".toPlainDate()"--> D;
      DT --".toPlainYearMonth()"--> YM;
      D --".toPlainMonthDay()"--> MD;
      MD --".toPlainDate()"--> D;
    end
  end
  subgraph Date
    direction TB
    A["従来のDate型"];
    A --".toTemporalInstant()"--> B;
    B --"new Date(instant.epochMilliseconds)"--> A;
  end
```

この図を載せたかっただけです。

### その他のクラス


##### 現在時刻を取得する`Temporal.Now`

`Temporal.Now`には現在時刻を取得するためのメソッドが含まれています。

```ts
Temporal.Now.timeZoneId(); // 現在のシステム設定のタイムゾーン (例："Asia/Tokyo")
Temporal.Now.instant(); // 現在時刻が入ったTemporal.Instant型
Temporal.Now.plainDateTimeISO(); // 現在時刻が入ったTemporal.PlainDateTime型
Temporal.Now.plainDateISO(); // 現在時刻が入ったTemporal.PlainDate型
Temporal.Now.plainTimeISO(); // 現在時刻が入ったTemporal.PlainTime型
Temporal.Now.zonedDateTimeISO(); // 現在時刻が入ったTemporal.ZonedDateTime型
```

##### 「時間」を表す`Temporal.Duration`

`Temporal.Duration`は、時刻ではなく時間を表します。

```ts
// 1年と1日という「期間」を表す
const duration = Temporal.Duration.from({ years: 1, days: 1 });

// 符号を反転する（この場合はマイナス1年とマイナス1日になる）
duration.negated();
// 符号(-1なら負、0なら0時間0分0秒、1なら正)
duration.sign;
// 絶対値を取る
duration.abs();
// Temporal.Duration同士の足し算
duration.add(Temporal.Duration.from({ days: 1 }));
// Temporal.Duration同士の引き算
duration.subtract(Temporal.Duration.from({ days: 1 }));
// 1日は24時間
Temporal.Duration.from({ days: 1 }).total({ unit: 'hour' }); //=> 24
```

単体で使われることはあまりありませんが、時刻の足し算や引き算で使用されます。


##### `Temporal.TimeZone` / `Temporal.Calendar`

タイムゾーン情報やカレンダー情報を扱いたい時に使用します。
通常は使用することは無いでしょう。

# まとめ

- Temporal APIには用途別に11個のクラスが存在する
- 日付や時刻を表すには主に3種類のクラスを使い分ける
  - `Temporal.Instant`：Unix Timeのような、単なる日時を表す
  - `Temporal.PlainXXX`系：カレンダー上の日付を表す。1週間後の日付や1か月前の日付を求めたり、カレンダーを作成するなど曜日情報が欲しいときに使う
  - `Temporal.ZonedDateTime`：タイムゾーン情報が必要な時に使う
- その他の補助的なクラス
  - `Temporal.Now`：現在時刻を取得するのに使う
  - `Temporal.Duration`：時間を表す
  - `Temporal.TimeZone` / `Temporal.Calendar`：タイムゾーン情報やカレンダー情報

以上、Temporal APIではどのようなクラスが使用できるのかの紹介でした。

[次の記事](https://qiita.com/access3151fq/items/c73563351788b63e0782)では実際にTemporalオブジェクトを操作して日付処理を行ってみます。

https://qiita.com/access3151fq/items/c73563351788b63e0782

また、[Temporalオブジェクトと文字列の間の変換方法](https://qiita.com/access3151fq/items/ec27a14271c99a379af0)についても紹介しています。

https://qiita.com/access3151fq/items/ec27a14271c99a379af0