kmkkiii.com

`Date`の日付ズレと`Temporal`

🗓️

JavaScriptのDateオブジェクトの日付ズレ

カレンダーで 2026-04-14選んだのに、シリアライズすると 2026-04-13なってしまうことがあります。

これはJavaScriptの Date「日付」ではなく「日時」を表しており、toISOString() などで文字列化すると UTC に変換されることで、タイムゾーンによって日付がズレます。 Dateには、「タイムゾーンをもたない日付」という概念がありません。

const d = new Date(2026, 3, 14); // Wed Apr 14 2026 00:00:00 GMT+0900 (日本標準時)
d.toISOString(); // 2026-04-13T15:00:00.000Z

そのため、カレンダーで選んだ年月日を扱いたい場合でも、Date のままではなく YYYY-MM-DD のような文字列に変換して扱う必要があります。

例: nuqs利用している場合

nuqsは、型安全にクエリパラメータを状態管理できるようにしてくれるライブラリです(React用)。

parseAsIsoDate

nuqsシリアライズ処理ではtoISOString()使用されており、元のタイムゾーン情報は失われ、UTCの日時に変換されてしまいます。

Document

parseAsIsoDateの実装

カスタムパーサーを書く

nuqs createParserカスタムパーサーが書けます。toISOString()使わず、getFullYear() などでローカルタイムゾーンのまま文字列化することで対応できます。

import { createParser } from "nuqs";

const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;

export const parseAsLocalIsoDate = createParser({
  parse: (v) => {
    const head = v.slice(0, 10);
    if (!ISO_DATE_RE.test(head)) return null;
    const [y, m, d] = head.split("-").map(Number);
    const date = new Date(y, m - 1, d);
    if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {
      return null;
    }
    return date;
  },
  serialize: (v: Date) => {
    const y = v.getFullYear();
    const m = String(v.getMonth() + 1).padStart(2, "0");
    const d = String(v.getDate()).padStart(2, "0");
    return `${y}-${m}-${d}`;
  },
  eq: (a, b) => a.valueOf() === b.valueOf(),
});

Temporal API

※ Temporal はまだ Baseline ではなく、一部ブラウザで未対応のため、利用時は互換性確認や polyfill の検討が必要です。

Temporal オブジェクトは、組み込みのタイムゾーンおよび暦の表現、実時刻の変換、算術演算、書式化など、さまざまなシナリオで日付と時刻の管理を可能にします。これは、 Date オブジェクトを完全に置き換えるものとして設計されています。

(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Temporal)

Temporalには時刻やタイムゾーンを持たない日付としてTemporal.PlainDateあり、カレンダーから選んだ年月日をそのまま表現できます。

const d = new Date(2026, 3, 14); // Tue Apr 14 2026 00:00:00 GMT+0900 (日本標準時)

const plainDate = date.toTemporalInstant().toZonedDateTimeISO("Asia/Tokyo").toPlainDate();

console.log(plainDate.toString()); // 2026-04-14