├── go.sum ├── go.mod ├── .gitmodules ├── gen ├── go.mod ├── testdata │ └── test.yml ├── go.sum ├── generate_internal_test.go └── generate.go ├── .github └── workflows │ └── ci.yml ├── README.md ├── .gitignore ├── LICENSE ├── holiday.go └── holiday_test.go /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/holiday-jp/holiday_jp-go 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "datasheet"] 2 | path = datasheet 3 | url = https://github.com/holiday-jp/holiday_jp.git 4 | -------------------------------------------------------------------------------- /gen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/holiday-jp/holiday_jp-go/gen 2 | 3 | go 1.15 4 | 5 | require gopkg.in/yaml.v2 v2.4.0 6 | -------------------------------------------------------------------------------- /gen/testdata/test.yml: -------------------------------------------------------------------------------- 1 | 1970-01-01: 2 | date: 1970-01-01 3 | week: 木 4 | week_en: Thursday 5 | name: 元日 6 | name_en: New Year's Day 7 | 1970-11-23: 8 | date: 1970-11-23 9 | week: 月 10 | week_en: Monday 11 | name: 勤労感謝の日 12 | name_en: Labor Thanksgiving Day 13 | -------------------------------------------------------------------------------- /gen/go.sum: -------------------------------------------------------------------------------- 1 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 2 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 3 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 4 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | job-test: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Set up Go 1.x 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: ^1.15 15 | 16 | - name: Check out source code 17 | uses: actions/checkout@v2 18 | 19 | - name: Run test 20 | run: | 21 | go build -v . 22 | go test -v . 23 | 24 | - name: Run generate test 25 | run: | 26 | go test -v . 27 | working-directory: ./gen 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # holiday_jp-go 2 | 3 | [![GoDoc](https://godoc.org/github.com/holiday-jp/holiday_jp-go?status.svg)](https://godoc.org/github.com/holiday-jp/holiday_jp-go) [![test](https://github.com/holiday-jp/holiday_jp-go/workflows/test/badge.svg)](https://github.com/holiday-jp/holiday_jp-go/actions) 4 | 5 | 🎌 Japanese holiday for Go 6 | 7 | ## Requirements 8 | * go 1.9 or later 9 | 10 | ## Installing 11 | 12 | ```bash 13 | $ go get github.com/holiday-jp/holiday_jp-go 14 | ``` 15 | 16 | ## Example 17 | 18 | ```go 19 | import "github.com/holiday-jp/holiday_jp-go" 20 | 21 | if holiday.IsHoliday(time.Now()) { 22 | fmt.Println("today is holiday!") 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Go ### 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | 17 | ### macOS ### 18 | *.DS_Store 19 | .AppleDouble 20 | .LSOverride 21 | 22 | # Icon must end with two \r 23 | Icon 24 | 25 | # Thumbnails 26 | ._* 27 | 28 | # Files that might appear in the root of a volume 29 | .DocumentRevisions-V100 30 | .fseventsd 31 | .Spotlight-V100 32 | .TemporaryItems 33 | .Trashes 34 | .VolumeIcon.icns 35 | .com.apple.timemachine.donotpresent 36 | 37 | # Directories potentially created on remote AFP share 38 | .AppleDB 39 | .AppleDesktop 40 | Network Trash Folder 41 | Temporary Items 42 | .apdisk 43 | 44 | vendor/ 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT license 2 | 3 | Copyright (c) 2018 aoi shirase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /gen/generate_internal_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestParse(t *testing.T) { 11 | data, err := parse(filepath.Join("testdata", "test.yml")) 12 | if err != nil { 13 | t.Fatalf("parse failed, %v", err) 14 | } 15 | 16 | want := map[string]map[string]string{ 17 | "1970-01-01": { 18 | "date": "1970-01-01", 19 | "week": "木", 20 | "week_en": "Thursday", 21 | "name": "元日", 22 | "name_en": "New Year's Day", 23 | }, 24 | "1970-11-23": { 25 | "date": "1970-11-23", 26 | "week": "月", 27 | "week_en": "Monday", 28 | "name": "勤労感謝の日", 29 | "name_en": "Labor Thanksgiving Day", 30 | }, 31 | } 32 | 33 | if !reflect.DeepEqual(data, want) { 34 | t.Errorf("unmatch result, want %+v, got %+v", want, data) 35 | } 36 | } 37 | 38 | func TestGenerate(t *testing.T) { 39 | in := map[string]map[string]string{ 40 | "1970-01-01": { 41 | "date": "1970-01-01", 42 | "week": "木", 43 | "week_en": "Thursday", 44 | "name": "元日", 45 | "name_en": "New Year's Day", 46 | }, 47 | "1970-11-23": { 48 | "date": "1970-11-23", 49 | "week": "月", 50 | "week_en": "Monday", 51 | "name": "勤労感謝の日", 52 | "name_en": "Labor Thanksgiving Day", 53 | }, 54 | } 55 | 56 | var buf strings.Builder 57 | 58 | if err := generate(in, &buf); err != nil { 59 | t.Fatalf("generate failed, %v", err) 60 | } 61 | 62 | want := ` 63 | // Code generated by gen/generate.go. DO NOT EDIT. 64 | // Generate from datasheet/holidays_detailed.yml 65 | 66 | package holiday 67 | 68 | // holidays holds the parse result of datasheet/holidays_detailed.yml 69 | var holidays = Holidays{ 70 | "1970-01-01": Holiday{ 71 | "date": "1970-01-01", 72 | "week": "木", 73 | "week_en": "Thursday", 74 | "name": "元日", 75 | "name_en": "New Year's Day", 76 | }, 77 | "1970-11-23": Holiday{ 78 | "date": "1970-11-23", 79 | "week": "月", 80 | "week_en": "Monday", 81 | "name": "勤労感謝の日", 82 | "name_en": "Labor Thanksgiving Day", 83 | }, 84 | } 85 | ` 86 | 87 | if buf.String() != want { 88 | t.Errorf("unmatch result, want %q, got %q", want, buf.String()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /gen/generate.go: -------------------------------------------------------------------------------- 1 | //go:generate go run . 2 | //go:generate gofmt -s -w ../datasheet.go 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "text/template" 13 | 14 | "gopkg.in/yaml.v2" 15 | ) 16 | 17 | // tpl template of datasheet.go 18 | var tpl = template.Must(template.New("datasheet").Parse(` 19 | // Code generated by gen/generate.go. DO NOT EDIT. 20 | // Generate from datasheet/holidays_detailed.yml 21 | 22 | package holiday 23 | 24 | // holidays holds the parse result of datasheet/holidays_detailed.yml 25 | var holidays = Holidays{ 26 | {{- range $key, $value := .Data }} 27 | "{{ $key }}": Holiday{ 28 | "date": "{{ $value.date }}", 29 | "week": "{{ $value.week }}", 30 | "week_en": "{{ $value.week_en }}", 31 | "name": "{{ $value.name }}", 32 | "name_en": "{{ $value.name_en }}", 33 | }, 34 | {{- end }} 35 | } 36 | `)) 37 | 38 | func main() { 39 | src := filepath.Join("..", "datasheet", "holidays_detailed.yml") 40 | out := filepath.Join("..", "datasheet.go") 41 | 42 | data, parseErr := parse(src) 43 | if parseErr != nil { 44 | log.Fatalln(parseErr.Error()) 45 | } 46 | 47 | file, fileErr := os.Create(out) 48 | if fileErr != nil { 49 | log.Fatalln(fileErr.Error()) 50 | } 51 | defer file.Close() 52 | 53 | if genErr := generate(data, file); genErr != nil { 54 | log.Fatalln(genErr.Error()) 55 | } 56 | } 57 | 58 | // parse yaml file and returns master data. 59 | func parse(filename string) (map[string]map[string]string, error) { 60 | file, fileErr := os.Open(filename) 61 | if fileErr != nil { 62 | return nil, fmt.Errorf("open file: %w", fileErr) 63 | } 64 | defer file.Close() 65 | 66 | var data = map[string]map[string]string{} 67 | 68 | if err := yaml.NewDecoder(file).Decode(&data); err != nil { 69 | return nil, fmt.Errorf("unmarshal: %w", err) 70 | } 71 | 72 | return data, nil 73 | } 74 | 75 | // generate code from template and master data. 76 | func generate(data map[string]map[string]string, w io.Writer) error { 77 | inventory := struct { 78 | Data map[string]map[string]string 79 | }{ 80 | Data: data, 81 | } 82 | 83 | if err := tpl.Execute(w, inventory); err != nil { 84 | return fmt.Errorf("template execute: %w", err) 85 | } 86 | 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /holiday.go: -------------------------------------------------------------------------------- 1 | package holiday 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | // Holiday holds holiday info. 10 | type Holiday map[string]string 11 | 12 | // New create a new Holiday. 13 | // If `t` is not holiday, when return nil and error. 14 | func New(t time.Time) (*Holiday, error) { 15 | holiday, ok := holidays[genDateStr(t)] 16 | if !ok { 17 | return nil, errors.New("There is no applicable holiday") 18 | } 19 | return &holiday, nil 20 | } 21 | 22 | // Date returns the day of the holiday. 23 | func (h *Holiday) Date() (*time.Time, error) { 24 | t, err := time.Parse(time.RFC3339, (*h)["date"]+" 00:00:00 +0000 UTC") 25 | if err != nil { 26 | return nil, err 27 | } 28 | return &t, nil 29 | } 30 | 31 | // Name returns name of the holiday. 32 | // It behaves in the same way as the String function. 33 | func (h *Holiday) Name() string { 34 | if h == nil { 35 | return "" 36 | } 37 | return (*h)["name"] 38 | } 39 | 40 | // NameEn returns english name of the holiday. 41 | func (h *Holiday) NameEn() string { 42 | if h == nil { 43 | return "" 44 | } 45 | return (*h)["name_en"] 46 | } 47 | 48 | func (h *Holiday) Week() string { 49 | if h == nil { 50 | return "" 51 | } 52 | return (*h)["week"] 53 | } 54 | 55 | func (h *Holiday) WeekEn() string { 56 | if h == nil { 57 | return "" 58 | } 59 | return (*h)["week_en"] 60 | } 61 | 62 | // String returns name of the holiday. 63 | func (h *Holiday) String() string { 64 | if h == nil { 65 | return "" 66 | } 67 | return (*h)["name"] 68 | } 69 | 70 | type Holidays map[string]Holiday 71 | 72 | func (h *Holidays) Names() []string { 73 | if h == nil { 74 | return nil 75 | } 76 | ret := make([]string, 0, len(*h)) 77 | for _, v := range *h { 78 | ret = append(ret, v.Name()) 79 | } 80 | return ret 81 | } 82 | 83 | func genDateStr(t time.Time) string { 84 | y, m, d := t.Date() 85 | return fmt.Sprintf("%d-%02d-%02d", y, m, d) 86 | } 87 | 88 | // IsHoliday function checks whether the specified date is a holiday. 89 | func IsHoliday(t time.Time) bool { 90 | _, ok := holidays[genDateStr(t)] 91 | return ok 92 | } 93 | 94 | // HolidayName function returns Holiday name string. 95 | func HolidayName(t time.Time) (string, error) { 96 | holiday, ok := holidays[genDateStr(t)] 97 | if !ok { 98 | return "", errors.New("There is no applicable holiday") 99 | } 100 | return holiday.Name(), nil 101 | } 102 | 103 | // Between acquires the holiday of the designated section. 104 | func Between(t0, t1 time.Time) Holidays { 105 | ret := Holidays{} 106 | for { 107 | if !t1.After(t0) && !t0.Equal(t1) { 108 | break 109 | } 110 | n, err := New(t0) 111 | if err != nil { 112 | t0 = t0.AddDate(0, 0, 1) 113 | continue 114 | } 115 | ret[(*n)["date"]] = *n 116 | t0 = t0.AddDate(0, 0, 1) 117 | } 118 | return ret 119 | } 120 | -------------------------------------------------------------------------------- /holiday_test.go: -------------------------------------------------------------------------------- 1 | package holiday 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestGenDateStr(t *testing.T) { 10 | tests := []struct { 11 | time time.Time 12 | want string 13 | }{ 14 | { 15 | time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), 16 | "1970-01-01", 17 | }, 18 | { 19 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 20 | "1970-01-01", 21 | }, 22 | { 23 | time.Date(1991, 12, 19, 0, 0, 0, 0, time.UTC), 24 | "1991-12-19", 25 | }, 26 | } 27 | 28 | for _, test := range tests { 29 | got := genDateStr(test.time) 30 | if test.want != got { 31 | t.Fatalf("want %q, but %q:", test.want, got) 32 | } 33 | } 34 | } 35 | 36 | func TestIsHoliday(t *testing.T) { 37 | tests := []struct { 38 | time time.Time 39 | want bool 40 | }{ 41 | { 42 | time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), 43 | true, 44 | }, 45 | { 46 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 47 | true, 48 | }, 49 | { 50 | time.Date(1970, 1, 2, 1, 1, 1, 1, time.UTC), 51 | false, 52 | }, 53 | { 54 | time.Date(1970, 1, 2, 0, 0, 0, 0, time.UTC), 55 | false, 56 | }, 57 | { 58 | // スポーツの日 59 | time.Date(2020, 7, 23, 0, 0, 0, 0, time.UTC), 60 | true, 61 | }, 62 | } 63 | 64 | for _, test := range tests { 65 | got := IsHoliday(test.time) 66 | if test.want != got { 67 | t.Fatalf("want '%t', but '%t':", test.want, got) 68 | } 69 | } 70 | } 71 | 72 | func TestHolidayName(t *testing.T) { 73 | tests := []struct { 74 | time time.Time 75 | want string 76 | err bool 77 | }{ 78 | { 79 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 80 | "元日", 81 | false, 82 | }, 83 | { 84 | time.Date(1970, 1, 2, 1, 1, 1, 1, time.UTC), 85 | "", 86 | true, 87 | }, 88 | } 89 | 90 | for _, test := range tests { 91 | got, err := HolidayName(test.time) 92 | if !test.err && err != nil { 93 | t.Fatalf("should not be error for %v but: %v", test.time, err) 94 | } 95 | if test.err && err == nil { 96 | t.Fatalf("should be error for %v but not:", test.time) 97 | } 98 | if got != "" && got != test.want { 99 | t.Fatalf("want %q, but %q:", test.want, got) 100 | } 101 | } 102 | } 103 | 104 | func TestBetween(t *testing.T) { 105 | tests := []struct { 106 | t0 time.Time 107 | t1 time.Time 108 | want Holidays 109 | }{ 110 | { 111 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 112 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 113 | Holidays{ 114 | "1970-01-01": Holiday{ 115 | "date": "1970-01-01", 116 | "week": "木", 117 | "week_en": "Thursday", 118 | "name": "元日", 119 | "name_en": "New Year's Day", 120 | }, 121 | }, 122 | }, 123 | { 124 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 125 | time.Date(1970, 1, 2, 1, 1, 1, 1, time.UTC), 126 | Holidays{ 127 | "1970-01-01": Holiday{ 128 | "date": "1970-01-01", 129 | "week": "木", 130 | "week_en": "Thursday", 131 | "name": "元日", 132 | "name_en": "New Year's Day", 133 | }, 134 | }, 135 | }, 136 | { 137 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 138 | time.Date(1970, 1, 14, 1, 1, 1, 1, time.UTC), 139 | Holidays{ 140 | "1970-01-01": Holiday{ 141 | "date": "1970-01-01", 142 | "week": "木", 143 | "week_en": "Thursday", 144 | "name": "元日", 145 | "name_en": "New Year's Day", 146 | }, 147 | }, 148 | }, 149 | { 150 | time.Date(1970, 1, 1, 1, 1, 1, 1, time.UTC), 151 | time.Date(1970, 1, 15, 1, 1, 1, 1, time.UTC), 152 | Holidays{ 153 | "1970-01-01": Holiday{ 154 | "date": "1970-01-01", 155 | "week": "木", 156 | "week_en": "Thursday", 157 | "name": "元日", 158 | "name_en": "New Year's Day", 159 | }, 160 | "1970-01-15": Holiday{ 161 | "date": "1970-01-15", 162 | "week": "木", 163 | "week_en": "Thursday", 164 | "name": "成人の日", 165 | "name_en": "Coming of Age Da", 166 | }, 167 | }, 168 | }, 169 | } 170 | 171 | for _, test := range tests { 172 | got := Between(test.t0, test.t1) 173 | 174 | if len(got) != len(test.want) { 175 | t.Fatalf("want %q, but %q:", test.want, got) 176 | } 177 | if reflect.DeepEqual(got, test.want) { 178 | for k, v := range got { 179 | vw := test.want[k] 180 | for k, v := range v { 181 | if vw[k] != v { 182 | t.Fatalf("\nwant %q,\nbut %q:", test.want, got) 183 | } 184 | } 185 | } 186 | } 187 | } 188 | } 189 | --------------------------------------------------------------------------------