JavaScriptの日時処理はこう変わる！　Temporal入門

日時の処理は様々なアプリケーションにおいて避けては通れないタスクです。JavaScriptにおいてもそれは例外ではありません。

JavaScriptでは最初期から`Date`オブジェクトが日時を表すオブジェクトとして存在していましたが、これは非常に使いにくいAPIで知られています。その結果、[moment](https://www.npmjs.com/package/moment)に代表されるような日時処理ライブラリを使うのが事実上スタンダードとなっています。

この記事では、将来的に日時処理の有力な選択肢になると期待されるモジュールである**Temporal**について解説します。Temporalでは、既存の`Date`による日時処理のつらい部分が解消されることが期待されています。

なお、例によってTemporalはまだ策定中の仕様です。現在Stage 2というフェーズにあり、APIを鋭意策定中という状況です。よって、この記事にかかれている内容は確定までにまだ変化するかもしれません。この記事でTemporalの雰囲気を理解し、実際に使う際はご自分での情報収集が必要です。覚えていればこの記事もちゃんとアップデートしますが。

## Temporalの特徴

**Temporal**について詳説する前に、Temporalの特徴的な部分をまとめておきます。具体的なひとつひとつのAPIがまだ今後変わりそうなことを考えれば、この記事で一番重要なのはここです。特に、Temporalについてどのようなデザイン上の決定が為されているのかを知るのはTemporal、ひいては今後のJavaScriptの展望を理解するのに役立つでしょう。

### 1. 標準ライブラリに含まれている

Temporalは**標準ライブラリ**に含まれるモジュールです。標準ライブラリについては筆者の別記事で詳しく解説しているのでぜひチェックしてください。

- [今からでも追いつける！　JavaScriptの「標準ライブラリ」を学ぶ](https://qiita.com/uhyo/items/acd645744fa61c805097)

外部ライブラリを使うという選択肢はTemporalの登場後も消えることは無いでしょうが、それらと比べたTemporalの利点はTemporalが標準ライブラリに含まれていることです。特にブラウザ環境では、Temporalで事足りるならば外部ライブラリが不要になり、JavaScriptのダウンロードサイズを削減することができます。

標準ライブラリの概念の登場によってこれまで最低限だったJavaScriptの機能を拡充しようという動きがあり、Temporalもその一環だといえます。

### 2. オブジェクトがイミュータブル

Temporalが提供するオブジェクトは**イミュータブル**です。つまり、オブジェクトを作成した後でそれが表す日時情報を書き換えることはできません。別の日時情報を表したい場合は別のオブジェクトを作ることになります。

これと対称的に、既存の`Date`オブジェクトはミュータブルでした。つまり、`setFullYear`や`setHours`などのメソッドを使って`Date`オブジェクトの日時情報を書き換えることができたのです。

なぜミュータブルなオブジェクトよりもイミュータブルなオブジェクトのほうがよいのかは今さら言うまでもありません。特に今回扱うのは日時情報という「データ」の側面が強いものを表すオブジェクトです。例えば日時データを他の関数に渡すような場合に、その関数の中でオブジェクトが書き換えられてしまわないかどうかをわざわざ心配するのは無駄ですね。

```js
// 日付データを引数で受け取る
function foo(date) {
  // 別の関数に渡す
  bar(date);
  // dateはイミュータブルなのでbarによって書き換えられている心配はない
  console.log(date);
}
```

そもそも単なる日時データとしても`Date`が使いにくかったことがTemporalが登場する動機のひとつとなっています。

### 3. 意味に応じて異なるオブジェクトを使用

既存の`Date`オブジェクトの最大の問題点はタイムゾーンの扱いがとても適当であるという点です。`Date`オブジェクトは自分のタイムゾーンはどこかという情報を持っています[^Date_timezone]。明示していない場合タイムゾーンは環境依存です。また、（特にタイムゾーンが1つしかない日本では）タイムゾーンが特に必要でない日時処理もあり、そのような場合でも`Date`の仕様上タイムゾーンを意識しなければならないのはバグの元だし残念です。

[^Date_timezone]: 厳密には`Date`は**オフセット**（世界標準時からの時差を数値で表したもの）でタイムゾーンを表しています。Temporalはタイムゾーンをそのまま扱うことができる点で進化しています。

そこで、Temporalではタイムゾーンの情報を持つデータとタイムゾーンの情報を持たないデータを別々のオブジェクトで表現します。タイムゾーンの情報を持たない日時情報は**`CivilDateTime`**、タイムゾーンの情報を持つ日時情報は**`ZonedDateTime`**というオブジェクトで表現します。また、詳しくは後述しますが他にもいくつかの種類のオブジェクトが利用可能です。

### 4. ナノ秒単位のデータが扱える

読んで字のごとしです。既存の`Date`オブジェクトの時刻データはミリ秒単位の精度ですが、Temporalでは精度が6桁増えてナノ秒単位になりました。

## Temporalの使い方

では、いよいよTemporalの使い方を解説していきます。

### Temporalを読み込む
Temporalは標準ライブラリに加えられる見込みですので、`import`文を用いて読み込むことになります。例えば`CivilDateTime`というオブジェクトを使用したいときは次のようにします。

```js
import { CivilDateTime } from "std:temporal";
```

`"std:temporal"`の部分は未確定です。`"js:temporal"`とかになるかもしれません。

もちろんこれはまだブラウザ等には実装されていません。[公式のPolyfill](https://github.com/std-proposal/temporal)が存在しますが、仕様が変更されるにつれてPolyfillにも変更が加えられることになっていますので安定したものではありません[^Temporal_Polyfill_bug]。また、現状ではこのPolyfillも少し古い情報（2019年3月以前の仕様）に基づいており、最新の情報を反映していません。以降のサンプルでは可能なものはこのPolyfillで動作を確認しています。

[^Temporal_Polyfill_bug]: このPolyfillは仕様の策定を担当している方が片手間に作った感じのもので、めちゃくちゃしっかり作られているわけではありません。実際、この記事を書いている間に筆者は1個バグを見つけました。

### オブジェクトの種類

Temporalが提供するオブジェクトは現在のところ以下の種類があります。

- Civil系： `CivilDateTime`, `CivilTime`, `CivilDate`, `CivilYearMonth`, `CivilMonthDay`
- 絶対時間系[^absolute_time]： `Instant`, `OffsetDateTime`, `ZonedDateTime`

[^absolute_time]: ここでは「全世界で共通の時間軸」や「その上の時刻」という程度の意味で絶対時間と言っています。この言葉で検索すると哲学とか物理学の話題が出てきて怖いのですが、あまりその方面から突っ込みを入れてくださらないようにお願いします。

#### Civil系オブジェクト

Civil系というのはタイムゾーン情報を持たないデータで、持っている情報によってオブジェクトの種類が細分化されています。例えば`CivilDate`というのは日付の情報、すなわち年・月・日を持つオブジェクトです、`CivilTime`は時計、すなわち時間・分・秒及びそれ以下です。`CivilDateTime`は日付と時計の両方の情報を持っています。また、`CivilYearMonth`というのは年と月のみで日及び時計の情報を持たないデータです。`CivilMonthDay`も同様に月と日のみで年の情報を持ちません。

重要なのは、これらの**Civil系オブジェクトは絶対的な（全世界的に一意の）ひとつの時刻を指し示すものではない**ということです。Civil系オブジェクトは例えば「2019年5月1日12時0分0秒」のように**暦**（こよみ）上の情報、すなわちカレンダーや時計の表示を表すものですが、このデータだけでは全世界的に一意の時刻を表すことはできません。例えば、日本とイギリスではこのデータが表す絶対時刻は9時間[^Britain_time]ずれています。

[^Britain_time]: イギリスが夏時間の場合は8時間ですが。

要するに、Civil系オブジェクトは**タイムゾーン**あるいは**オフセット**の情報を持たないデータであるということです。これらのオブジェクトはタイムゾーンと関係ないローカルなデータや、カレンダーの処理をしたい場合に適しています。

#### 絶対時間系オブジェクト
Civil系以外の残りの3つのオブジェクトは絶対時間を表すことができます。`Instant`オブジェクトはその中でも「生データ」に近いオブジェクトであり、時刻を1970年1月1日0時0分0秒（世界標準時）からの経過時間（ナノ秒単位）で保持しています[^unixtime]。

[^unixtime]: なお、この情報は閏秒を考慮していないため、その分だけ実際の経過時間（物理的に1970年1月1日0時0分0秒から経過した時間）とはずれがあります。コンピュータにおける時刻表現は基本的に閏秒を考えていないのでTemporalが特別に劣っているわけではありませんが。閏秒を厳密に処理する必要がある場合は閏秒のデータを用意して自分で処理する（あるいはそういう処理をやってくれるライブラリを使用する）必要があります。


このように1970年1月1日からの経過時間で絶対時刻を表す方法はコンピュータにおける時刻表現として広く使われており、UNIX時間として知られています。`Instant`はこのUNIX時間を表すオブジェクトであるといえます。

さて、`Instant`により絶対時刻を表すことができますが、これは不便です。なぜなら、**オフセット**の情報が無いと実際にはこれが何月何日の何時何分なのか分からないからです。これも先ほどと同じ話で、例えば「1970年1月1日0時0分0秒（世界標準時）から100秒後」という時刻はイギリスでは1970年1月1日の0時1分40秒ですが、日本では1970年1月1日の9時1分40秒を表しており、ひとつに定まりません。

**オフセット**は、世界標準時からどれだけ時差があるかということを表す分単位の情報です。例えば日本は世界標準時よりも9時間進んでいるのでオフセットは`+09:00`です。イギリスは世界標準時と同じなので`+00:00`となります。

絶対時刻とオフセットの2つの情報を組み合わせることで日時（カレンダー・時計の値）をひとつに定めることができます。例えば「1970年1月1日0時0分0秒（世界標準時）から100秒後」という時刻は`+09:00`というオフセットにおいては「1970年1月1日9時1分40秒」という日時を表すのです。

逆に言えば、日時とオフセットがあれば絶対時刻を特定することができます。つまり、「オフセット`+09:00`における1970年1月1日9時1分40秒」というデータは「1970年1月1日0時0分0秒（世界標準時）から100秒後」という絶対時刻を一意に表しています。

前置きが長くなりましたが、このような「日時とオフセット」の情報を持つオブジェクトが`OffsetDateTime`です。

最後の`ZonedDateTime`は`OffsetDateTime`と同様に日時の情報を持つオブジェクトですが、**オフセット**の代わりに**タイムゾーン**を持っています。

タイムゾーンは**地域名**により表現されるデータであり、例えば日本のタイムゾーンは`Asia/Tokyo`です。そして、`Asia/Tokyo`のオフセットは`+09:00`であるというデータが[tz database](https://ja.wikipedia.org/wiki/Tz_database)に保存されているためタイムゾーンからはオフセットを得ることができます。これにより、`ZonedDateTime`もやはり絶対時刻を表すことができます。

`OffsetDateTime`と`ZonedDateTime`の大きな違いは、後者は**タイムゾーン内でのオフセットの変化**に対応可能だということです。日本ではあまり馴染みがありませんが、典型的には夏時間の影響により、時季によって同じタイムゾーン内でもオフセットが変わる可能性があるのです。これにより、`ZonedDateTime`に対して日時の操作を行う場合、同じタイムゾーンでもオフセットが勝手に変動することがあります。一方の`OffsetDateTime`は常に固定のオフセットを持ちます。一般に、タイムゾーンに寄り添った日時処理を行いたい場合は`ZonedDateTime`が便利ですね。

まとめると、Civil系オブジェクトは暦により表された情報のみを持ち**オフセット**や**タイムゾーン**の情報を持たないオブジェクトで、絶対時刻を表すものではありません。`Instant`, `OffsetDateTime`, `ZonedDateTime`はいずれも絶対時刻を表すもので、さらに`OffsetDateTime`は**オフセット**の情報を、`ZonedDateTime`はタイムゾーンの情報を持っています。

なお、Temporalが採用している暦は[先発グレゴリオ暦](https://ja.wikipedia.org/wiki/%E5%85%88%E7%99%BA%E3%82%B0%E3%83%AC%E3%82%B4%E3%83%AA%E3%82%AA%E6%9A%A6)であり、これは我々が慣れ親しんでいる暦（グレゴリオ暦）をグレゴリオ暦が実際に使われるよりも以前にも適用するようにしたものです。この暦は古い日付も統一的に扱えて簡単であるという利点からプログラミング言語での採用例があり、日時の表記の国際規格である**ISO 8601**にもこれが採用されています。Temporalは基本的にISO 8601に準拠して作られています。

では、ここからは各オブジェクトを見ていきます。とはいえ、TemporalのAPIはまだ変化の途上にあり、何ヶ月もすればAPIが別物になっているということも普通にあります。ですから以下の話はおまけ程度に考えてください。どちらかといえばここまで説明したコンセプトのほうが重要です。

### `CivilDateTime`

つい先程述べたように、`CivilDateTime`は**暦**の上での日時情報を表すオブジェクトです。具体的には、年・月・日・時間・分・秒・そして秒以下の数値で日時を表します。`CivilDateTime`オブジェクトを作る方法の1つは`CivilDateTime`コンストラクタを使うものです。例えば、過ぎし2019年5月1日の0時0分0秒を表す`CivilDateTime`は次のように作ります。

```js
// 年, 月, 日, 時, 分の順でコンストラクタに数値を渡す
const startOfReiwa = new CivilDateTime(2019, 5, 1, 0, 0);
```

まず最初に気づいていただきたいのは、**月は1始まり**ということです。古い`Date`オブジェクトでは月は0〜11の整数で表され、0が1月、1が2月、……11が12月というとても間違えやすい仕様になっていたのですが、`Temporal`では1〜12の数値になっています。

上の例ではコンストラクタの引数は5個ですが、**最大9個まで数値を指定可能**です。意味は順に年、月、日、時、分、秒、ミリ秒、マイクロ秒、ナノ秒です。ミリ秒以下は0から999の数値で指定します。例えば、2019年5月1日の2時34分56.789012345秒という時刻を表したい場合は次のようにします。

```js
const time = new CivilDateTime(2019, 5, 1, 2, 34, 56, 789, 12, 345);
```

`CivilDateTime`コンストラクタの引数は最低5個必要です。つまり、秒以下は省略可能ですが分以上は省略できません。省略したところは当然0になります。

#### `CivilDateTime`のプロパティ

こうして作られた`CivilDateTime`オブジェクトからはいくつかのプロパティを通して情報を取得可能です。基本的なプロパティは`year`、`month`、`date`、`hour`、`minute`、`second`、`millisecond`、`microsecond`、`nanosecond`で、上記のコンストラクタの引数にちょうど対応した情報を持っています。

```js
console.log(time.year); // 2019
console.log(time.day);  // 1
console.log(time.millisecond); // 789
```

`Date`では`getFullYear()`などとする必要があったのに比べると簡潔で嬉しいですね。

また、いくつか追加の情報を取得できるプロパティがあります。`dayOfWeek`はその日付の曜日を表す数値であり、1（月曜日）〜7（日曜日）の整数で表されます。`dayOfYear`はその日がその年の1月1日から数えて何日目かを表す数値です（1月1日は`1`日目です）。そして`weekOfYear`は週番号、すなわちその日を含む週がその年の何週目かを表す数値です。

```js
console.log(time.dayOfWeek);  // 3 （2019年5月1日は水曜日）
console.log(time.dayOfYear);  // 121
console.log(time.weekOfYear); // 18
```

なお、週番号についてはISO 8601で規定されているものであり、「年の最初の木曜日を含む週がその年の第1週である」とされています。その結果、次の例のように年始の日付が前の年の最終週に属するという判定になることがあります。

```js
const wah = new CivilDateTime(2010, 1, 3, 0, 0);
console.log(wah.weekOfYear); // 52
```

2010年最初の木曜日は1月7日ですから、1月7日を含む週が2010年の第1週です。`dayOfWeek`で月曜日が1であることからも分かるように週は月曜日〜日曜日が1つの週という数え方になりますから、2010年の第1週は1月4日（月）〜1月10日（日）ということになります。よって、それより前の1月3日は週番号上は2009年の最終週に属することになるのです。

#### 文字列への変換

日時データというのは頻繁にコンピュータ間でやり取りされます。そのためには日時データを表現する共通の方法が必要であり、それはやはりISO 8601によって規定されています。`CivilDateTime`は`toString()`によってISO 8601に適合する文字列に変換可能です。`toString()`は文字列への暗黙の変換の際も呼ばれます。

```js
const startOfReiwa = new CivilDateTime(2019, 5, 1, 0, 0);

console.log(startOfReiwa.toString());        // "2019-05-01T00:00:00.000000000"
```

ちなみに、ISO 8601では秒未満（`.`以降の部分はオプショナルで桁数の規定はありませんが、Temporalでは9桁、すなわちナノ秒の精度で文字列化することが規定されています。

なお、日時データを機械ではなく人間が読みやすい形で表示できる機能というのも需要がありますが、[それは現在議論中でまだ固まっていないようです](https://github.com/tc39/proposal-temporal/issues/129)。

#### 文字列からの変換

上で紹介したメソッドたちとは逆に、文字列から`CivilDateTime`オブジェクトを得るための方法も用意されています。それが`CivilDateTime.fromString()`です。

```js:CivilDateTime.fromStringの例
console.log(CivilDateTime.fromString("2019-05-01T00:00:00.000000000"));
```

なお、ISO 8601文字列はもう少しバリエーションがありますが、`CivilDateTime.fromString`はそれらの文字列も受け付けて**くれそうな雰囲気**があります。雰囲気というのはどういうことかというと、正確に何が受け付けられて何が受け付けられないのかは仕様策定者たちの頭の中にしかない（あるいは決まっていないかもしれない）ということです。

#### `plus`: 時刻の加算

`CivilDateTime`オブジェクトは`plus`メソッドを持ち、ある時刻に対して加算を行うことができます。`CivilDateTime`オブジェクトはイミュータブルですから、結果は新しい`CivilDateTime`オブジェクトとして得られます。さっそく例を見ましょう。

```js
const startOfReiwa = new CivilDateTime(2019, 5, 1, 0, 0);

// 12時間足して正午にする
const noon = startOfReiwa.plus({ hours: 12 });

console.log(noon.toString()); // "2019-05-01T12:00:00.000000000"

// 1年戻して5000秒進める
const time2 = noon.plus({ years: -1, seconds: 5000 });

console.log(time2.toString()); // "2018-05-01T13:23:20.000000000"
```

このように、`plus`にはオブジェクトを渡します。オブジェクトは`years`, `months`, `days`, `hours`, `minutes`, `milliseconds`, `microseconds`, `nanoseconds`を持つことができます。プロパティ名が複数形である点に注意してください。2番目の例のように、負の数を渡すことで時間を戻すこともできます。

#### `with`: 部分的な上書き

`with`メソッドは、既存の`CivilDateTime`オブジェクトの情報を部分的に書き換えて得られる新しい`CivilDateTime`オブジェクトを返します。

```js
const startOfReiwa = new CivilDateTime(2019, 5, 1, 0, 0);

// 年を2100年に、日を31日に変更する
const maybeNotReiwa = startOfReiwa.with({ year: 2100, day: 31 });
console.log(maybeNotReiwa.toString()); // "2100-05-31T00:00:00.000000000"
```

渡すのはやはりオブジェクトであり、`CivilDateTime`と同名のプロパティたちを用いて上書き箇所を指定します。

### `minus`: 2つの日時の差を得る

2つの`CivilDateTime`の間の期間を得ることができる`minus`メソッドも用意されています。使い方は**多分こんな感じ**です。

```js
const startOfReiwa = new CivilDateTime(2019, 5, 1, 0, 0);
const startOfHeisei = new CivilDateTime(1989, 1, 8, 0, 0);

const diff = startOfReiwa.minus(startOfHeisei);
console.log(diff);
/*
{
  years: 31,
  months: 3,
  days: 23,
  hours: 0,
  minutes: 0,
  seconds: 0,
  (以下略)
}
*/
```

多分こんな感じというのは、メソッドの存在だけとりあえず決まって**いそうな感じ**がして具体的な計算方法はまだ決まって**いなそうな感じ**がしていることを意味しています。

### `CivilDate`

他のCivil系のオブジェクトは基本的に`CivilDateTime`より情報が少ないバージョンです。`CivilDate`は年・月・日の情報だけを持ち、コンストラクタの引数は年・月・日の3つです。

```js
const startOfReiwa = new CivilDate(2019, 5, 1);
```

プロパティとしては`year`, `month`, `day`を持ちます。また、`dayOfWeek`, `dayOfYear`, `weekOfYear`も日付だけあれば計算可能なので持っています。持っているメソッドたちも同様で、`with`, `plus`, `minus`があります。

注意すべき点は、`plus`では渡されたオブジェクトの`years`, `months`, `days`のみ見られるということです。次のようにしても日付が1日進んだりはしません。

```js
// hoursは無視されるのでこれはstartOfReiwaと同じ
const tomorrow = startOfReiwa.plus({ hours: 24 });
```

また、`CivilDate`は日付の情報のみを持っているオブジェクトでしたが、次に紹介する`CivilTime`（一日の中の時刻情報だけを持つ）と組み合わせることで`CivilDateTime`を作ることができます。そのためには`withTime`メソッドを用います。

```js
const startOfReiwa = new CivilDate(2019, 5, 1);
const clock = new CivilTime(12, 34, 56);

const dateTime = startOfReiwa.withTime(clock);

console.log(dateTime.toString()); // "2019-05-01T12:34:56.000000000"
```

#### 文字列への変換

`CivilDate`で特筆すべき点は文字列への変換メソッドを3種類持っている点です。実はISO 8601では日付の表現方法が3種類規定されています。1つは`2019-05-01`のように年-月-日で表現する方法、2つ目は`2019-W18-03`のように年-週番号-曜日で表現する方法、そして最後は`2019-121`のように年とその年の1月1日からの日数で表す方法です。

この3種類の変換はそれぞれ`toDateString()`, `toWeekDateString()`, `toOrdinalDateString()`で可能です。`toString()`は`toDateString()`と同じになります。

`fromString()`は3種類の表現の全てを受け付けてくれます。

### `CivilTime`

`CivilTime`のコンストラクタは2〜6引数です。最初の2つ、すなわち時と分は省略不可能で、それ以下（秒、ミリ秒、マイクロ秒、ナノ秒）は省略可能です。

また、`withDate`に`CivilDate`を渡すことで`CivilDateTime`に変換することができます。

### `CivilYearMonth`

`CivilYearMonth`は年と月だけの情報を持ちます。コンストラクタの引数もその2つです。`withDay()`メソッドで`CivilDate`に変換できます。

### `CivilMonthDay`

`CivilMonthDay`は月と日だけの情報を持ちます。`withYear()`メソッドで`CivilDate`に変換できます。

なんだか後半駆け足でしたが、以上でCivil系の説明は終わりです。

### `Instant`

`Instant`は前述の通り、UNIXエポック（1970年1月1日0時0分0秒（世界標準時））からの経過時間（ナノ秒）によって絶対時刻を表すデータです。よって、`Instant`コンストラクタにはUNIXエポックからの経過時間を渡します。例えば、UNIXエポックから1556636400000000000ナノ秒後の絶対時刻を表す`Instance`オブジェクトを作りたい場合は次のようにします。

```js
const inst = new Instant(1556636400000000000n);
```

なお、`1556636400000000000n`というのはBigIntのリテラルです。普通の数値型ではナノ秒単位の秒数を扱うには精度が小さいのでBigIntが使われています。数値型の精度とかBigIntの話は筆者の別記事がありますのでよろしければそちらもご覧ください。

- [JavaScriptの数値型完全理解](https://qiita.com/uhyo/items/f9abb94bcc0374d7ed23)

`Instant`オブジェクトからは、UNIXエポックからの経過時間を秒・ミリ秒・マイクロ秒・ナノ秒単位で得ることができます。

```js
console.log(inst.epochSeconds);      // 1556636400
console.log(inst.epochMilliseconds); // 1556636400000
console.log(inst.epochMicroseconds); // 1556636400000000n
console.log(inst.epochNanoseconds);  // 1556636400000000000n
```

マイクロ秒とナノ秒はBigIntになっています。ミリ秒くらいなら普通の数値で行けるだろという判断のようです。ちなみに、ミリ秒をBigIntではなく普通の数値で表した場合、西暦約29万年問題の発生が懸念されます。今からとても心配ですね。

また、`Instant`にも`toString`と`fromString`がありますが、何に変換されるのかは**よく分かりません**。多分`Z`タイムゾーンのISO 8601表現とかだと思いますが。

`Instant`にはオフセットやタイムゾーンの情報がないため具体的な日付や時刻の数値が分からないのでした。そのため、上記のように得られる情報は人間には分かりにくいものとなっています。

そこで、`withOffset`や`withZone`メソッドでこれらの情報を付加することで`OffsetDateTime`や`ZonedDateTime`に変換することができます。`withOffset`には`"+09:00"`のようなオフセットを表す文字列を、`withZone`には`"Asia/Tokyo"`のようなタイムゾーンを表す文字列を渡します。

### `OffsetDateTime`

`OffsetDateTime`はオフセット情報のついた時刻データです。API的には`CivilDateTime`プラスアルファだと思えば間違いありません。

作り方は`Instant`の`withOffset`を使う方法や`new OffsetDateTime(instant, offset)`のようにする方法があります。どちらにせよ`Instant`のインスタンスが必要ですね。`CivilDateTime`にも`withOffset`がある**気がする**のですがはっきりとは分かりませんでした。また、`OffsetDateTime.fromString`で文字列から作る方法もあります。

`OffsetDateTime`は自分のタイムゾーンの元で自分が何年何月何日の何時何分かということを知っていますから、`CivilDateTime`の機能は全て利用可能です。`year`, `month`などのプロパティで数値を取得したり`plus`, `with`, `minus`メソッドを使用できます。

加えて、`instant`プロパティで自分の絶対時刻を表す`Instant`オブジェクトを取得可能です。また、`offset`プロパティはオフセットを表す文字列です。`toString`に関してはISO 8601に従ってオフセット情報が付加された文字列を返します。


```js
const inst = new Instant(1556636400000000000n);
const datetime = new OffsetDateTime(inst, "+09:00");
console.log(datetime.year);  // 2019
console.log(datetime.month); // 5
console.log(datetime.day);   // 1
console.log(datetime.offset);// "+09:00"

console.log(datetime.toString()); // "2019-05-01T00:00:00.000000000+09:00"
```

なお、なぜか`OffsetDateTime.fromZonedDateTime`が用意されており`ZonedDateTime`から`OffsetDateTime`への変換ができます。

### `ZonedDateTime`

`ZonedDateTime`はタイムゾーンの情報を持ったオブジェクトで、`OffsetDateTime`のさらに上位互換です。作り方は先ほど説明した`Instant`の`withZone`か、`new ZonedDateTime(instant, zone)`です（あと`ZonedDateTime.fromString`）。

`OffsetDateTime`の機能は全て使える上に、`timeZone`プロパティでタイムゾーンを取得できます。また、`toString()`の結果は下のようにタイムゾーンの情報が付加されます。

```js
const inst = new Instant(1556636400000000000n);
const datetime = new ZonedDateTime(inst, "Asia/Tokyo");
console.log(datetime.year);  // 2019
console.log(datetime.month); // 5
console.log(datetime.day);   // 1
console.log(datetime.offset);// "+09:00"
console.log(datetime.timeZone); // "Asia/Tokyo"

console.log(datetime.toString()); // "2019-05-01T00:00:00.000000000+09:00[Asia/Tokyo]"
```

`[Asia/Tokyo]`のように角括弧で囲まれたタイムゾーン名があるのが特徴的です。また、オフセットの情報もついています。

## 補足：現在時刻の取得について

古い`Date`は引数なしで`Date`コンストラクタを呼ぶと初期値が現在時刻になったり、`Date.now()`でミリ秒の形で現在時刻を取得できたりします。Temporalでは同様の方針は採用されませんでした。一時期は「JavaScriptの言語仕様自体に外界の情報を得る手段をこれ以上増やすべきではない」との観点から仕様としてはそのような手段を用意しないべきではという議論もされましたが、さすがに何かあったほうがいいだろうという方向に話が進んでいるように見えます（[参考](https://github.com/tc39/proposal-temporal/issues/120)）。一案として以下のようなAPIが出ています。

```js
import { now, timeZone } from "std:temporal/now";

now(); // 現在時刻を表すInstantが返る
timeZone(); // 現在地のタイムゾーンを表す文字列が返る

// この2つの情報から現在時刻を表すZonedDateTimeが作れる
const currentTime = new ZonedDateTime(now(), timeZone();
```

## まとめ

というわけで、現在使用策定中のTemporalについて紹介しました。旧来の`Date`に比べると様々な点で改良されています。日付の計算は`plus`と`minus`くらいしかありませんが、結構それで事足りることもあるでしょう。また、記事中でちらっと触れた通り、人間向けのフォーマッティングについても別個に議論されるようです（それ系は`Intl`という別の仕様の範疇となる可能性が高いです）。

後半のAPIの話をよく読んだ方は**`CivilDateTime`から`ZonedDateTime`にどうやって変換すんのこれ**などの鋭い疑問を持ったかもしれません。自分も持ちましたが、まだAPIが整理されていないということで勘弁してあげましょう。実はこの記事がベースとしているのは[つい2週間ほど前にドラフトが書き上がったばかりの文書](https://github.com/tc39/proposal-temporal/pull/130)で、実際自分も読んでいる途中にいくつもおかしな箇所を見つけました。仕様化が終わるまでには直っているでしょう。

それよりも、おおよそのTemporalの方向性をこの記事で理解していただけたら幸いです。Civil系、`Instant`、`OffsetDate`、そして`ZonedDateTime`というようにオブジェクトを細分化し、今どの情報を持っていてどの情報を持っていないのかということを明示できるようにしたのは`Date`からの大きな進歩です。

個人的には、Temporalが実際に利用可能になるには速く進んでもあと2〜3年はかかりそうだと考えています。<del>いざTemporalがブラウザに搭載されたときに解説記事を書こうとした人がn年前に書かれたこの記事を見て悔しがるのが今から楽しみです。</del>


## リンク
- [Temporal Proposal](https://github.com/tc39/proposal-temporal) - プロポーザルの文書です。
- [What about Temporal in JavaScript](https://speakerdeck.com/arayaryoma/what-about-temporal-in-javascript) - Temporalを紹介する恐らく唯一の既存日本語資料です。2019年2月のスライドなのでAPIが少し古いです（`ZonedTimeZOne`ではなく`ZonedInstant`になっていたり`OffsetDateTime`が存在しないなど）。
- [Fixing JavaScript Date - Getting Started](https://maggiepint.com/2017/04/09/fixing-javascript-date-getting-started/) - Temporalを作るモチベーションを解説する文書です。