timeパッケージのタイムゾーン設計レビュー完全ガイド:UTC・TZ依存のバグを防ぐ構造的判断法

time.Time はGo言語における日付・時刻処理の中核を担う。
API連携・DB保存・スケジューリング・ログ記録・ビジネスロジック判定など、あらゆる層で登場するにも関わらず、実装者の主観的な時間感覚がコードに反映されやすい危険領域でもある。

レビューアーは「動くかどうか」ではなく「環境差異で破綻しない構造か」を重視して評価する必要がある。

本稿では、timeパッケージの利用コードをレビューする際に必ず確認すべき構造的判断基準を、設計例とレビュー指摘付きで整理する。


良い実装例:タイムゾーン設計が明示された安定構造

func IsExpired(target time.Time, locName string) (bool, error) {
  loc, err := time.LoadLocation(locName)
  if err != nil {
    return false, err
  }
  now := time.Now().In(loc)
  return now.After(target.In(loc)), nil
}
  • ロケーション名を引数で受け取る設計
  • 比較前にタイムゾーンを正規化
  • サーバ実行環境に依存しない構造

レビューアー視点では理想的な「環境非依存型time設計」。


問題のある実装例とレビュー指摘

① time.Now()の環境依存使用

func ExpiredAt(target time.Time) bool {
  now := time.Now()
  return now.After(target)
}
@Reviewer
環境依存のローカル時刻を使用しています。UTC基準またはロケーションを明示指定し、比較の前提時刻を統一してください。

CIと本番で比較結果が異なる典型構造。


② 比較対象間のロケーション不一致

t1 := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
t2 := time.Date(2024, 1, 1, 9, 0, 0, 0, time.FixedZone("JST", 9*3600))

if t1.Equal(t2) {
  fmt.Println("Equal")
}
@Reviewer
Equalはロケーションを含めて比較します。同時刻比較用途なら t1.Equal(t2.UTC()) などで正規化して判定してください。

③ time.Date()生成時のローカル依存

today := time.Date(2024, 10, 1, 0, 0, 0, 0, time.Local)
@Reviewer
time.Localは環境依存です。常に固定ロケーション(例:Asia/Tokyo)をLoadLocationで明示してください。

④ 日付境界処理の曖昧性

if time.Now().Hour() == 0 {
  resetJob()
}
@Reviewer
Hour判定はTZ依存でズレます。比較対象時刻を事前にUTCまたは指定ロケーションへ変換して評価してください。

⑤ 固定オフセットでの擬似TZ実装

loc := time.FixedZone("JST", 9*3600)
now := time.Now().In(loc)
@Reviewer
FixedZoneは夏時間に対応しません。常にIANA形式("Asia/Tokyo")をLoadLocationで取得する構造に統一してください。

時刻比較設計の安定化パターン

loc, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
  log.Fatal(err)
}
base := time.Date(2024, 12, 31, 0, 0, 0, 0, loc)
now := time.Now().In(loc)

if now.After(base) {
  fmt.Println("期限切れ")
}
  • ロケーション指定を共通関数化しておくと設計安定性が上がる
  • DB保存時は UTC / 表示時は In(loc) の2層構成が実務最適解

時刻処理レビュー時の設計視点

観点 説明
実行環境依存排除 time.Local 使用禁止。IANAロケーション統一
比較前の正規化 .UTC()または.In(loc)でTZ統一してから判定
境界処理の安定性 0時判定・日跨ぎ処理はTZ固定前提で組み立て
フォーマット統一 RFC3339を標準化。日付型は "2006-01-02" 明示
入力パースのロケーション指定 time.ParseInLocation() 使用の徹底
夏時間考慮 FixedZone非使用。必ずLoadLocation使用

実装事故パターン一覧

環境差異でズレる例

now := time.Now()  // 開発: JST / 本番: UTC
@Reviewer
実行環境でNow()返却TZが変わります。全環境統一できる構造へ変更してください。

フォーマット不統一で破綻

s := t.Format("2006-01-02 15:04:05")
@Reviewer
文字列パース時のTZ逸脱を招きます。RFC3339など標準フォーマットへ統一してください。

サマータイム差異吸収失敗

t := time.Date(2024, 3, 10, 2, 0, 0, 0, time.FixedZone("EST", -5*3600))
@Reviewer
米国はDST適用対象です。FixedZoneでの静的TZ表現は誤動作原因になります。

タイムゾーン未指定による環境差異流れ

UML Diagram

time処理レビュー実施フロー

UML Diagram

まとめ:時刻レビューは「設計の隠れ地雷」を可視化する実務力

  • タイムゾーンは常に環境依存を生む
  • 比較演算はロケーション統一前提で構造化
  • サマータイムを含む世界対応はFixedZone非推奨
  • レビューは「環境移動耐性」視点で実施

時刻処理レビューは「実装者の主観世界」を「設計の絶対空間」へ矯正する作業とも言える。
レビューアーがTZ依存設計の芽を初期から排除することで、極めて高い運用安定性が確保される。