Goで月末日の時は増加減した月の月末日を返す

GoのtimeパッケージのAddDate()関数ですが、例えば10月31日に1ヶ月を足すと11月31日となりDateの正規化によって12月1日になります。

```Go
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Date(2019, 10, 31, 0, 0, 0, 0, time.Local)
	fmt.Println(t)
	fmt.Println(t.AddDate(0, 1, 0), "(Add 1 month)")
}

実行結果

2019-10-31 00:00:00 +0000 UTC
2019-12-01 00:00:00 +0000 UTC (Add 1 month)
```

そこで、月末日の時は増減した月の月末日、すなわち、10月31日に1ヶ月を足すと11月30日となる関数「AddMonth()」を考えてみました。（注意：横スクロールしてください）

- [Go Playgound へ](https://play.golang.org/p/8McHRpONKlC)

```Go
package main

import (
	"fmt"
	"time"
)

// AddMonth returns the time corresponding to adding the
// given number of months to t.
// For example, AddMonth(t, 2) applied to January 1, 2011
// (= t) returns March 1, 2011.
//
// AddMonth does not normalize its result in the same way
// that Date does, so, for example, adding one month to
// October 31 yields November 30.
func AddMonth(t time.Time, months int) time.Time {
	lastMonthDay := func(t time.Time) int {
		return time.Date(t.Year(), t.Month()+1, 1, 0, 0, 0, 0, t.Location()).AddDate(0, 0, -1).Day()
	}

	// Creating 1st Date from t and adding months because AddDate() normalizes t.
	am := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, months, 0)
	ad := lastMonthDay(am)

	if ld := lastMonthDay(t); t.Day() == ld || t.Day() > ad {
		return time.Date(am.Year(), am.Month(), ad, am.Hour(), am.Minute(), am.Second(), am.Nanosecond(), am.Location())
	}

	return t.AddDate(0, months, 0)
}

func main() {
	cnt := 14

	fmt.Println("First Days")
	ft := time.Date(2020, 2, 1, 0, 0, 0, 0, time.Local)
	for i := -cnt; i <= -1; i++ {
		fmt.Println(AddMonth(ft, i), "(Add", i, "month)")
	}
	fmt.Println(ft)
	for i := 1; i <= cnt; i++ {
		fmt.Println(AddMonth(ft, i), "(Add", i, "month)")
	}

	fmt.Println()

	fmt.Println("Last Days")
	lt := time.Date(2020, 2, 29, 0, 0, 0, 0, time.Local)
	for i := -cnt; i <= -1; i++ {
		fmt.Println(AddMonth(lt, i), "(Add", i, "month)")
	}
	fmt.Println(lt)
	for i := 1; i <= cnt; i++ {
		fmt.Println(AddMonth(lt, i), "(Add", i, "month)")
	}
}

実行結果

First Days
2018-12-01 00:00:00 +0000 UTC (Add -14 month)
2019-01-01 00:00:00 +0000 UTC (Add -13 month)
2019-02-01 00:00:00 +0000 UTC (Add -12 month)
2019-03-01 00:00:00 +0000 UTC (Add -11 month)
2019-04-01 00:00:00 +0000 UTC (Add -10 month)
2019-05-01 00:00:00 +0000 UTC (Add -9 month)
2019-06-01 00:00:00 +0000 UTC (Add -8 month)
2019-07-01 00:00:00 +0000 UTC (Add -7 month)
2019-08-01 00:00:00 +0000 UTC (Add -6 month)
2019-09-01 00:00:00 +0000 UTC (Add -5 month)
2019-10-01 00:00:00 +0000 UTC (Add -4 month)
2019-11-01 00:00:00 +0000 UTC (Add -3 month)
2019-12-01 00:00:00 +0000 UTC (Add -2 month)
2020-01-01 00:00:00 +0000 UTC (Add -1 month)
2020-02-01 00:00:00 +0000 UTC
2020-03-01 00:00:00 +0000 UTC (Add 1 month)
2020-04-01 00:00:00 +0000 UTC (Add 2 month)
2020-05-01 00:00:00 +0000 UTC (Add 3 month)
2020-06-01 00:00:00 +0000 UTC (Add 4 month)
2020-07-01 00:00:00 +0000 UTC (Add 5 month)
2020-08-01 00:00:00 +0000 UTC (Add 6 month)
2020-09-01 00:00:00 +0000 UTC (Add 7 month)
2020-10-01 00:00:00 +0000 UTC (Add 8 month)
2020-11-01 00:00:00 +0000 UTC (Add 9 month)
2020-12-01 00:00:00 +0000 UTC (Add 10 month)
2021-01-01 00:00:00 +0000 UTC (Add 11 month)
2021-02-01 00:00:00 +0000 UTC (Add 12 month)
2021-03-01 00:00:00 +0000 UTC (Add 13 month)
2021-04-01 00:00:00 +0000 UTC (Add 14 month)

Last Days
2018-12-31 00:00:00 +0000 UTC (Add -14 month)
2019-01-31 00:00:00 +0000 UTC (Add -13 month)
2019-02-28 00:00:00 +0000 UTC (Add -12 month)
2019-03-31 00:00:00 +0000 UTC (Add -11 month)
2019-04-30 00:00:00 +0000 UTC (Add -10 month)
2019-05-31 00:00:00 +0000 UTC (Add -9 month)
2019-06-30 00:00:00 +0000 UTC (Add -8 month)
2019-07-31 00:00:00 +0000 UTC (Add -7 month)
2019-08-31 00:00:00 +0000 UTC (Add -6 month)
2019-09-30 00:00:00 +0000 UTC (Add -5 month)
2019-10-31 00:00:00 +0000 UTC (Add -4 month)
2019-11-30 00:00:00 +0000 UTC (Add -3 month)
2019-12-31 00:00:00 +0000 UTC (Add -2 month)
2020-01-31 00:00:00 +0000 UTC (Add -1 month)
2020-02-29 00:00:00 +0000 UTC
2020-03-31 00:00:00 +0000 UTC (Add 1 month)
2020-04-30 00:00:00 +0000 UTC (Add 2 month)
2020-05-31 00:00:00 +0000 UTC (Add 3 month)
2020-06-30 00:00:00 +0000 UTC (Add 4 month)
2020-07-31 00:00:00 +0000 UTC (Add 5 month)
2020-08-31 00:00:00 +0000 UTC (Add 6 month)
2020-09-30 00:00:00 +0000 UTC (Add 7 month)
2020-10-31 00:00:00 +0000 UTC (Add 8 month)
2020-11-30 00:00:00 +0000 UTC (Add 9 month)
2020-12-31 00:00:00 +0000 UTC (Add 10 month)
2021-01-31 00:00:00 +0000 UTC (Add 11 month)
2021-02-28 00:00:00 +0000 UTC (Add 12 month)
2021-03-31 00:00:00 +0000 UTC (Add 13 month)
2021-04-30 00:00:00 +0000 UTC (Add 14 month)
```

月末日に関しては、12ヶ月の月末日の並びから

```
...,31|31,28&29,31,30,31,30,31,31,30,31,30,31|31,...
```

30日が連続することがないことを考慮して、以下のケースで動作確認しました。（基：基準日の月末日）

```
-1 基 +1
-- -- --
31,28,31
31,29,31
31,30,31
28,31,30
29,31,30
30,31,30
30,31,31
31,31,28
31,31,29
31,31,30
```

例）基準日が2019年2月28日で前後2ヶ月の月末日を出力するmain()関数内のコード。

```Go
	tl := time.Date(2019, 2, 28, 0, 0, 0, 0, time.Local)
	fmt.Println(AddMonth(tl, -2), "(Add -2 month)")
	fmt.Println(AddMonth(tl, -1), "(Add -1 month)")
	fmt.Println(tl)
	fmt.Println(AddMonth(tl, 1), "(Add 1 month)")
	fmt.Println(AddMonth(tl, 2), "(Add 2 month)")
```

2020.05.02 11月30日の時に-1ヶ月すると10月31日ではなく10月30日になっていた不具合を修正しました。

2020.05.03 2020年3月31日の時に-1ヶ月すると2月29日ではなく3月1日になっていた不具合を修正しました。