いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-301 (内閣府が提供している国民の祝日CSVファイルで遊ぶ)

関連記事

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のおすすめ書籍

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。