このドキュメントは、ECMAScript仕様のTemporal API周辺、特にタイムゾーンと日付・時刻を扱う抽象操作(Abstract Operation)に関する輪読会の議論をまとめたものです。
本編に入る前に、前回議論した内容について振り返りが行われました。
NamedTimeZoneOffsetNanoseconds の実装依存性:
TimeZoneIdentifier レコードは Identifier と PrimaryIdentifier の2つのフィールドを持つ。Identifier はIANAタイムゾーンデータベースのリンク名(エイリアス)である場合がある。SystemTimeZoneIdentifier はホスト環境のタイムゾーン名を返し、オフセットではなく"America/New_York"のようなIANA名で返すことが推奨される。LocalTime(t):
t を受け取り、システムのタイムゾーンに基づいてローカル時刻に変換する操作。UTC(t) 抽象操作の読解今回は LocalTime の逆操作である UTC(t) から読み進めました。この操作はローカル時刻 t をUTC時刻に変換します。
この操作の主な目的は、ローカル時刻をUTCのタイムスタンプ(Time Value)に変換することです。 複雑性の中心は、夏時間の切り替えなどによって発生する時刻の重複と欠落の扱いです。
[0])を選択することでこれを実現しています。t の直前の有効な時刻 tBefore を見つけ、その時刻を基準にオフセットを計算します。仕様のノートでは、これらの複雑なケースについて具体的な例が挙げられていました。
2017-11-05 01:30:00 in America/New_York
UTC-04 として解釈されなければならない、とされています。(移行後の UTC-05 ではない)2017-03-12 02:30:00 in America/New_York
UTC-05 の 02:30 (UTC-04 の 03:30 と同義)として解釈されなければならない、とされています。これは移行前のタイムゾーンオフセットを適用した結果です。LocalTime(UTC(tLocal)) が必ずしも元の tLocal と等しくならない、という往復変換の非可逆性についても言及されました。これは、時刻の重複・欠落があるためです。
続いて、日付と時刻の各要素(年、月、日、時、分、秒など)から最終的なタイムスタンプを組み立てるための一連の抽象操作を読み進めました。
MakeTime(hour, min, sec, ms)// (h * msPerHour + m * msPerMinute) + (s * msPerSecond + ms) のような順番で計算
(h * 3600000 + m * 60000) + (s * 1000 + ms)
MakeDay(year, month, day)13月 は翌年の 1月 のように、年と月に変換されます。t を求めます。Day(t) + day - 1 という計算で最終的な経過日数を算出します。
day を直接使わず、一度「1日」のタイムスタンプを求めてから day - 1 を加算するのは、day に 32 などの不正な値が渡される可能性があるため、まず有効な日付(その月の1日)を基準として計算を安全に行うための設計であると考察されました。MakeDate(day, time)MakeDay で得られた経過日数と MakeTime で得られたミリ秒を結合し、エポックからの総ミリ秒数を算出する。非常にシンプルな結合操作です。MakeFullYear(year)year が 0 から 99 の範囲で与えられた場合、1900年台の年として解釈します。
MakeFullYear(26) は 1926 を返します。MakeFullYear(0) は 1900 を返します。100 以上の場合は、その値がそのまま返されます。背景: これは new Date() コンストラクタの歴史的な挙動との互換性を保つための仕様です。輪読会では実際にJavaScriptでこの挙動が確認されました。
// 輪読会で実行されたコード例
new Date(26, 1, 1); // -> Tue Feb 01 1926 00:00:00 GMT+0900 (日本標準時)
new Date(0, 1, 1); // -> Tue Feb 01 1900 00:00:00 GMT+0900 (日本標準時)
この「JavaScriptの闇」とも言える仕様に、参加者からは驚きの声が上がりました。
TimeClip(time)TimeValue の範囲内(±8.64e15 ミリ秒)に収まるようにクリップ(切り捨て)する。NaN を返します。これにより、タイムスタンプが常に有効な範囲内に収まることを保証します。次回は DateTime String Format から読み進める予定です。