1
0

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 1 year has passed since last update.

Temporal入門その2 ~ 実際に日付と時刻を操作してみる

Last updated at Posted at 2023-07-10

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.InstantTemporal.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.ZonedDateTimeTemporal.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関数の返り値は-101です。
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オブジェクトと文字列の間の変換方法についての記事も書きましたので、合わせてご覧ください。

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?