本資料における前提
システムのタイムゾーンはJSTとする。
まずは問題のないケース
27日、28日、29日の連続した日付を生成したいとき、以下のようにJST時刻を使用しparseISOでDateオブジェクトを生成する。こうすることで、問題なく連続した日付を生成することができる。というか一般的にはこうすると思う。
import { eachDayOfInterval, parseISO } from "date-fns";
test("JSTを使った場合の eachDayOfInterval の挙動が意図通りか確認するテスト", () => {
const start = parseISO("2024-07-27T00:00:00+09:00");
const end = parseISO("2024-07-29T00:00:00+09:00");
const dateList = eachDayOfInterval({ start, end });
expect(dateList).toEqual([
parseISO("2024-07-27T00:00:00.000+09:00"),
parseISO("2024-07-28T00:00:00.000+09:00"),
parseISO("2024-07-29T00:00:00.000+09:00"),
]);
});
問題となるようなケース
ところが、なんらかの事情によりJSTではなくUTC時刻によって生成されたDateオブジェクトをbeginとendに与えて、UTCのまま計算したいケースがあるかもしれない。UTCで渡してしまうと、以下のようにUTC時刻27日から返してほしくてもUTC26日から返してしまう。
test("UTCを使った場合の eachDayOfInterval の挙動が意図通りか確認するテスト", () => {
const start = parseISO("2024-07-27T00:00:00Z");
const end = parseISO("2024-07-29T00:00:00Z");
const dateList = eachDayOfInterval({ start, end });
expect(dateList).toEqual([
parseISO("2024-07-27T00:00:00.000Z"), // actual: 2024-07-26T15:00:00.000Z
parseISO("2024-07-28T00:00:00.000Z"), // actual: 2024-07-27T15:00:00.000Z
parseISO("2024-07-29T00:00:00.000Z"), // actual: 2024-07-28T15:00:00.000Z
]);
});
「date-fnsはタイムゾーンが考慮されない」ということを考えれば当たり前では?と思える人はここで読むのをやめてもらっても構いません。同様のissueがあることからも、この挙動に関して不思議に思うのは自分だけではないはず。
解決方法
いくつかあると思うが、最も理解しやすい方法は、UTC0時ではなく15時でDateオブジェクトを生成すれば良い。
test("UTC15時であれば eachDayOfInterval の挙動が意図通りか確認するテスト", () => {
// utc date
const start = parseISO("2024-07-27T15:00:00Z");
const end = parseISO("2024-07-29T15:00:00Z");
const dateList = eachDayOfInterval({ start, end });
expect(dateList).toEqual([
parseISO("2024-07-27T15:00:00.000Z"),
parseISO("2024-07-28T15:00:00.000Z"),
parseISO("2024-07-29T15:00:00.000Z"),
]);
});
なぜこうなるのか
以下の処理が行われることにより、最初と最後の日付が1日ずれてしまい意図していない挙動になると推測する。
- 開始時刻
utc1
がUTCで与えられることにより、JSTjst1
に変換する - 1日目の日付
d1
にjst1
を当日00:00に巻き戻した結果を代入する -
d1
をUTC時刻utc1'
に戻すと、UTCから見た場合に前日まで巻き戻りすぎてしまっている - 同様に終了時刻
utc2
についてもjst2
に変換する - 最終日の日付
dn
を計算するためにjst2
を当日00:00に巻き戻す -
dn
をUTC時刻utc2
に戻した時もやっぱり前日まで戻りすぎてしまっている
先ほどのうまくいかない例で説明すると、
-
utc1 = 7/27 00:00 (UTC)
なので、jst1 = 7/27 09:00 (JST)
に変換される -
jst1
を巻き戻すのでd1 = 7/27 00:00 (JST)
-
d1
を UTCに戻すと、utc1' = 7/26 15:00 (UTC)
であり、本当はUTCでも27日を期待しているのに、UTCだと7/26になってしまう -
utc2 = 7/29 00:00 (UTC)
なので、jst2 = 7/29 09:00 (JST)
に変換される -
jst2
を巻き戻すのでdn = 7/29 00:00 (JST)
-
dn
を UTCに戻すと、utc2' = 7/28 15:00 (UTC)
であり、本当はUTCでも29日を期待しているのに、UTCだと7/28になってしまう
感想
時間情報がいらないケースって結構あると思うけど、どうしてDateとDateTimeをわけないのだろう。