Temporal
Limited availability
This feature is not Baseline because it does not work in some of the most widely-used browsers.
Experimental: これは実験的な機能です。
本番で使用する前にブラウザー互換性一覧表をチェックしてください。
Temporal オブジェクトは、組み込みのタイムゾーンおよび暦の表現、実時刻の変換、算術演算、書式化など、さまざまなシナリオで日付と時刻の管理を可能にします。これは、 Date オブジェクトを完全に置き換えるものとして設計されています。
解説
ほとんどのグローバルオブジェクトとは異なり、 Temporal はコンストラクターではありません。 new 演算子と一緒に使用したり、 Temporal オブジェクトを関数として呼び出したりすることはできません。 Temporal のすべてのプロパティとメソッドは静的です(Math オブジェクトと同様)。
Temporal は複雑で強力な API を備えています。複数のクラスを通じて 200 以上のユーティリティメソッドを公開しているため、とても複雑に見えるかもしれません。これらの API が互いにどのように関連しているかを、概要で説明します。
背景と概念
JavaScript には、その誕生当初から日付と時刻を処理するための Date オブジェクトが存在していました。しかし、Date API は、設計が良くなく 2010 年代初頭に置き換えられた Java の java.util.Date クラスをベースにしています。ただ、JavaScript は下位互換性を重視しているため、Date はこの言語に残っています。
この紹介の冒頭で重要なことは、日付の処理は複雑であるということです。Date の問題のほとんどは、メソッドを追加することで解決できますが、根本的な設計上の欠陥は残っています。つまり、同じオブジェクトにあまりにも多くのメソッドが公開されているため、開発者はどれを使用すべきか迷うことが多く、予期せぬ問題が発生しやすいのです。よく設計された API は、より多くのことを行う必要があるだけでなく、各抽象化レベルで行うことを少なくする必要があります。なぜなら、誤用を防ぐことは、用途を拡大することと同じくらい重要だからです。
Date オブジェクトは 2 つの役割を同時に果たします。
- タイムスタンプとして。特定の時点(元期)から経過したミリ秒またはナノ秒の数値。
- 成分の組み合わせとして。月、日、時、分、秒、ミリ秒、ナノ秒。年、月、日の識別子は、暦システムを基準にしてのみ意味を持ちます。この組み合わせ全体は、タイムゾーンに関連付けられている場合、歴史上の特定の瞬間に割り当てられます。
Dateオブジェクトは、これらの成分を読み取り、変更するためのメソッドを提供しています。
タイムゾーンは、日付関連のバグの多くの根底にあります。「成分の組み合わせ」モデルで Date を操作する場合、時刻は UTC とローカル (端末) の 2 つのタイムゾーンしか指定できず、任意のタイムゾーンを指定する方法がありません。また、「タイムゾーンがない」という概念も持っていません。これは、カレンダーの日付 (日付の場合) または壁掛け時計の時刻 (時刻の場合) として知られており、「カレンダーや時計で読み取る」時刻です。例えば、毎日の目覚ましアラームを設定する場合、夏時間であるかどうか、別のタイムゾーンに移動しているかどうかに関係なく、「午前 8 時」に設定したいでしょう。
Date に欠けている 2 つ目の機能は、暦システムです。ほとんどの人は、西暦と紀元前という 2 つの時代があり、12 か月があり、各月の日数は異なり、4 年に 1 回は閏年がある、といったグレゴリオ暦に慣れているでしょう。しかし、ヘブライ暦、中国の旧暦、日本の旧暦など、別の暦システムを使用している場合、これらの概念の一部は適用されない場合があります。Date では、グレゴリオ暦のモデルしか使用できません。
Date には、すべてのセッターが変更可能である (これは多くの場合、望ましくない副作用を引き起こします)、日付と時刻の文字列形式を一貫した方法で解釈できない、など、他にも多くの望ましくない古い部分があります。結局、最善の解決策は、新しい API を一から構築することであり、それが Temporal です。
API 概要
Temporal は Intl と同様に名前空間です。これには、日付および時刻の管理の特定の側面を処理するように設計された、いくつかのクラスおよび名前空間が含まれています。クラスは次のようにグループ化できます。
- 時間の表現(2 つの時点間の差):
Temporal.Duration - 時点の表現:
- 歴史上の固有の瞬間の表現:
- タイムスタンプとして:
Temporal.Instant - 日時の成分の組み合わせにタイムゾーンを併せたもの:
Temporal.ZonedDateTime
- タイムスタンプとして:
- タイムゾーンに関わらない日時の表現(すべて "Plain" の接頭辞が付く):
- 日付(年、月、日) + 時刻(時、分、秒、ミリ秒、マイクロ秒、ナノ秒):
Temporal.PlainDateTime(注:ZonedDateTimeはPlainDateTimeにタイムゾーンを加えたもの)- 日付(年、月、日):
Temporal.PlainDate - 時刻(時、分、秒、ミリ秒、マイクロ秒、ナノ秒):
Temporal.PlainTime
- 日付(年、月、日):
- 日付(年、月、日) + 時刻(時、分、秒、ミリ秒、マイクロ秒、ナノ秒):
- 歴史上の固有の瞬間の表現:
さらに、 Temporal.Now という別のユーティリティ名前空間もあり、さまざまな形式で現在の時刻を取得するメソッドを提供しています。
共有クラスインターフェイス
Temporal 名前空間には多数のクラスがいますが、これらのクラスは多くの似たメソッドを共有しています。次の表は、各クラスのすべてのメソッド(変換メソッドを除く)を一覧表示したものです。
次の表は、各クラスで利用できるプロパティをまとめたもので、各クラスが表す情報の意味を把握するのに役立ちます。
クラス間の変換
以下の表は、各クラスに存在するすべての変換メソッドをまとめたものです。
| 変換元 | ||||||||
Instant |
ZonedDateTime |
PlainDateTime |
PlainDate |
PlainTime |
PlainYearMonth |
PlainMonthDay |
||
|---|---|---|---|---|---|---|---|---|
| 変換先 | Instant | / | toInstant() | まず ZonedDateTime へ変換 | ||||
ZonedDateTime | toZonedDateTimeISO() | / | toZonedDateTime() | toZonedDateTime() | PlainDate#toZonedDateTime() (引数として渡す) | まず PlainDate へ変換 | ||
PlainDateTime | まず ZonedDateTime へ変換 | toPlainDateTime() | / | toPlainDateTime() | PlainDate#toPlainDateTime() (引数として渡す) | |||
PlainDate | toPlainDate() | toPlainDate() | / | 情報に対応するものがない | toPlainDate() | toPlainDate() | ||
PlainTime | toPlainTime() | toPlainTime() | 情報に対応するものがない | / | 情報に対応するものがない | |||
PlainYearMonth | まず PlainDate へ変換 | toPlainYearMonth() | 情報に対応するものがない | / | まず PlainDate へ変換 | |||
PlainMonthDay | toPlainMonthDay() | まず PlainDate へ変換 | / | |||||
これらの表で、 Temporal API の基本的な考え方が理解できると思います。
暦
暦は、通常、週、月、年、時代などの期間に日を整理する方法です。世界の大半はグレゴリオ暦を使用していますが、特に宗教的および文化的な文脈では、他にも多くの暦が使用されています。既定では、暦を認識するすべての Temporal オブジェクトは、グレゴリオ暦をベースとし、週の番号付けに関する追加の規則を定義する ISO 8601 暦システムを使用します。 Intl.supportedValuesOf() には、ブラウザーで対応している可能性の高い暦のほとんどが掲載されています。ここでは、暦システムがどのように構成されているかを簡単に説明し、暦同士で異なる成分を内部的に理解しやすくします。
地球上には 3 つの顕著な周期的なイベントがあります。それは、太陽の周りを回る回転(1 回転は 365.242 日)、月が地球の周りを回る回転(新月から新月まで 29.53 日)、そして自転(日の出から日の出まで 24 時間)です。すべての文化では、「1 日」は 24 時間という同じ単位で測定されます。夏時間などの一時的な変更は、暦には属しません。これらはタイムゾーンの情報の一部です。
- 暦の中には、1 年を平均 365.242 日と定義し、1 年を 365 日とし、約 4 年ごとに 1 日(閏日)を追加することで、1 年を定義するものあります。そして、1 年を月と呼ばれる部分にさらに分割することができます。このような暦は、太陽暦と呼ばれます。グレゴリオ暦と太陽ヒジュラ暦は、太陽暦です。
- 暦の中には、月を 29 日と 30 日の交互と定義することにより、1 か月を平均 29.5 日と定義するものがあります。そして、12 か月を 354 日の 1 年としてグループ化します。このような暦は「太陰暦」と呼ばれます。イスラム暦は太陰暦です。太陰年は人工的なものであり、季節の周期とは関連がないため、太陰暦は一般的にあまり使用されていません。
- 暦の中には、太陰暦と同様に、主に月の周期に基づいて月を定義するものがあります。そして、太陽年との 11 日の誤差を補うために、約 3 年ごとに 1 ヶ月分の閏月が追加されます。このような暦は、太陰太陽暦と呼ばれます。ヘブライ暦および中国暦は太陰太陽暦です。
Temporal では、1 つの暦システムにおけるすべての日付は、year、month、day の 3 つの成分によって一意に識別されます。 year は通常、正の整数ですが、0 または負の値も取り、時間とともに単調に増加します。1 の年(または、存在する場合 0)は、暦の元期として知られており、暦ごとに任意に設定されます。month は 1 から始まり、date.monthsInYear で終わり、年が改まると 1 にリセットされる、1 回ごとに 1 ずつ増加する正の整数です。day も正の整数ですが、政治的な変更によって日がスキップされたり繰り返されたりする場合があるため、1 から開始したり、毎回 1 ずつ増加したりしない場合があります。しかし、一般的には、day は単調に増加し、月が改まるとリセットされます。
year に加えて、時代を使用する暦では、era と eraYear の組み合わせによって、年を固有に識別することもできます。例えば、グレゴリオ暦では "CE" (Common Era) と "BCE" (Before Common Era) という時代が使用されており、年 -1 は { era: "bce", eraYear: 2 } と同じです(なお、年 0 はすべての暦に存在します。グレゴリオ暦では、これは天文学的紀年法のため 1 BCE に対応します)。 era は小文字の文字列で、eraYear は 0 または負の任意の整数で、時点とともに減少する場合もあります (通常は最も古い時代の場合)。
メモ:
era と eraYear は常にペアで使用してください。一方のプロパティだけを使用しないでください。また、競合を避けるため、年を指定する際に year と era/eraYear を組み合わせて使用しないでください。1 つの年表現を選択し、それを一貫して使用してください。
年に関する次の誤った仮定に注意してください。
eraおよびeraYearが常に存在すると想定しないでください。これらはundefinedである場合もあります。eraが人間にとってわかりやすい文字列であると想定しないでください。代わりに、toLocaleString()を使用して日付を書式化して使用してください。- 異なる暦の 2 つの
year値が比較可能であると想定しないでください。代わりに、静的メソッドcompare()を使用してください。 - 1 年は 365 日または 366 日、12 ヶ月であると想定しないでください。代わりに
daysInYearおよびmonthsInYearを使用してください。 - 閏年(
inLeapYearがtrue)には 1 日が追加されるとは想定しないでください。1 か月が追加される場合もあります。
month に加えて、1 年間の月は monthCode によって一意に識別することもできます。monthCode は通常、月の名前に割り当てられますが、month はそうではありません。例えば、太陰太陽暦の場合、同じ monthCode を持つ 2 つの月があり、一方は閏年に属し、もう一方は閏年に属さない場合、閏月の後に続く 2 つの月の month 値は、閏月の挿入により、異なる値になります。
メモ:
競合を避けるため、月を指定する際に month と monthCode を組み合わせないでください。 1 つの月を表す表記法を選び、それを一貫して使用してください。 month は、1 年間の月の順番が必要なとき(たとえば、月をループする場合など)に、 monthCode は、月の名前が必要なとき(たとえば、誕生日を格納する場合など)に、それぞれより有用です。
月に関する次の誤った仮定に注意してください。
monthCodeとmonthが常に一致すると想定しないでください。- 1 か月間の日数を想定しないでください。代わりに
daysInMonthを使用してください。 monthCodeが人間にとってわかりやすい文字列であると想定しないでください。代わりに、toLocaleString()を使用して日付を書式化してください。- 通常、配列やオブジェクトに月の名前をキャッシュしないでください。たとえ
monthCodeが通常 1 つの暦内の月の名前に割り当てられている場合でも、例えばdate.toLocaleString("en-US", { calendar: date.calendarId, month: "long" })を使用して、常に月の名前を計算することをお勧めします。
day (月ベースのインデックス) に加えて、1 年の中の 1 日は dayOfYear によって一意に識別することもできます。dayOfYear は、1 から始まり、date.daysInYear で終わる、1 ずつ増加する正の値です。
「週」という概念は、天文学的なイベントとは関係なく、文化的に構築されたものです。最も一般的な長さは 7 日間ですが、週は 4、5、6、8 日、あるいはそれ以上、あるいは固定の日数がない場合もあります。ある日付のある週の具体的な日数を取得するには、その日付の daysInWeek を使用します。Temporal は、weekOfYear と yearOfWeek の組み合わせによって週を識別します。weekOfYear は、1 から始まり、年が進むごとに 1 ずつ増加する正の整数です。yearOfWeek は通常 year と同じですが、1 週間が 2 年にまたがる場合があり、yearOfWeek は暦の規則に基づいて 2 年のうちどちらか一方を選択するため、各年の開始または終了時に異なる場合があります。
メモ:
weekOfYear と yearOfWeek は常にペアで使用してください。weekOfYear と year の組み合わせでは使用しないでください。
週に関する次の誤った仮定に注意してください。
weekOfYearおよびyearOfWeekが常に存在すると想定しないでください。これらはundefinedである場合もあります。- 週は常に 7 日間であると想定しないでください。代わりに
daysInWeekを使用してください。 - 現在の
TemporalAPI は年週日付に対応していないため、これらのプロパティを使用して日付を構築したり、日付を年週表現にシリアル化したりすることはできません。これらは情報提供のみを目的としたプロパティです。
RFC 9557 形式
すべての Temporal クラスは、 ISO 8601 / RFC 3339 に基づく RFC 9557 で指定されている書式を使用して、シリアル化およびデシリアル化することができます。この書式の完全な形式は次のとおりです(空白は読みやすさのためにのみ使用されており、実際の文字列には存在しません)。
YYYY-MM-DD T HH:mm:ss.sssssssss Z/±HH:mm [time_zone_id] [u-ca=calendar_id]
クラスによって、各成分の存在に関する要件が異なるため、各クラスのドキュメント内に「RFC 9557 の書式化」という節があり、そのクラスが認識する書式化が指定されています。
これは、ISO 8601 にも基づいている Date で使用されている日付と時刻の文字列の書式とよく似ています。主な追加機能は、マイクロ秒およびナノ秒の成分を指定できることと、タイムゾーンおよび暦システムを指定できることです。
表現可能な日付
特定の暦の日付を表すすべての Temporal オブジェクトは、表現可能な日付の範囲に同様の制限を課します。この制限は、Unix 元期から ±108 日 (包括的) または -271821-04-20T00:00:00 から +275760-09-13T00:00:00 までの瞬間です。これは、有効な日時と同じ範囲です。より具体的には、次のとおりです。
Temporal.InstantとTemporal.ZonedDateTimeは、この制限をそのepochNanoseconds値に直接適用します。Temporal.PlainDateTimeは、 UTC タイムゾーンの日付と時刻を解釈し、Unix 元期から ±(108 + 1) 日(両端を含まない)である必要があります。したがって、有効な範囲は-271821-04-19T00:00:00より大きく+275760-09-14T00:00:00未満です。これにより、オフセットに関係なく、任意のZonedDateTimeをPlainDateTimeに変換することができます。Temporal.PlainDateは、PlainDateTimeと同じチェックをその日付の正午 (12:00:00) に適用するため、有効な範囲は-271821-04-19から+275760-09-13となります。これにより、PlainDateTimeは、その時点に関係なくPlainDateに変換することができ、その逆も同様です。Temporal.PlainYearMonthは、-271821-04から+275760-09までの範囲が有効です。これにより、日付に関係なく、任意のPlainDateをPlainYearMonthに変換することができます (ISO 以外の月の最初の日が ISO 月-271821-03に該当する場合を除く)。
Temporal オブジェクトは、この制限を超える日付/時刻を表すインスタンスの構築を拒否します。これには以下が含まれます。
- コンストラクターまたは
from()静的メソッドを使用します。 with()メソッドを使用して暦フィールドを更新します。add(),subtract(),round()、またはその他のメソッドを使用して、新しいインスタンスを導出します。
静的プロパティ
Temporal.DurationExperimental-
2 つの時点間の差を表します。日付/時刻の演算で使用できます。基本的には、年、月、週、日、時、分、秒、ミリ秒、マイクロ秒、ナノ秒の値の組み合わせとして表されます。
Temporal.InstantExperimental-
ナノ秒の精度で、固有の時点を表します。これは基本的に、タイムゾーンや暦システムを使用せずに、 Unix 元期(1970 年 1 月 1 日午前 0 時、UTC)からのナノ秒の数として表されます。
Temporal.NowExperimental-
さまざまな書式で現在の時刻を取得するメソッドを提供します。
Temporal.PlainDateExperimental-
暦の日付(時刻やタイムゾーンを含まない日付)を表します。例えば、どのタイムゾーンで発生しても 1 日中続く暦上のイベントなどです。基本的には、年、月、日フィールドと関連付けられた暦システムを含む ISO 8601 暦日付として表されます。
Temporal.PlainDateTimeExperimental-
タイムゾーンのない日付(暦日)および時刻(壁時計の時間)を表します。基本的には、日付(関連付けられた暦システム付き)と時刻の組み合わせとして表されます。
Temporal.PlainMonthDayExperimental-
年やタイムゾーンを含まない、暦の日付の月と日を表します。例えば、毎年繰り返し、1 日中に現れる暦上のイベントなどです。これは基本的に、年、月、日フィールドと関連付けられた暦システムを含む ISO 8601 の暦の日付として表されます。年は、ISO 以外の暦システムで月と日を明確にするために使用されます。
Temporal.PlainTimeExperimental-
日付やタイムゾーンを含まない時点を表します。例えば、毎日同じ時間に現れる繰り返しイベントなどです。基本的には、時、分、秒、ミリ秒、マイクロ秒、ナノ秒の値の組み合わせとして表されます。
Temporal.PlainYearMonthExperimental-
日やタイムゾーンを含まない、暦日の年と月を表します。例えば、1 か月間に現れる暦上のイベントなどです。基本的には、年、月、日フィールドと、関連付けられた暦システムを含む ISO 8601 暦日として表されます。日は、ISO 以外の暦システムで年と月を区別するために使用されます。
Temporal.ZonedDateTimeExperimental-
タイムゾーン付きの日付と時刻を表します。基本的には、瞬間、タイムゾーン、および暦システムの組み合わせとして表されます。
Temporal[Symbol.toStringTag]-
[Symbol.toStringTag]プロパティの初期値は、文字列"Temporal"です。このプロパティは、Object.prototype.toString()で使用されます。
仕様書
| Specification |
|---|
| Temporal> # sec-temporal-objects> |