😺

DBから取り出した日時データが画面上でUTCで扱われる場合の対処法(Next.js)

に公開

DB上に登録されているTIMESTAMP型のデータはタイムゾーン情報を持たないためUTCとして解釈される。

テーブルから取り出すと下記のように「000Z」=「UTC」となる。
2025-04-10T03:00:00.000Z

PostgreSQLで明示的にTimeZoneを指定する場合は
TIMESTAMPTZ(timestamp with time zone)型を使うこともできるようだが、今回は(色々試行錯誤して結果として)API側でテーブルから抜き出した後にタイムゾーンを付けた。

●変換の関数

function toJSTISOString(dateObj) {
  // UTC → JST に変換して ISO 文字列で返す
  return DateTime.fromJSDate(dateObj, { zone: 'Asia/Tokyo' }).toISO();
}

データを取り出す処理。jstStartとjstEndにJST変換後のTIMEデータが入っている。

    const result = await pool.query('SELECT * FROM events');
    const events = result.rows.map(row => {
      console.log("raw start_time:", row.start_time);
      const jstStart = toJSTISOString(row.start_time);
      const jstEnd = toJSTISOString(row.end_time);
      console.log("JST start_time:", jstStart);
      return {
        id: row.id,
        title: row.title,
        start: jstStart,
        end: jstEnd,
        booking_status: row.booking_status,
      };

raw start_time: 2025-04-10T03:00:00.000Z (rawデータ)
JST start_time: 2025-04-10T12:00:00.000+09:00 (JSTに変更後)

これで明示的にTimeZoneを設定できているため、
ローカル環境/デプロイ環境関わらず、JSTで表示されるはず。

(試していないが)恐らく登録する時も下記のようにtimeZoneを指定する必要あり。(JST→UTC)

const tokyoTime = new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Tokyo' });

因みに、TimeZone名は下記から確認可能です。Asia/Tokyoが多いと思いますが。
🌍 IANA公式リスト(Wikipedia):
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

🌐 Developer向けタイムゾーン一覧:
https://moment.github.io/luxon/#/zones
※ライブラリ Luxon のドキュメントですが、タイムゾーンIDは共通です。

参考になれば幸いです。

Discussion