タイムゾーンとは
- 地球上の地域ごとに設定された標準時間の区分
- 地方の時間自体は、単に "地域時間" "地方時間"などという
前提
UTC: 世界共通の基準時刻
日本標準時(JST): UTCに+9時間したもの
タイムゾーンオフセット: UTCからの時差表記の部分のこと-> "+09:00"
ISO String: 日付時刻の国際標準文字列形式⇩
2023-12-25T15:30:00+09:00 // 日本時間
2023-12-25T15:30:00-05:00 // 東部標準時
2023-12-25T06:30:00Z // UTC(Zは+00:00と同じ意味)
何故複雑なのか
Webフォームにおけるタイムゾーン情報の欠落
Webのformで受け取った文字列には多くの場合、タイムゾーンが存在していないためプログラマーがタイムゾーンを持たせる必要があります
これはhtmlのinputタグが文字列を返すということもありUIライブラリでも、文字列を返すものが多いと思います
タイムゾーンを理解していないと、ここでその場しのぎの型変換を行なってしまいデータが壊れる危険があります。
暗黙的な解釈が発生する
タイムゾーン情報がない文字列「2023-12-25T15:30:00」をどの地域の時間として扱うかが曖昧
システムの各層(フロントエンド、バックエンド、データベース)で異なるタイムゾーンの前提で動作する可能性がある
「日付だけ」扱いたいときは例外
例えば「2023-12-25」という日付だけを扱いたいだけなのに、Dateオブジェクトなどに格納すると時刻やタイムゾーンの影響で日付がずれることがあります。
例:new Date("2023-12-25")
をUTCで扱うと 2023-12-24T15:00:00Z
(日本時間)となり、「25日」のはずが24日として処理されてしまうことも
データがおかしくなるパターン
フロントエンドでUTC変換が行われないままバックエンドに投げられ、UTCとして解釈してしまう
- 例:日本時間の「2023-12-25T15:30:00」がそのままバックエンドに送信される
- バックエンドがこれをUTCとして解釈すると、実際は日本時間なので本来の時間より9時間遅い時間として保存される
- 結果:日本時間15:30のつもりが、UTC 15:30として保存され、日本で表示すると24:30(翌日0:30)になってしまう
UTCで保存したものが地域時間に変換されないまま表示されてしまう
- 例:UTC 06:30:00で保存されたデータをそのまま表示
- 日本では+9時間して15:30:00で表示すべきだが、06:30:00で表示されてしまう
- 結果:本来の時間より9時間早い値が表示される
See the Pen Untitled by 林知遼 (@tkckdfku-the-sasster) on CodePen.
対処法
対象方は複数あって、プロジェクトの規模など環境に応じて選択して使い分けることができると思います。
-
バックエンドはUTCで統一して、常にフロントエンドの末端(表示と入力時)で変換する
- こちらであればシンプルなルールで運用できます
- 少人数プロジェクトではこれだけで運用できると思いますし、レビュー観点として置くのもの良いと思います。
-
ミドルウェアやパーサーで Validation & 自動変換
- こちらはルールではなく仕組みを設ける方法です
- 処理が隠蔽されてしまうので、誤って重複した処理をしてしまったり、あえてutcで扱いたい例外パターンに対して弱いです
- リクエストとレスポンスでそれぞれ考える必要があり、さらにフロントエンドとバックエンドの連携の問題もあり、複雑化しやすいのが注意点です
- Validationの掛け方は、バックエンドでタイムゾーンオフセットがない文字列を許可しないなどの方法があります
例外:日付だけを扱いたいとき(i18n考慮)
日付だけを扱いたい時、データベース型をDate型にすることが可能です。
こちらはタイムゾーンを含まずシンプルに日付だけを保存する型です。
基本的に日付だけを扱いたいならDate型、時間だけを扱いたいならTime型というように使い分けるのが良いとおもいます。
ただ、日本で入力された日付はアメリカで表示する場合、自然になるように、1日前にしたい場合などでは、上記の変換処理を挟む必要がありDatetime型で統一して扱うことが有用です。
この場合は入力フォームではではUTCの0時として入力された日付を扱うと良いと思います。
例:Form入力 「2023-12-25」→ 2023-12-25T00:00:00Z
これにより、世界中の大半の地域で「25日」として扱われます
グリニッジ子午線(経度0度)より西の国では前日扱いとなります
地域 | タイムゾーン | UTC 0時のときのローカル時刻 | 日付 |
---|---|---|---|
日本 | +9 | 09:00 | ✅ 同日 |
中国 | +8 | 08:00 | ✅ 同日 |
韓国 | +9 | 09:00 | ✅ 同日 |
インド | +5:30 | 05:30 | ✅ 同日 |
イギリス | +0 | 00:00 | ✅ 同日 |
アメリカ東部 | -5 | 前日 19:00 | 前日扱い |
アメリカ西部 | -8 | 前日 16:00 | 前日扱い |
このように、UTC 0時で保存すれば、大半の国では「その日」を正しく共有できるため、i18nを意識した設計として最適です。
ライブラリ
こちらの方の記事が参考になりました。
Typescriptと相性が良さそうで、有名さも十分なdate-fnsを使っていきたいと思いました。
間違っているかも
- 完全に個人で調べながら書いているので、間違っている可能性があります
- また同じ問題に遭遇したときこの記事をメンテしていこうと思います