Temporalとは
Temporalとは、JavaScriptに導入が予定されている(2023年時点)、Date型に代わる新しい日付処理APIです。
https://tc39.es/proposal-temporal/docs/index.html
現在TC39のStage 3で、仕様策定に関する議論が行われています。 https://github.com/tc39/proposal-temporal
従来のDate型の問題点を解消しており、タイムゾーンやサマータイムに関する処理も行うことができるようになっています。
この記事では、前回の記事に引き続き、日付と時刻の処理を試していきます。
日付と時刻の操作
前回の記事では、日付や時刻を表すためにTemporal.Instant
、Temporal.PlainXXX
系、Temporal.ZonedDateTime
の3タイプの型があると解説しました。
ただし、細かい違いはあるものの、これらの操作方法は基本的に同一になるように設計されています。(例えば、equals
メソッドは全ての型に実装されていて、使い方も共通。)
以下では例としてTemporal.ZonedDateTime
を使用して解説しますが、一部のケースを除いて他の型でも同じように使用できます。
年月日の取得
year
(年)、month
(月)、day
(日)、hour
(時)、minute
(分)、second
(秒)などの情報はプロパティから取得することができます。
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,
});
console.log(zonedDateTime.year); // => 2023
console.log(zonedDateTime.month); // => 4
console.log(zonedDateTime.monthCode); // => "M04"
console.log(zonedDateTime.day); // => 8
console.log(zonedDateTime.hour); // => 22
console.log(zonedDateTime.minute); // => 34
console.log(zonedDateTime.second); // => 18
console.log(zonedDateTime.millisecond); // => 4
console.log(zonedDateTime.microsecond); // => 2
console.log(zonedDateTime.nanosecond); // => 1
曜日など詳しい情報を取得する
Temporal.ZonedDateTime
やTemporal.PlainXXX
を使用している場合、カレンダー情報を基に日付に関する詳しい情報を取得できます。
例えば、何曜日かについてはzonedDateTime.dayOfWeek
で取得します。
// 何曜日か(月曜日が1、日曜日が7)
console.log(zonedDateTime.dayOfWeek); // => 6
// その年の何日目か
console.log(zonedDateTime.dayOfYear); // => 98
// ISO週番号方式において、その年の何週目か
// 詳しくは=> https://casualstartup.hatenablog.jp/entry/20130908/iso_weeknum
console.log(zonedDateTime.weekOfYear); // => 14
// ISO週番号方式において何年か
// 詳しくは=> https://casualstartup.hatenablog.jp/entry/20130908/iso_weeknum
console.log(zonedDateTime.yearOfWeek); // => 2023
// 1週間は何日か(通常は7日)
console.log(zonedDateTime.daysInWeek); // => 7
// (その月の) 1か月は何日あるか
console.log(zonedDateTime.daysInMonth); // => 30
// (その年の) 1年間は何日あるか
console.log(zonedDateTime.daysInYear); // => 365
// (その年の) 1年間は何か月あるか
console.log(zonedDateTime.monthsInYear); // => 12
// その年はうるう年か
console.log(zonedDateTime.inLeapYear); // => false
// (その日の) 1日は何時間か(通常24時間だがサマータイムによって差あり)
console.log(zonedDateTime.hoursInDay); // => 24
// その日の起点時刻(=0時00分)を取得する
// サマータイムによって0時00分にならないこともある
console.log(zonedDateTime.startOfDay()); // => Temporal.ZonedDateTime { 2023-04-08T00:00:00+09:00[Asia/Tokyo] }
日付の足し算、引き算
Temporalでは、日時同士の以下のような計算をサポートしています。
- 日時 + 時間 = 日時 (
.add()
メソッド) - 日時 - 時間 = 日時 (
.subtract()
メソッド) - 日時 - 日時 = 時間 (
.until()
メソッドと.since()
メソッド)
これらを利用することで、「翌日の同じ時刻」「翌月の同じ日」などを求めることができます。
zonedDateTime.add({ days: 1 }); // 1日後の日付が返る
zonedDateTime.add({ weeks: 2 }); // 2週間後の日付
zonedDateTime.add({ months: 3 }); // 3か月後の日付
zonedDateTime.add({ years: 4 }); // 4年後の日付
zonedDateTime.subtract({ days: 1 }); // 1日前の日付が返る
zonedDateTime.subtract({ weeks: 2 }); // 2週間前の日付
zonedDateTime.subtract({ months: 3 }); // 3か月前の日付
zonedDateTime.subtract({ years: 4 }); // 4年前の日付
const timeA = Temporal.ZonedDateTime.from({
timeZone: "Asia/Tokyo",
year: 2022,
month: 3,
day: 7,
hour: 21,
minute: 33,
second: 17,
});
const timeB = Temporal.ZonedDateTime.from({
timeZone: "Asia/Tokyo",
year: 2023,
month: 4,
day: 8,
hour: 22,
minute: 34,
second: 18,
});
// timeB - timeA を計算する
const duration1 = timeA.until(timeB);
console.log(duration1.years);
console.log(duration1.months);
console.log(duration1.weeks);
console.log(duration1.days);
console.log(duration1.hours);
console.log(duration1.minutes);
console.log(duration1.seconds);
console.log(duration1.milliseconds);
console.log(duration1.microseconds);
console.log(duration1.nanoseconds);
// timeA - timeB を計算する
const duration2 = timeA.since(timeB);
console.log(duration2.years);
console.log(duration2.months);
console.log(duration2.weeks);
console.log(duration2.days);
console.log(duration2.hours);
console.log(duration2.minutes);
console.log(duration2.seconds);
console.log(duration2.milliseconds);
console.log(duration2.microseconds);
console.log(duration2.nanoseconds);
A.since(B)
とA.until(B)
は符号が逆になっているだけで、同じ操作です。
.since()
と.until()
の返り値はTemporal.Duration
型です。
Temporal.Duration
型を人間が読める形にフォーマットするIntl.DurationFormat
という関数が提案されていますが、現状では使えるブラウザは少ないです。
一部の値を変更して新しい日付を生成する
日付時刻のうち、一部分だけ書き換えたい場合があります。
例えば、その月の初めの日(1日)を求めたい時は、元のオブジェクトのday
の部分だけを1
に書き換えることで可能です。
そのような処理は、.with()
メソッドで可能です。
// 元の日付から、dayの値のみが1に変更された新しいオブジェクトを返す
console.log(zonedDateTime.with({ day: 1 })); // 2023-04-01T22:34:18.004002001+09:00[Asia/Tokyo]
// 元の日付から、hourとminuteの値のみが0に変更された新しいオブジェクトを返す
console.log(zonedDateTime.with({ hour: 0, minute: 0 })); // 2023-04-08T00:00:18.004002001+09:00[Asia/Tokyo]
Temporalはイミュータブルなデータ構造のため、.with()
メソッドは新しいインスタンスを返します(元のオブジェクトは変更されません)。
同様のメソッドに、withPlainTime
, withPlainDate
, withTimeZone
, withCalendar
が存在します。
zonedDateTime.withTimeZone('Europe/London'); // => London時間で言うと何時か
日付の丸め処理
.round()
メソッドを使用すると、好きな単位に日付を四捨五入、切り捨て、切り上げできます。
// smallestUnitで、どの単位で丸めるかを指定できる
console.log(zonedDateTime.round({ smallestUnit: "second" }));
// roundingIncrementで、丸める大きさを指定できる
// この場合は20分単位に丸める
console.log(zonedDateTime.round({
roundingIncrement: 20,
smallestUnit: "minute"
}));
// roundingModeで、四捨五入するか切り上げるか切り捨てるかを選べる
// - "ceil": 切り上げ
// - "floor": 切り捨て
// - "halfCeil": 四捨五入(ちょうど時刻の中間地点で、丸める先が選べない場合は、切り上げられる)
// - "halfFloor": 四捨五入(ちょうど時刻の中間地点で、丸める先が選べない場合は、切り捨てられる)
// - "halfEven": 四捨五入(ちょうど時刻の中間地点で、丸める先が選べない場合は、偶数丸め)
// デフォルトは"halfExpand"
console.log(zonedDateTime.round({
roundingIncrement: 20,
smallestUnit: "minute",
roundingMode: "floor",
}));
なお、roundingIncrement
の値は、86400秒=24時間を均等に分割できる値でなければならないそうです。(15分はOKだが7秒はNG)
日時のソート、比較
まず、日時と日時が等しいか調べる方法です。
Temporalの日時はクラスのインスタンスなので、===
演算子で比較しても正しい結果はえられません。
const timeA = Temporal.ZonedDateTime.from({
timeZone: "Asia/Tokyo",
year: 2023,
month: 4,
day: 8,
});
const timeB = Temporal.ZonedDateTime.from({
timeZone: "Asia/Tokyo",
year: 2023,
month: 4,
day: 8,
});
// ❌ 正しく比較できない
console.log(timeA === timeB); // 同じ日時を表しているが、false
日時と日時が等しいか調べるには、.equals()
メソッドを使用します。
console.log(timeA.equals(timeB)); // => true
日時と日時を大小比較するには、各クラスのstatic methodであるcompare
関数を使用します。
compare
関数の返り値は-1
か0
か1
です。
compare(timeA, timeB)
のように引数を渡した場合の戻り値は、
- timeAよりtimeBが後の場合、
-1
- timeAとtimeBが同じ日時の場合、
0
- timeAよりtimeBが後の場合、
1
のようになります。
// 以下の関数は全て、-1か0か1を返す
// timeAよりtimeBが後の場合、-1
// timeAとtimeBが同じ日時の場合、0
// timeAよりtimeBが後の場合、1
Temporal.Instant.compare(timeA, timeB); // Instantを大小比較する
Temporal.PlainDateTime.compare(timeA, timeB); // PlainDateTimeを大小比較する
Temporal.PlainDate.compare(timeA, timeB); // PlainDateを大小比較する
Temporal.PlainTime.compare(timeA, timeB); // PlainTimeを大小比較する
Temporal.PlainYearMonth.compare(timeA, timeB); // PlainYearMonthを大小比較する
// Temporal.PlainMonthDay.compare(timeA, timeB);
Temporal.ZonedDateTime.compare(timeA, timeB); // ZonedDateTimeを大小比較する
なお、Temporal.PlainMonthDay.compare
は今のところ存在しません。
将来的にはproposal-temporal-v2で導入される可能性があります。
配列に入った日時をソートすることもできます。
配列内の日時をソートするには、Array.prototype.sort
に渡す比較関数に先ほどのcompare
関数を渡すだけです。
const array = [.....]; // 日時が入った配列
array.sort(Temporal.Instant.compare); // Instantの配列をソートする
array.sort(Temporal.PlainDateTime.compare); // PlainDateTimeの配列をソートする
array.sort(Temporal.PlainDate.compare); // PlainDateの配列をソートする
array.sort(Temporal.PlainTime.compare); // PlainTimeの配列をソートする
array.sort(Temporal.PlainYearMonth.compare); // PlainYearMonthの配列をソートする
// array.sort(Temporal.PlainMonthDay.compare);
array.sort(Temporal.ZonedDateTime.compare); // ZonedDateTimeの配列をソートする
Unix Timeの取得
Unix TimeはepochMilliseconds
などのプロパティから取得できます。
マイクロ秒とナノ秒では、値はBigInt型になます。
console.log(zonedDateTime.epochSeconds); // => 1680960858 (秒)
console.log(zonedDateTime.epochMilliseconds); // => 1680960858004 (ミリ秒)
console.log(zonedDateTime.epochMicroseconds); // => 1680960858004002n (マイクロ秒)
console.log(zonedDateTime.epochNanoseconds); // => 1680960858004002001n (ナノ秒)
元号の取得
zonedDateTime.era
は通常はundefined
ですが、Temporal.ZonedDateTime
の生成時に[u-ca=japanese]
を指定することで、「Heisei」や「Reiwa」などの元号を取得できます。
const zonedDateTime = Temporal.ZonedDateTime.from("2023-02-23T03:24:30[Asia/Tokyo][u-ca=japanese]");
console.log(zonedDateTime.era); // => "reiwa"
console.log(zonedDateTime.eraYear); // => 5
まとめ
Temporalを使って日付と時刻を操作する方法について解説しました。
Temporalを使用すると、日付と時刻に対して以下のような操作を行うことができます。
- 年月日の取得 (
.year
や.month
などのプロパティ) - 曜日など詳しい情報の取得 (
.dayOfWeek
などのプロパティ) - 日付の足し算、引き算 (
add
,subtract
,until
,since
メソッド) - 一部の値を変更して新しい日付を生成する (
with
メソッド) - 日付の丸め処理 (
round
メソッド) - 日時のソート、比較 (
equals
メソッド、compare
関数) - Unix Timeの取得 (
epochMilliseconds
プロパティなど) - 元号の取得 (
era
プロパティなど)
この記事に続いて、Temporalオブジェクトと文字列の間の変換方法についての記事も書きましたので、合わせてご覧ください。