satoshi.inoue's blog

備忘録を兼ねているので、薄い内容の投稿もあります。

go time.TimeのAddDateの落とし穴

golangのtime.TimeのAddDateが意図しない日時を返してきたので、備忘録として書きます。

AddDateの一般的な使い方

package main

import (
    "fmt"
    "time"
)

func main() {
    // 現在時刻を取得
    now := time.Now()
    fmt.Printf("%v (now)\n", now)
    // +1 year
    fmt.Printf("%v (+1 year)\n", now.AddDate(1, 0, 0))
    // +1 month
    fmt.Printf("%v (+1 month)\n", now.AddDate(0, 1, 0))
    // +1 day
    fmt.Printf("%v (+1 day)\n", now.AddDate(0, 0, 1))
}

// 2009-11-10 23:00:00 +0000 UTC m=+0.000000001 (now)
// 2010-11-10 23:00:00 +0000 UTC (+1 year)
// 2009-12-10 23:00:00 +0000 UTC (+1 month)
// 2009-11-11 23:00:00 +0000 UTC (+1 day)

The Go Playground

AddDateは、
第一引数にyearの増減値
第二引数にmonthの増減値
第三引数にdayの増減値
を設定する。

注意が必要な使い方

増減して取得する月の日数が、元の月の日数より少ないときに、月末を取得する場合

例) 元: 8/31、取得したい日: 9/30

package main

import (
    "fmt"
    "time"
)

func main() {
    location, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        return
    }
    date := time.Date(2018, 8, 31, 0, 0, 0, 0, location)
    fmt.Printf("%v (date)\n", date)
    // +1 month
    fmt.Printf("%v (+1 month)\n", date.AddDate(0, 1, 0))
}

// 2018-08-31 00:00:00 +0900 JST (date)
// 2018-10-01 00:00:00 +0900 JST (+1 month)

The Go Playground
9/30が取得できるかと思いきや、10/1が取得できてしまいます。

2月末を取得する場合

package main

import (
    "fmt"
    "time"
)

func main() {
    location, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        return
    }
    date := time.Date(2018, 1, 31, 0, 0, 0, 0, location)
    fmt.Printf("%v (date)\n", date)
    // +1 month
    fmt.Printf("%v (+1 month)\n", date.AddDate(0, 1, 0))
}

// 2018-01-31 00:00:00 +0900 JST (date)
// 2018-03-03 00:00:00 +0900 JST (+1 month)

The Go Playground
2/28が取得できるかと思いきや、3/3が取得できてしまいます。
閏年の場合は3/2が取得できます。

Appendix

うるう秒

前回挿入されたのは2017/1/1 09:00の直前

package main

import (
    "fmt"
    "time"
)

func main() {
    location, err := time.LoadLocation("Asia/Tokyo")
    if err != nil {
        return
    }
    date := time.Date(2017, 1, 1, 8, 59, 58, 0, location)
 
    for {
        date = date.Add(time. Millisecond * 100)
        fmt.Printf("%v\n", date.Add(time. Millisecond * 100))
        if date.After(time.Date(2017, 1, 1, 9, 0, 0, 0, location)) {
            break
        }
    }
}

// 2017-01-01 08:59:58.2 +0900 JST
// 2017-01-01 08:59:58.3 +0900 JST
// 2017-01-01 08:59:58.4 +0900 JST
// 2017-01-01 08:59:58.5 +0900 JST
// 2017-01-01 08:59:58.6 +0900 JST
// 2017-01-01 08:59:58.7 +0900 JST
// 2017-01-01 08:59:58.8 +0900 JST
// 2017-01-01 08:59:58.9 +0900 JST
// 2017-01-01 08:59:59 +0900 JST
// 2017-01-01 08:59:59.1 +0900 JST
// 2017-01-01 08:59:59.2 +0900 JST
// 2017-01-01 08:59:59.3 +0900 JST
// 2017-01-01 08:59:59.4 +0900 JST
// 2017-01-01 08:59:59.5 +0900 JST
// 2017-01-01 08:59:59.6 +0900 JST
// 2017-01-01 08:59:59.7 +0900 JST
// 2017-01-01 08:59:59.8 +0900 JST
// 2017-01-01 08:59:59.9 +0900 JST
// 2017-01-01 09:00:00 +0900 JST
// 2017-01-01 09:00:00.1 +0900 JST
// 2017-01-01 09:00:00.2 +0900 JST

The Go Playground
8:59:59が2回きています。