家計簿アプリを作る #70:Cloudflare Pages への再デプロイ
家計簿アプリ作成シリーズの第70回です。よく使う取引のクイック登録機能をCloudflare Pagesへ再デプロイします。
デプロイ手順
transaction_templates テーブルのマイグレーションを本番のD1に適用してから、ビルド・デプロイを実行しました。
ハマりポイント:本番環境だけ日付が1日ずれる
テンプレートからのクイック登録は「今日の日付」で取引を作成する仕様でしたが、ローカルでは正しく動くのに、デプロイ後の本番環境だけ日付が1日前になってしまう不具合が発覚しました。
原因は、Cloudflare Workers(Cloudflare Pagesの実行基盤)がデフォルトでUTCタイムゾーンで動作することでした。ローカルのMacはJST(UTC+9)なので new Date() の年月日取得(getFullYear()など)はJST基準になりますが、本番のUTC環境では同じコードが日本時間の午前9時より前(UTCではまだ前日)にUTC基準の日付を返してしまい、1日前として保存されていました。
// ❌ 実行環境のローカルタイムゾーンに依存する
function formatLocalDate(date: Date): string {
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}`;
}
Intl.DateTimeFormat に timeZone: 'Asia/Tokyo' を明示することで、実行環境のタイムゾーンに関係なく常に日本時間で計算するように修正しました。
// ✅ 明示的にAsia/Tokyoを指定し、実行環境に依存しない
export function formatLocalDate(date: Date, format: 'day' | 'month' = 'day'): string {
const options: Intl.DateTimeFormatOptions = format === 'month'
? { timeZone: 'Asia/Tokyo', year: 'numeric', month: '2-digit' }
: { timeZone: 'Asia/Tokyo', year: 'numeric', month: '2-digit', day: '2-digit' };
const formatter = new Intl.DateTimeFormat('sv-SE', options);
return formatter.format(date);
}
'day'/'month' のフォーマット切り替えを引数で持たせることで、「今日の日付(YYYY-MM-DD)」だけでなく、定期収支の自動登録で使っている「今月(YYYY-MM)」の計算にも同じ関数を使い回せるようにしています。
「瞬間」を扱う値と「カレンダー上の日付」を扱う値の違い
この修正の過程で、「DBに保存されている updatedAt などのタイムスタンプはUTCのままでいいのか」という疑問も出てきました。結論は、UTCのままで問題ありません。
- 「いつか(瞬間)」を記録する値(
updatedAtなど):タイムスタンプとしてUTCのまま保存し、表示する側でタイムゾーン変換すればよい - 「何月何日か(カレンダー上の日付)」を扱う値(取引の
dateなど):タイムゾーンを明示して文字列に変換する必要がある
今回のバグは後者(カレンダー上の日付)を、実行環境依存の方法で計算してしまったことが原因でした。DBに保存する瞬時点のタイムスタンプと、人間が意識する「暦の上の日付」は性質が異なる、という整理が今回の学びでした。
まとめ
| ポイント | 内容 |
|---|---|
| ローカルと本番のタイムゾーン差 | Cloudflare Workersはデフォルト UTC。開発機(JST)と本番で結果が変わりうる |
| 対処法 | Intl.DateTimeFormatにtimeZoneを明示し、実行環境に依存しない計算にする |
| タイムスタンプとカレンダー日付 | 前者はUTC保存のままでよく、後者だけタイムゾーンを意識した変換が必要 |