⏱️
time.Durationで期間を扱うときに気をつけること
これはUnipos Advent Calendar 2024の記事です。
time.Duration
の扱いに関する、ちょっとしたハマりポイントについて説明します。
データの有効期限を動的に設定できるようにしたうえで、期限切れ判定をしたい
期間判定をするつもりの、こういうコードがありました。
var ValidityPeriodDays = 30 // 実際はDBとかから取得する
createdAt := time.Date(2024, 12, 5, 0, 0, 0, 0, time.UTC) // 仮
year, month, date := createdAt.Add(time.Hour * 24 * time.Duration(ValidityPeriodDays)).Date()
validityPeriod := time.Date(year, month, date, 0, 0, 0, 0, time.UTC)
fmt.Println(time.Now().After(validityPeriod)) // 期限切れてあればtrue
// 実際はこの判定結果でなにかする
この実装が起こした問題
実質無限にするつもりで ValidityPeriodDays
に 200000
を代入したところ、判定が常に失敗するようになりました。
何がいけなかったか
time.Duration
はナノ秒をint64で持つ実装になっているので、符号つき64bit整数で表現できるナノ秒の範囲でしか時間を扱えません。
time.Duration
が扱える期間を日数に直すとすると、106751日までとなります(気になる方はPlaygroundのコードで試してみてください)。
対応方法
どんな値でも正しく計算するようにしたいのであれば、日数に相当する time.Duration
を加算するのではなく、time.Time
型の AddDate
メソッドを使って日数を加算すればよいです[1]。
year, month, date := createdAt.AddDate(0, 0, ValidityPeriodDays).Date()
ただ、現実的にはたとえば50万日後に期限を設定したいということはほとんどないと思うので、time.Duration
型が扱える範囲で ValiditiyPeriodDays
に入る値のバリデーションをしたうえで、実装はそのまま、でもいいと思います。
まとめ
- デカい期間を扱うときは
time.Duration
を使っていいか考えよう - 制約はだいたいドキュメントに書いてあるからちゃんと読もう
-
AddDateメソッドは日数を渡すだけでいい感じの日付表現に正規化してくれる(1月30日に5日足して1月35日、とかにはならず2月4日と解釈される)ので、とにかくn日後を計算してほしい!というパターンではうれしい ↩︎
Discussion