0
0

[date-fns] JST環境下で eachDayOfInterval に UTC時刻を突っ込んだ時の挙動が微妙な話

Posted at

本資料における前提

システムのタイムゾーンは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日ずれてしまい意図していない挙動になると推測する。

  1. 開始時刻utc1がUTCで与えられることにより、JSTjst1に変換する
  2. 1日目の日付d1jst1を当日00:00に巻き戻した結果を代入する
  3. d1をUTC時刻utc1'に戻すと、UTCから見た場合に前日まで巻き戻りすぎてしまっている
  4. 同様に終了時刻utc2についてもjst2に変換する
  5. 最終日の日付dnを計算するためにjst2を当日00:00に巻き戻す
  6. dnをUTC時刻utc2に戻した時もやっぱり前日まで戻りすぎてしまっている

先ほどのうまくいかない例で説明すると、

  1. utc1 = 7/27 00:00 (UTC) なので、 jst1 = 7/27 09:00 (JST) に変換される
  2. jst1を巻き戻すので d1 = 7/27 00:00 (JST)
  3. d1 を UTCに戻すと、 utc1' = 7/26 15:00 (UTC)であり、本当はUTCでも27日を期待しているのに、UTCだと7/26になってしまう
  4. utc2 = 7/29 00:00 (UTC)なので、jst2 = 7/29 09:00 (JST) に変換される
  5. jst2を巻き戻すので dn = 7/29 00:00 (JST)
  6. dn を UTCに戻すと、 utc2' = 7/28 15:00 (UTC)であり、本当はUTCでも29日を期待しているのに、UTCだと7/28になってしまう

感想

時間情報がいらないケースって結構あると思うけど、どうしてDateとDateTimeをわけないのだろう。

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