145
117

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-05-06

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

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

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

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

Temporalの特徴

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Temporalの使い方

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

Temporalを読み込む

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

import { CivilDateTime } from "std:temporal";

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

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

オブジェクトの種類

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

  • Civil系: CivilDateTime, CivilTime, CivilDate, CivilYearMonth, CivilMonthDay
  • 絶対時間系3Instant, OffsetDateTime, ZonedDateTime

Civil系オブジェクト

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

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

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

絶対時間系オブジェクト

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

このように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です。

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

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

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

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

なお、Temporalが採用している暦は先発グレゴリオ暦であり、これは我々が慣れ親しんでいる暦(グレゴリオ暦)をグレゴリオ暦が実際に使われるよりも以前にも適用するようにしたものです。この暦は古い日付も統一的に扱えて簡単であるという利点からプログラミング言語での採用例があり、日時の表記の国際規格であるISO 8601にもこれが採用されています。Temporalは基本的にISO 8601に準拠して作られています。

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

CivilDateTime

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

// 年, 月, 日, 時, 分の順でコンストラクタに数値を渡す
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秒という時刻を表したい場合は次のようにします。

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

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

CivilDateTimeのプロパティ

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

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は週番号、すなわちその日を含む週がその年の何週目かを表す数値です。

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

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

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によって規定されています。CivilDateTimetoString()によってISO 8601に適合する文字列に変換可能です。toString()は文字列への暗黙の変換の際も呼ばれます。

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

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

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

なお、日時データを機械ではなく人間が読みやすい形で表示できる機能というのも需要がありますが、それは現在議論中でまだ固まっていないようです

文字列からの変換

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

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

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

plus: 時刻の加算

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

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オブジェクトを返します。

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メソッドも用意されています。使い方は多分こんな感じです。

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つです。

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

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

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

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

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

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つ、すなわち時と分は省略不可能で、それ以下(秒、ミリ秒、マイクロ秒、ナノ秒)は省略可能です。

また、withDateCivilDateを渡すことで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オブジェクトを作りたい場合は次のようにします。

const inst = new Instant(1556636400000000000n);

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

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

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

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

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

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

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

OffsetDateTime

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

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

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

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

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のさらに上位互換です。作り方は先ほど説明したInstantwithZoneか、new ZonedDateTime(instant, zone)です(あとZonedDateTime.fromString)。

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

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の言語仕様自体に外界の情報を得る手段をこれ以上増やすべきではない」との観点から仕様としてはそのような手段を用意しないべきではという議論もされましたが、さすがに何かあったほうがいいだろうという方向に話が進んでいるように見えます(参考)。一案として以下のようなAPIが出ています。

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

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

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

まとめ

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

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

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

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

リンク

  1. 厳密にはDateオフセット(世界標準時からの時差を数値で表したもの)でタイムゾーンを表しています。Temporalはタイムゾーンをそのまま扱うことができる点で進化しています。

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

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

  4. イギリスが夏時間の場合は8時間ですが。

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

145
117
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
145
117

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?