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?

タイムゾーン処理のベストプラクティス

Last updated at Posted at 2025-12-28

📋 概要

このドキュメントでは、アプリにおけるタイムゾーン処理のベストプラクティスを説明します。将来的な海外展開を見据えた実装方針です。

🌍 基本的な方針

1. データベースには UTC 時間で保存

原則:すべてのタイムスタンプは UTC 時間で保存する

  • データベースのカラム型はTIMESTAMPTZ(タイムゾーン付きタイムスタンプ)を使用
  • PostgreSQL のNOW()関数は自動的に UTC 時間を返す
  • タイムゾーン情報はデータベースに保存しない
-- ✅ 正しい実装
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
// ✅ 正しい実装(UTC時間で保存)
updateData.completed_at = new Date().toISOString();

2. フロントエンドでローカル時間に変換して表示

原則:表示時のみユーザーのローカルタイムゾーンに変換する

  • データベースから取得した UTC 時間の文字列を、表示時にローカル時間に変換
  • toLocaleString()toLocaleDateString()を使用してローカル時間で表示
  • ユーザーのデバイスのタイムゾーン設定を自動的に使用
// ✅ 正しい実装
const date = new Date("2024-01-01T00:00:00Z"); // UTC時間
date.toLocaleString("ja-JP"); // ローカル時間で表示(日本時間の場合:2024/1/1 9:00:00)

3. 日付のみを扱う場合は注意が必要

原則:日付のみを扱う場合は、ユーザーのローカルタイムゾーンの日付として扱う

  • due_dateなどの日付のみのフィールドは、YYYY-MM-DD形式で保存
  • 時刻を含めないことで、タイムゾーンの影響を回避
  • 表示時は、ローカル時間の日付として解釈
// ✅ 正しい実装(日付のみ)
const dueDate = "2024-01-01"; // YYYY-MM-DD形式
const [year, month, day] = dueDate.split("-").map(Number);
const date = new Date(year, month - 1, day); // ローカル時間として解釈

4. タイムゾーン情報はデバイスから取得

原則:ユーザーのタイムゾーン情報は保存せず、デバイスの設定を使用

  • データベースにユーザーのタイムゾーン情報を保存しない
  • JavaScript のDateオブジェクトは自動的にデバイスのタイムゾーンを使用
  • 海外展開時も、ユーザーのデバイス設定に従って表示される

🔧 実装パターン

パターン 1: タイムスタンプの表示(created_at、updated_at など)

import { formatTimestamp } from "../utils/dateUtils";

// UTC時間のタイムスタンプをローカル時間で表示
formatTimestamp(profile.created_at);
// → "2024年1月1日 9:00"

// 日付のみ表示
formatTimestampDateOnly(profile.created_at);
// → "2024年1月1日"

パターン 2: 日付のみの表示(due_date など)

import { formatDateOnly } from "../utils/dateUtils";

// YYYY-MM-DD形式の文字列をローカル時間の日付として表示
formatDateOnly(task.due_date);
// → "2024年1月1日"

パターン 3: 日付の比較(グラフなど)

// 日付キー(YYYY-MM-DD)で比較することで、タイムゾーンの影響を回避
const formatDateKey = (date: Date) => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
};

const dateKey = formatDateKey(new Date(insight.created_at));

🚫 避けるべき実装

❌ タイムゾーンをハードコードする

// ❌ 悪い実装
const date = new Date(profile.created_at);
date.setHours(date.getHours() + 9); // 日本時間(UTC+9)をハードコード

❌ toISOString()でローカル時間を保存する

// ❌ 悪い実装(タイムゾーンのずれが発生)
const localDate = new Date();
dueDate = localDate.toISOString(); // UTC時間に変換されてしまう

❌ タイムゾーン情報をデータベースに保存する

-- ❌ 悪い実装
ALTER TABLE profiles ADD COLUMN timezone TEXT; -- 不要

📊 データフロー

ユーザーがアクションを実行
    ↓
フロントエンド: new Date()でローカル時間のDateオブジェクトを作成
    ↓
データベース: toISOString()でUTC時間の文字列に変換して保存
    ↓
データベース: TIMESTAMPTZ型でUTC時間として保存
    ↓
フロントエンド: データベースからUTC時間の文字列を取得
    ↓
フロントエンド: new Date(dateString)でDateオブジェクトに変換(UTC時間として解釈)
    ↓
フロントエンド: toLocaleString()でローカル時間に変換して表示

🌐 海外展開時の考慮事項

1. 言語設定

  • toLocaleString("ja-JP")のように言語コードを指定している箇所は、将来的にユーザーの言語設定に応じて動的に変更する必要がある
// 将来的な実装例
const locale = getUserLocale(); // "ja-JP", "en-US", etc.
date.toLocaleString(locale);

2. 日付フォーマット

  • 日本の「2024 年 1 月 1 日」形式と、アメリカの「January 1, 2024」形式など、地域によって異なる
  • Intl.DateTimeFormatを使用することで、自動的に適切な形式で表示される

3. 週の始まり

  • 日本では週の始まりは月曜日、アメリカでは日曜日
  • カレンダー表示などで週の始まりを扱う場合は、Intl.DateTimeFormatのオプションを使用

4. 夏時間(DST)

  • アメリカなどでは夏時間が存在するが、JavaScript のDateオブジェクトが自動的に処理するため、特別な対応は不要
  • データベースのTIMESTAMPTZ型も自動的に処理する

🔗 参考資料

📲 僕の作ったアプリ(宣伝)

Tanao – 家計簿いらずの資産トラッカーアプリ - App Store
https://apps.apple.com/jp/app/tanao-%E5%AE%B6%E8%A8%88%E7%B0%BF%E3%81%84%E3%82%89%E3%81%9A%E3%81%AE%E8%B3%87%E7%94%A3%E3%83%88%E3%83%A9%E3%83%83%E3%82%AB%E3%83%BC/id6753191685

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?