Go言語で今月から標準入力で受けたyyyy-mmまでの各月の月初と月末を一覧表示する簡単なコマンドラインアプリを作ってみた


# Go言語で今月から標準入力で受けたyyyy-mmまでの各月の月初と月末を一覧表示する簡単なコマンドラインアプリを作ってみた
Goで標準入力と [time package](https://golang.org/pkg/time/)を用いて、日付に関する簡単なコマンドラインアプリを作ってみた。
月初や月末を求めるようなことは日々の業務でも割と出くわすのではないだろうか。

「ここんところ、こうした方が良いだろ」というアドバイスがあれば、コメントをいただけると幸いです。


## 流れ
### main
1. 標準入力を受け取る
2. 標準入力で受け取った値が不適切だったら、再度入力させる処理を行う
3. 標準入力から受け取った文字列をyyyyとmmに分割
4. 標準入力から受け取った文字列をyyyyとmmを引数にShowSpecificTermListを呼び出し

### ShowSpecificTermList
1. 現在の時刻を取得
2. 現在の時刻を元に基準となるDateを作成
3. 標準入力から受け取った文字列をyyyyとmmを元に終了の条件となるDateを設定
4. for文で月初は、基準となるDateからループごとに-1月する
5. for文で月末は、基準となるDateからループごとに-1月&&-1日する
6. 終了条件の日付以前の日にちまで遡ったら、breakしてループを抜ける

月末で、基準日(日付が1日)から1日を引いていることがポイント


## 実装してみた
各処理の詳細はコード中にコメントとして記述しています。

```go
package main

import (
	"fmt"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"
)

func main() {
	// 遡る日付をyyyy-mmで入力を受け付ける
	fmt.Println("いつまで遡りますか?\nyyyy-mmで入力してください。（現在と同じかそれよりも前を指定してください。）")

	var input string

	var limitYear int

	var limitMonth int

	// 適切な入力がなされない限りループ
	for {
		// 標準入力をスキャン
		fmt.Scan(&input)

		// 正規表現のチェック
		if b, err := regexp.MatchString(`^(\d{4})-(0[1-9]|1[0-2])$`, input); !b || err != nil {
			fmt.Println("入力いただいた文字列が不適切です。 yyyy-mmで入力してください。")
		} else {
			// 入力された文字列を"-"で分割
			s := strings.Split(input, "-")
			// 遡る西暦の限界値
			limitYear, err = strconv.Atoi(s[0])
			if err != nil {
				log.Printf("can not strconv.Atoi :%v \n", err)
			}

			// 遡る月の限界値
			limitMonth, err = strconv.Atoi(s[1])
			if err != nil {
				log.Printf("can not strconv.Atoi :%v \n", err)
			}

			// 入力で受け付けた日付をDateに変換
			inputDate := time.Date(limitYear, time.Month(limitMonth), 0, 0, 0, 0, 0, time.UTC)

			// 現在よりも未来の日付にならないようにする
			if !inputDate.After(time.Now()) {
				// 適切な値が入力されたら、ループを抜ける
				break
			}

			fmt.Println("現在と同じかそれよりも前を指定してください。")
		}
	}

	ShowSpecificTermList(limitYear, limitMonth)
}

// 現在の時刻から指定した日付まで遡った月初と月末の日付の一覧を表示する
func ShowSpecificTermList(limitYear, limitMonth int) {
	// 現在の時刻を取得
	now := time.Now()
	// 基準となる日付を設定
	criterionDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)

	// 終了の条件となる日付を設定
	finDate := time.Date(limitYear, time.Month(limitMonth), 30, 0, 0, 0, 0, time.UTC)

	i := 0
	for {
		// 月初を設定
		beginningOfTheMonth := criterionDate.AddDate(0, -i, 0)
		// 月末を設定
		endOfTheMonth := criterionDate.AddDate(0, -i+1, -1)

		fmt.Printf("月初:%v", beginningOfTheMonth)
		fmt.Printf("月末:%v\n", endOfTheMonth)

		// 終了条件の日付以前の日にちまで遡ったら、breakしてループを抜ける
		if beginningOfTheMonth.Before(finDate) {
			fmt.Println("終了")
			break
		}

		// インクリメント
		i++
	}
}

```

## 実行結果

```
いつまで遡りますか?
yyyy-mmで入力してください。（現在と同じかそれよりも前を指定してください。）
2020-03 // <= 入力
現在と同じかそれよりも前を指定してください
2016-01 // 入力
月初:2017-06-01 12:00:00 +0000 UTC月末:2017-06-30 12:00:00 +0000 UTC
月初:2017-05-01 12:00:00 +0000 UTC月末:2017-05-31 12:00:00 +0000 UTC
月初:2017-04-01 12:00:00 +0000 UTC月末:2017-04-30 12:00:00 +0000 UTC
月初:2017-03-01 12:00:00 +0000 UTC月末:2017-03-31 12:00:00 +0000 UTC
月初:2017-02-01 12:00:00 +0000 UTC月末:2017-02-28 12:00:00 +0000 UTC
月初:2017-01-01 12:00:00 +0000 UTC月末:2017-01-31 12:00:00 +0000 UTC
月初:2016-12-01 12:00:00 +0000 UTC月末:2016-12-31 12:00:00 +0000 UTC
月初:2016-11-01 12:00:00 +0000 UTC月末:2016-11-30 12:00:00 +0000 UTC
月初:2016-10-01 12:00:00 +0000 UTC月末:2016-10-31 12:00:00 +0000 UTC
月初:2016-09-01 12:00:00 +0000 UTC月末:2016-09-30 12:00:00 +0000 UTC
月初:2016-08-01 12:00:00 +0000 UTC月末:2016-08-31 12:00:00 +0000 UTC
月初:2016-07-01 12:00:00 +0000 UTC月末:2016-07-31 12:00:00 +0000 UTC
月初:2016-06-01 12:00:00 +0000 UTC月末:2016-06-30 12:00:00 +0000 UTC
月初:2016-05-01 12:00:00 +0000 UTC月末:2016-05-31 12:00:00 +0000 UTC
月初:2016-04-01 12:00:00 +0000 UTC月末:2016-04-30 12:00:00 +0000 UTC
月初:2016-03-01 12:00:00 +0000 UTC月末:2016-03-31 12:00:00 +0000 UTC
月初:2016-02-01 12:00:00 +0000 UTC月末:2016-02-29 12:00:00 +0000 UTC
月初:2016-01-01 12:00:00 +0000 UTC月末:2016-01-31 12:00:00 +0000 UTC
終了


```

## time.AddDate(years int, months int, days int) Timeの使い方
time.AddDateでは、引数にyearsを与えれば「年」が、monthを与えれば「月」が、daysを与えれば「日」が加算されたTimeを返してくれる。
今回は、これをちょっと応用して、monthやdaysの引数にマイナスの値を与えることで、過去のTimeを作成している。


##　参考にさせていただいたサイト
[time package](https://golang.org/pkg/time/)
[regexp package](https://golang.org/pkg/regexp/)
http://qiita.com/peketamin/items/ccc881b46f4cc56e4aa9

※ ブログでも同一記事を投稿している
[Go言語で今月から標準入力で受けたyyyy-mmまでの各月の月初と月末を一覧表示する簡単なコマンドラインアプリを作ってみた](http://www.sekky0905.com/entry/2017/06/06/Go%E8%A8%80%E8%AA%9E%E3%81%A7%E4%BB%8A%E6%9C%88%E3%81%8B%E3%82%89%E6%A8%99%E6%BA%96%E5%85%A5%E5%8A%9B%E3%81%A7%E5%8F%97%E3%81%91%E3%81%9Fyyyy-mm%E3%81%BE%E3%81%A7%E3%81%AE%E5%90%84%E6%9C%88%E3%81%AE%E6%9C%88)