関連記事
Goメモ-153 (Goでのファイル I/O のやり方まとめ)(Open,Write,Append,CSV,TSV,BINARY,XML,JSON,YAML,TOML) - いろいろ備忘録日記
Goメモ-300 (バイナリを構造体にパック・アンパック) - いろいろ備忘録日記
概要
以下、自分用のメモです。
国民の祝日って、次に何があるかって覚えてますか?私は全然覚えられません。。
手帳をめくったり、カレンダー見たりしたらすぐ確認できるのですが、プログラムで出力してもらったら楽かもしれません。(多分手帳の方が早いですw)
国民の祝日に関しては、内閣府からCSVファイルが公開されています。
これを使って、ちょっと遊ぶプログラムを書いてみましょう。
こういうの手習いの型にちょうどいいんですよね。
試してみる
上のURLで公開されているCSVファイルは、2列構成でShiftJISでエンコーディングされています。改行コードは CRLF 。
これを読み込んで、本日以降の祝日を出力するプログラムを作ってみます。
必要となる処理は
- ファイルを読み込む(これは標準入力から与えるとする)
- ShiftJISで読み込む
- CSVとして解釈して処理
- 本日以降のデータのみに絞り込み
- 出力(これは標準出力に出力するとする)
ですね。
package main import ( "bufio" "encoding/csv" "errors" "fmt" "io" "log" "os" "time" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) type Holiday struct { Date time.Time Name string } func (me Holiday) String() string { return fmt.Sprintf("%s (%s)", me.Date.Format("2006-01-02"), me.Name) } func main() { if err := run(); err != nil { log.Fatal(err) } } func run() error { // // 標準入力から読み取ったデータを 国民の祝日 CSV ファイル として解釈し // 本日以降の祝日を出力する. // // (*) 国民の祝日 CSV ファイルは、エンコーディングが Shift-JIS となっている // (*) CSVファイルのURLは、Taskfile.yml を参照 // // Reader var ( bufReader = bufio.NewReader(os.Stdin) decoder = japanese.ShiftJIS.NewDecoder() sjisReader = transform.NewReader(bufReader, decoder) csvReader = csv.NewReader(sjisReader) ) // Channels var ( dataCh = make(chan Holiday) resultCh = make(chan Holiday) errCh = make(chan error) ) defer close(errCh) // 読み込みとフィルタリング開始 go read(csvReader, dataCh, errCh) go filter(dataCh, today(), resultCh, errCh) // 結果出力 LOOP: for { select { case err := <-errCh: return err case result, ok := <-resultCh: if !ok { break LOOP } fmt.Println(result) } } return nil } func today() time.Time { now := time.Now() return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) } func read(in *csv.Reader, outCh chan<- Holiday, errCh chan<- error) { defer close(outCh) // 一行目はヘッダなので読み飛ばし _, err := in.Read() if err != nil { errCh <- err return } for { row, err := in.Read() if err != nil { if errors.Is(err, io.EOF) { break } errCh <- err break } // 日付のフォーマットは yyyy/M/d となっている t, err := time.Parse("2006/1/2", row[0]) if err != nil { errCh <- err break } outCh <- Holiday{Date: t, Name: row[1]} } } func filter(inCh <-chan Holiday, sentinel time.Time, outCh chan<- Holiday, errCh chan<- error) { defer close(outCh) for v := range inCh { if v.Date.Compare(sentinel) < 0 { continue } outCh <- v } }
タスクファイル は、以下。
version: "3" vars: DATA_FILE: syukujitsu.csv DATA_URL: https://www8.cao.go.jp/chosei/shukujitsu/{{.DATA_FILE}} tasks: default: cmds: - task: get-holiday-csv - task: run get-holiday-csv: cmds: # https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html - cmd: if ! [ -f {{.DATA_FILE}} ]; then wget --quiet {{.DATA_URL}}; fi run: cmds: - time go run . < ./{{.DATA_FILE}} clean: cmds: - rm -f ./{{.DATA_FILE}}
プログラム実行の度にファイルを取りに行ったりしたら、なんか怒られそうな感じがしたりしなかったりするので、ファイルが存在しない場合だけ wget するようにしています。
実行すると以下のようになります。
$ date "+%Y-%m-%d" 2023-03-27 $ task task: [get-holiday-csv] if ! [ -f syukujitsu.csv ]; then wget --quiet https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv; fi task: [run] time go run . < ./syukujitsu.csv 2023-04-29 (昭和の日) 2023-05-03 (憲法記念日) 2023-05-04 (みどりの日) 2023-05-05 (こどもの日) 2023-07-17 (海の日) 2023-08-11 (山の日) 2023-09-18 (敬老の日) 2023-09-23 (秋分の日) 2023-10-09 (スポーツの日) 2023-11-03 (文化の日) 2023-11-23 (勤労感謝の日) 2024-01-01 (元日) 2024-01-08 (成人の日) 2024-02-11 (建国記念の日) 2024-02-12 (休日) 2024-02-23 (天皇誕生日) 2024-03-20 (春分の日) 2024-04-29 (昭和の日) 2024-05-03 (憲法記念日) 2024-05-04 (みどりの日) 2024-05-05 (こどもの日) 2024-05-06 (休日) 2024-07-15 (海の日) 2024-08-11 (山の日) 2024-08-12 (休日) 2024-09-16 (敬老の日) 2024-09-22 (秋分の日) 2024-09-23 (休日) 2024-10-14 (スポーツの日) 2024-11-03 (文化の日) 2024-11-04 (休日) 2024-11-23 (勤労感謝の日) real 0m0.150s user 0m0.000s sys 0m0.000s
参考情報
bufio package - bufio - Go Packages
japanese package - golang.org/x/text/encoding/japanese - Go Packages
csv package - encoding/csv - Go Packages
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。