├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── lint.yml │ └── vulns.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Taskfile.yml ├── calendarid.go ├── dependency.png ├── dot-config.toml ├── ecode.go ├── events.go ├── example_test.go ├── go.mod ├── go.sum ├── jdn ├── jdn.go └── jdn_test.go ├── sample ├── sample0.go ├── sample0b.go ├── sample1.go ├── sample2.go ├── sample3.go └── sample4.go ├── source.go ├── value ├── date.go ├── date_test.go ├── era.go └── era_test.go └── zodiac ├── zodiac.go └── zodiac_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 4 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | 11 | [*.go] 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | trim_trailing_whitespace = true 22 | 23 | [*.toml] 24 | indent_style = space 25 | indent_size = 2 26 | trim_trailing_whitespace = true 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 20 * * 0' 16 | 17 | jobs: 18 | CodeQL-Build: 19 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 20 | runs-on: ubuntu-latest 21 | 22 | permissions: 23 | # required for all workflows 24 | security-events: write 25 | 26 | # only required for workflows in private repositories 27 | actions: read 28 | contents: read 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v3 33 | 34 | # Initializes the CodeQL tools for scanning. 35 | - name: Initialize CodeQL 36 | uses: github/codeql-action/init@v2 37 | # Override language selection by uncommenting this and choosing your languages 38 | with: 39 | languages: go 40 | 41 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 42 | # If this step fails, then you should remove it and run the build manually (see below). 43 | - name: Autobuild 44 | uses: github/codeql-action/autobuild@v2 45 | 46 | # ℹ️ Command-line programs to run using the OS shell. 47 | # 📚 https://git.io/JvXDl 48 | 49 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 50 | # three lines and modify them (or add more) to build your code if your 51 | # project uses a compiled language 52 | 53 | #- run: | 54 | # make bootstrap 55 | # make release 56 | 57 | - name: Perform CodeQL Analysis 58 | uses: github/codeql-action/analyze@v2 59 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 11 | # pull-requests: read 12 | jobs: 13 | golangci: 14 | name: lint 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-go@v3 19 | with: 20 | go-version-file: 'go.mod' 21 | - name: golangci-lint 22 | uses: golangci/golangci-lint-action@v3 23 | with: 24 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 25 | version: latest 26 | 27 | # Optional: working directory, useful for monorepos 28 | # working-directory: somedir 29 | 30 | # Optional: golangci-lint command line arguments. 31 | args: --enable gosec --exclude=G115 32 | 33 | # Optional: show only new issues if it's a pull request. The default value is `false`. 34 | # only-new-issues: true 35 | 36 | # Optional: if set to true then the all caching functionality will be complete disabled, 37 | # takes precedence over all other caching options. 38 | # skip-cache: true 39 | 40 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 41 | # skip-pkg-cache: true 42 | 43 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 44 | # skip-build-cache: true 45 | - name: testing 46 | run: go test -shuffle on ./... 47 | - name: install govulncheck 48 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 49 | - name: running govulncheck 50 | run: govulncheck ./... 51 | -------------------------------------------------------------------------------- /.github/workflows/vulns.yml: -------------------------------------------------------------------------------- 1 | name: vulns 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | jobs: 8 | vulns: 9 | name: Vulnerability scanner 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-go@v3 14 | with: 15 | go-version-file: 'go.mod' 16 | - name: install depm 17 | run: go install github.com/goark/depm@latest 18 | - name: WriteGoList 19 | run: depm list --json > go.list 20 | - name: Nancy 21 | uses: sonatype-nexus-community/nancy-github-action@main 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # Other files and directories 18 | *.bak 19 | tmp/ 20 | .task/ 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.14.x" 5 | 6 | env: 7 | global: 8 | - GO111MODULE=on 9 | 10 | install: 11 | - go mod download 12 | 13 | script: 14 | - go test ./... 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [koyomi] -- 日本のこよみ 2 | 3 | [![check vulns](https://github.com/goark/koyomi/workflows/vulns/badge.svg)](https://github.com/goark/koyomi/actions) 4 | [![lint status](https://github.com/goark/koyomi/workflows/lint/badge.svg)](https://github.com/goark/koyomi/actions) 5 | [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/goark/koyomi/master/LICENSE) 6 | [![GitHub release](https://img.shields.io/github/release/goark/koyomi.svg)](https://github.com/goark/koyomi/releases/latest) 7 | 8 | **Migrated repository to [github.com/goark/koyomi][koyomi]** 9 | 10 | 「[国立天文台 天文情報センター 暦計算室](http://eco.mtk.nao.ac.jp/koyomi/)」より日本の暦情報を取得する [Go 言語]用パッケージです。 Google Calendar を経由して取得しています。 11 | 12 | 取得可能な情報は以下の通りです。 13 | 14 | ```go 15 | const ( 16 | Holiday CalendarID = iota + 1 //国民の祝日および休日 17 | MoonPhase //朔弦望 18 | SolarTerm //二十四節気・雑節 19 | Eclipse //日食・月食・日面経過 20 | Planet //惑星現象 21 | ) 22 | ``` 23 | 24 | ## 簡単な使い方 25 | 26 | ### CSV 形式で出力 27 | 28 | ```go 29 | package main 30 | 31 | import ( 32 | "bytes" 33 | "io" 34 | "os" 35 | "time" 36 | 37 | "github.com/goark/koyomi" 38 | "github.com/goark/koyomi/value" 39 | ) 40 | 41 | func main() { 42 | start, _ := value.DateFrom("2019-05-01") 43 | end := value.NewDate(time.Date(2019, time.May, 31, 0, 0, 0, 0, value.JST)) 44 | td, err := os.MkdirTemp(os.TempDir(), "sample") 45 | if err != nil { 46 | return 47 | } 48 | defer func() { _ = os.RemoveAll(td) }() 49 | k, err := koyomi.NewSource( 50 | koyomi.WithCalendarID(koyomi.Holiday, koyomi.SolarTerm), 51 | koyomi.WithStartDate(start), 52 | koyomi.WithEndDate(end), 53 | koyomi.WithTempDir(td), 54 | ).Get() 55 | if err != nil { 56 | return 57 | } 58 | 59 | csv, err := k.EncodeCSV() 60 | if err != nil { 61 | return 62 | } 63 | io.Copy(os.Stdout, bytes.NewReader(csv)) 64 | } 65 | ``` 66 | 67 | ### テンプレートを指定して出力 68 | 69 | ```go 70 | package main 71 | 72 | import ( 73 | "os" 74 | "text/template" 75 | "time" 76 | 77 | "github.com/goark/koyomi" 78 | "github.com/goark/koyomi/value" 79 | ) 80 | 81 | func main() { 82 | start, _ := value.DateFrom("2019-05-01") 83 | end := value.NewDate(time.Date(2019, time.May, 31, 0, 0, 0, 0, value.JST)) 84 | td, err := os.MkdirTemp(os.TempDir(), "sample") 85 | if err != nil { 86 | return 87 | } 88 | defer func() { _ = os.RemoveAll(td) }() 89 | k, err := koyomi.NewSource( 90 | koyomi.WithCalendarID(koyomi.Holiday, koyomi.SolarTerm), 91 | koyomi.WithStartDate(start), 92 | koyomi.WithEndDate(end), 93 | koyomi.WithTempDir(td), 94 | ).Get() 95 | if err != nil { 96 | return 97 | } 98 | 99 | myTemplate := `| 日付 | 曜日 | 内容 | 100 | | ---- |:----:| ---- | 101 | {{ range . }}| {{ .Date.StringJp }} | {{ .Date.WeekdayJp.ShortStringJp }} | {{ .Title }} | 102 | {{ end -}}` 103 | 104 | t, err := template.New("").Parse(myTemplate) 105 | if err != nil { 106 | return 107 | } 108 | if err := t.Execute(os.Stdout, k.Events()); err != nil { 109 | return 110 | } 111 | //Output: 112 | //| 日付 | 曜日 | 内容 | 113 | //| ---- |:----:| ---- | 114 | //| 2019年5月1日 | 水 | 休日 (天皇の即位の日) | 115 | //| 2019年5月2日 | 木 | 休日 | 116 | //| 2019年5月2日 | 木 | 八十八夜 | 117 | //| 2019年5月3日 | 金 | 憲法記念日 | 118 | //| 2019年5月4日 | 土 | みどりの日 | 119 | //| 2019年5月5日 | 日 | こどもの日 | 120 | //| 2019年5月6日 | 月 | 休日 | 121 | //| 2019年5月6日 | 月 | 立夏 | 122 | //| 2019年5月21日 | 火 | 小満 | 123 | } 124 | ``` 125 | 126 | ## おまけ機能 127 | 128 | ### 西暦⇔和暦 変換 129 | 130 | 元号を含む和暦と西暦との変換を行います。 131 | 元号は以下のものに対応しています(グレゴリオ暦採用以降)。 132 | 133 | | 元号 | 起点 | 134 | | ---------------- | -------------- | 135 | | 明治(改暦以降) | 1873年1月1日 | 136 | | 大正 | 1912年7月30日 | 137 | | 昭和 | 1926年12月25日 | 138 | | 平成 | 1989年1月8日 | 139 | | 令和 | 2019年5月1日 | 140 | 141 | #### 西暦から和暦への変換 142 | 143 | ```go 144 | package main 145 | 146 | import ( 147 | "flag" 148 | "fmt" 149 | "os" 150 | "strconv" 151 | "time" 152 | 153 | "github.com/goark/koyomi/value" 154 | ) 155 | 156 | func main() { 157 | flag.Parse() 158 | argsStr := flag.Args() 159 | tm := time.Now() 160 | if len(argsStr) > 0 { 161 | if len(argsStr) < 3 { 162 | fmt.Fprintln(os.Stderr, "年月日を指定してください") 163 | return 164 | } 165 | args := make([]int, 3) 166 | for i := 0; i < 3; i++ { 167 | num, err := strconv.Atoi(argsStr[i]) 168 | if err != nil { 169 | fmt.Fprintln(os.Stderr, err) 170 | return 171 | } 172 | args[i] = num 173 | } 174 | tm = time.Date(args[0], time.Month(args[1]), args[2], 0, 0, 0, 0, time.Local) 175 | } 176 | te := value.NewDate(tm) 177 | n, y := te.YearEraString() 178 | if len(n) == 0 { 179 | fmt.Fprintln(os.Stderr, "正しい年月日を指定してください") 180 | return 181 | } 182 | fmt.Printf("%s%s%d月%d日\n", n, y, te.Month(), te.Day()) 183 | } 184 | ``` 185 | 186 | これを実行すると以下のような結果になります。 187 | 188 | ``` 189 | $ go run sample/sample1.go 2019 4 30 190 | 平成31年4月30日 191 | 192 | $ go run sample/sample1.go 2019 5 1 193 | 令和元年5月1日 194 | ``` 195 | 196 | #### 和暦から西暦への変換 197 | 198 | ```go 199 | package main 200 | 201 | import ( 202 | "flag" 203 | "fmt" 204 | "os" 205 | "strconv" 206 | "time" 207 | 208 | "github.com/goark/koyomi/value" 209 | ) 210 | 211 | func main() { 212 | flag.Parse() 213 | argsStr := flag.Args() 214 | 215 | if len(argsStr) < 4 { 216 | fmt.Fprintln(os.Stderr, "元号 年 月 日 を指定してください") 217 | return 218 | } 219 | name := argsStr[0] 220 | args := make([]int, 3) 221 | for i := 0; i < 3; i++ { 222 | num, err := strconv.Atoi(argsStr[i+1]) 223 | if err != nil { 224 | fmt.Fprintln(os.Stderr, err) 225 | return 226 | } 227 | args[i] = num 228 | } 229 | te := value.NewDateEra(value.EraName(name), args[0], time.Month(args[1]), args[2]) 230 | fmt.Println(te.Format("西暦2006年1月2日")) 231 | } 232 | ``` 233 | 234 | これを実行すると以下のような結果になります。 235 | 236 | ``` 237 | $ go run sample/sample2.go 平成 31 4 30 238 | 西暦2019年4月30日 239 | 240 | $ go run sample/sample2.go 令和 1 5 1 241 | 西暦2019年5月1日 242 | 243 | $ go run sample/sample2.go 昭和 100 1 1 244 | 西暦2025年1月1日 245 | ``` 246 | 247 | ### 十干十二支を数え上げる 248 | 249 | ```go 250 | package main 251 | 252 | import ( 253 | "flag" 254 | "fmt" 255 | "os" 256 | 257 | "github.com/goark/koyomi/value" 258 | "github.com/goark/koyomi/zodiac" 259 | ) 260 | 261 | func main() { 262 | flag.Parse() 263 | args := flag.Args() 264 | if len(args) < 1 { 265 | fmt.Fprintln(os.Stderr, os.ErrInvalid) 266 | return 267 | } 268 | for _, s := range args { 269 | t, err := value.DateFrom(s) 270 | if err != nil { 271 | fmt.Fprintln(os.Stderr, err) 272 | continue 273 | } 274 | kan, shi := zodiac.ZodiacYearNumber(t.Year()) 275 | fmt.Printf("Year %v is %v%v (Eho: %v)\n", t.Year(), kan, shi, kan.DirectionJp()) 276 | kan, shi = zodiac.ZodiacDayNumber(t) 277 | fmt.Printf("Day %v is %v%v\n", t.Format("2006-01-02"), kan, shi) 278 | } 279 | } 280 | ``` 281 | 282 | これを実行すると以下のような結果になります。 283 | 284 | ``` 285 | $ go run sample/sample3.go 2021-07-28 286 | Year 2021 is 辛丑 (Eho: 南南東微南) 287 | Day 2021-07-28 is 丁丑 288 | ``` 289 | 290 | ### ユリウス日 291 | 292 | ```go 293 | package main 294 | 295 | import ( 296 | "flag" 297 | "fmt" 298 | "os" 299 | 300 | "github.com/goark/koyomi/jdn" 301 | "github.com/goark/koyomi/value" 302 | ) 303 | 304 | func main() { 305 | flag.Parse() 306 | args := flag.Args() 307 | if len(args) < 1 { 308 | fmt.Fprintln(os.Stderr, os.ErrInvalid) 309 | return 310 | } 311 | for _, s := range args { 312 | t, err := value.DateFrom(s) 313 | if err != nil { 314 | fmt.Fprintln(os.Stderr, err) 315 | continue 316 | } 317 | j := jdn.GetJDN(t.Time) 318 | fmt.Printf("Julian Day Number of %v is %v\n", t.Format("2006-01-02"), j) 319 | } 320 | } 321 | ``` 322 | 323 | これを実行すると以下のような結果になります。 324 | 325 | ``` 326 | $ go run sample/sample4.go 2023-02-25 327 | Julian Day Number of 2023-02-25 is 2460000 328 | ``` 329 | 330 | ## Modules Requirement Graph 331 | 332 | [![dependency.png](./dependency.png)](./dependency.png) 333 | 334 | ## Links 335 | 336 | - [国立天文台 天文情報センター 暦計算室](https://eco.mtk.nao.ac.jp/koyomi/) 337 | - [今月のこよみ powered by Google Calendar - 国立天文台暦計算室](https://eco.mtk.nao.ac.jp/koyomi/cande/calendar.html) 338 | - [ユリウス日について - 国立天文台暦計算室](https://eco.mtk.nao.ac.jp/koyomi/topics/html/topics2023_1.html) 339 | - [日本の暦情報を取得するパッケージを作ってみた — リリース情報 | text.Baldanders.info](https://text.baldanders.info/release/2020/05/koyomi/) 340 | 341 | [Go 言語]: https://go.dev/ "The Go Programming Language" 342 | [koyomi]: https://github.com/goark/koyomi "goark/koyomi: 日本のこよみ" 343 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | tasks: 4 | default: 5 | cmds: 6 | - task: prepare 7 | - task: test 8 | - task: nancy 9 | - task: graph 10 | 11 | test: 12 | desc: Test and lint. 13 | cmds: 14 | - go mod verify 15 | - go test -shuffle on ./... 16 | - govulncheck ./... 17 | - golangci-lint run --enable gosec --exclude=G115 --timeout 3m0s ./... 18 | sources: 19 | - ./go.mod 20 | - '**/*.go' 21 | 22 | nancy: 23 | desc: Check vulnerability of external packages with Nancy. 24 | cmds: 25 | - depm list -j | nancy sleuth -n 26 | sources: 27 | - ./go.mod 28 | - '**/*.go' 29 | 30 | prepare: 31 | - go mod tidy -v -go=1.23 32 | 33 | clean: 34 | desc: Initialize module and build cache, and remake go.sum file. 35 | cmds: 36 | - rm -f ./go.sum 37 | - go clean -cache 38 | - go clean -modcache 39 | 40 | graph: 41 | desc: Make grapth of dependency modules. 42 | cmds: 43 | - depm m --dot --dot-config dot-config.toml | dot -Tpng -o ./dependency.png 44 | sources: 45 | - ./go.mod 46 | - '**/*.go' 47 | generates: 48 | - ./dependency.png 49 | -------------------------------------------------------------------------------- /calendarid.go: -------------------------------------------------------------------------------- 1 | package koyomi 2 | 3 | import "net/url" 4 | 5 | type CalendarID int 6 | 7 | const ( 8 | Holiday CalendarID = iota + 1 //国民の祝日および休日 9 | MoonPhase //朔弦望 10 | SolarTerm //二十四節気・雑節 11 | Eclipse //日食・月食・日面経過 12 | Planet //惑星現象 13 | ) 14 | 15 | var cidMap = map[CalendarID]string{ 16 | Holiday: "2bk907eqjut8imoorgq1qa4olc@group.calendar.google.com", //国民の祝日および休日 17 | MoonPhase: "mr1q70hu2iacu62adntahc69q0@group.calendar.google.com", //朔弦望 18 | SolarTerm: "2i7smciu430uh0mv3i0qmd8iuk@group.calendar.google.com", //二十四節気・雑節 19 | Eclipse: "9lpmd80aki4edordf25nqjnln4@group.calendar.google.com", //日食・月食・日面経過 20 | Planet: "fsj78svf2km2stokku3r2ajuts@group.calendar.google.com", //惑星現象 21 | } 22 | 23 | //String is Stringer of CalendarID 24 | func (cid CalendarID) String() string { 25 | if s, ok := cidMap[cid]; ok { 26 | return s 27 | } 28 | return "" 29 | } 30 | 31 | //URL returns URL string from CalendarID 32 | func (cid CalendarID) URL() string { 33 | id := cid.String() 34 | if len(id) == 0 { 35 | return "" 36 | } 37 | return "https://calendar.google.com/calendar/ical/" + url.PathEscape(id) + "/public/basic.ics" 38 | } 39 | 40 | /* Copyright 2020 Spiegel 41 | * 42 | * Licensed under the Apache License, Version 2.0 (the "License"); 43 | * you may not use this file except in compliance with the License. 44 | * You may obtain a copy of the License at 45 | * 46 | * http://www.apache.org/licenses/LICENSE-2.0 47 | * 48 | * Unless required by applicable law or agreed to in writing, software 49 | * distributed under the License is distributed on an "AS IS" BASIS, 50 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 51 | * See the License for the specific language governing permissions and 52 | * limitations under the License. 53 | */ 54 | -------------------------------------------------------------------------------- /dependency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goark/koyomi/5deb2857f3473d62d59d5be63d64ec84d487423e/dependency.png -------------------------------------------------------------------------------- /dot-config.toml: -------------------------------------------------------------------------------- 1 | [node] 2 | fontname = "Inconsolata" 3 | [edge] 4 | color = "red" 5 | -------------------------------------------------------------------------------- /ecode.go: -------------------------------------------------------------------------------- 1 | package koyomi 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNullPointer = errors.New("Null reference instance") 7 | ErrNoData = errors.New("No data") 8 | ErrInvalidRecord = errors.New("Invalid record") 9 | ) 10 | 11 | /* Copyright 2020-2022 Spiegel 12 | * 13 | * Licensed under the Apache License, Version 2.0 (the "License"); 14 | * you may not use this file except in compliance with the License. 15 | * You may obtain a copy of the License at 16 | * 17 | * http://www.apache.org/licenses/LICENSE-2.0 18 | * 19 | * Unless required by applicable law or agreed to in writing, software 20 | * distributed under the License is distributed on an "AS IS" BASIS, 21 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | * See the License for the specific language governing permissions and 23 | * limitations under the License. 24 | */ 25 | -------------------------------------------------------------------------------- /events.go: -------------------------------------------------------------------------------- 1 | package koyomi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "sort" 8 | "strconv" 9 | 10 | "github.com/goark/errs" 11 | "github.com/goark/koyomi/value" 12 | ) 13 | 14 | // Event is koyomi event data 15 | type Event struct { 16 | Date value.DateJp 17 | Title string 18 | } 19 | 20 | // Koyomi is array of Event 21 | type Koyomi struct { 22 | events []Event 23 | } 24 | 25 | // newKoyomi createw Koyomi instance 26 | func newKoyomi() *Koyomi { 27 | return &Koyomi{events: make([]Event, 0)} 28 | } 29 | 30 | // Events returns event array 31 | func (k *Koyomi) Events() []Event { 32 | if k == nil { 33 | return []Event{} 34 | } 35 | return k.events 36 | } 37 | 38 | // SortByDate sorts event data by date 39 | func (k *Koyomi) SortByDate() { 40 | if k == nil || len(k.events) <= 1 { 41 | return 42 | } 43 | sort.SliceStable(k.events, func(i, j int) bool { 44 | return k.events[i].Date.Before(k.events[j].Date) 45 | }) 46 | } 47 | 48 | // Add adds other Kyomoi instance 49 | func (k *Koyomi) Add(kk *Koyomi) { 50 | if kk == nil { 51 | return 52 | } 53 | k.append(kk.events...) 54 | k.SortByDate() 55 | } 56 | 57 | func (k *Koyomi) append(e ...Event) { 58 | if k == nil { 59 | return 60 | } 61 | k.events = append(k.events, e...) 62 | } 63 | 64 | func (k *Koyomi) EncodeJSON() ([]byte, error) { 65 | if k == nil || len(k.events) == 0 { 66 | return nil, errs.Wrap(ErrNoData) 67 | } 68 | return json.Marshal(k.events) 69 | } 70 | 71 | func (k *Koyomi) EncodeCSV() ([]byte, error) { 72 | if k == nil || len(k.events) == 0 { 73 | return nil, errs.Wrap(ErrNoData) 74 | } 75 | buf := &bytes.Buffer{} 76 | _, err := buf.WriteString(`"Date","Title"` + "\n") 77 | if err != nil { 78 | return nil, errs.Wrap(err) 79 | } 80 | for _, e := range k.events { 81 | _, err := buf.WriteString(fmt.Sprintf("%s,%s\n", strconv.Quote(e.Date.String()), strconv.Quote(e.Title))) 82 | if err != nil { 83 | return nil, errs.Wrap(err) 84 | } 85 | } 86 | return buf.Bytes(), nil 87 | } 88 | 89 | /* Copyright 2020-2022 Spiegel 90 | * 91 | * Licensed under the Apache License, Version 2.0 (the "License"); 92 | * you may not use this file except in compliance with the License. 93 | * You may obtain a copy of the License at 94 | * 95 | * http://www.apache.org/licenses/LICENSE-2.0 96 | * 97 | * Unless required by applicable law or agreed to in writing, software 98 | * distributed under the License is distributed on an "AS IS" BASIS, 99 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 100 | * See the License for the specific language governing permissions and 101 | * limitations under the License. 102 | */ 103 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package koyomi_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "time" 9 | 10 | "github.com/goark/koyomi" 11 | "github.com/goark/koyomi/value" 12 | ) 13 | 14 | func ExampleKoyomi() { 15 | start, _ := value.DateFrom("2019-05-01") 16 | end := value.NewDate(time.Date(2019, time.May, 31, 0, 0, 0, 0, value.JST)) 17 | td, err := os.MkdirTemp(os.TempDir(), "sample") 18 | if err != nil { 19 | return 20 | } 21 | defer func() { _ = os.RemoveAll(td) }() 22 | k, err := koyomi.NewSource( 23 | koyomi.WithCalendarID(koyomi.Holiday, koyomi.SolarTerm), 24 | koyomi.WithStartDate(start), 25 | koyomi.WithEndDate(end), 26 | koyomi.WithTempDir(td), 27 | ).Get() 28 | if err != nil { 29 | return 30 | } 31 | 32 | csv, err := k.EncodeCSV() 33 | if err != nil { 34 | return 35 | } 36 | if _, err := io.Copy(os.Stdout, bytes.NewReader(csv)); err != nil { 37 | fmt.Fprintln(os.Stderr, err) 38 | return 39 | } 40 | //Output: 41 | //"Date","Title" 42 | //"2019-05-01","休日 (天皇の即位の日)" 43 | //"2019-05-02","休日" 44 | //"2019-05-02","八十八夜" 45 | //"2019-05-03","憲法記念日" 46 | //"2019-05-04","みどりの日" 47 | //"2019-05-05","こどもの日" 48 | //"2019-05-06","休日" 49 | //"2019-05-06","立夏" 50 | //"2019-05-21","小満" 51 | } 52 | 53 | /* Copyright 2020-2025 Spiegel 54 | * 55 | * Licensed under the Apache License, Version 2.0 (the "License"); 56 | * you may not use this file except in compliance with the License. 57 | * You may obtain a copy of the License at 58 | * 59 | * http://www.apache.org/licenses/LICENSE-2.0 60 | * 61 | * Unless required by applicable law or agreed to in writing, software 62 | * distributed under the License is distributed on an "AS IS" BASIS, 63 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 64 | * See the License for the specific language governing permissions and 65 | * limitations under the License. 66 | */ 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goark/koyomi 2 | 3 | go 1.23 4 | 5 | toolchain go1.23.6 6 | 7 | require ( 8 | github.com/goark/errs v1.3.2 9 | github.com/spiegel-im-spiegel/ics-golang v0.1.1 10 | ) 11 | 12 | require ( 13 | github.com/goark/fetch v0.4.2 // indirect 14 | github.com/spiegel-im-spiegel/iso8601duration v0.1.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/goark/errs v1.3.2 h1:ifccNe1aK7Xezt4XVYwHUqalmnfhuphnEvh3FshCReQ= 4 | github.com/goark/errs v1.3.2/go.mod h1:ZsQucxaDFVfSB8I99j4bxkDRfNOrlKINwg72QMuRWKw= 5 | github.com/goark/fetch v0.4.2 h1:PDIojCBiNL37o1vOw0KUWrSvAiYmxxqUwrwAwKXWb8M= 6 | github.com/goark/fetch v0.4.2/go.mod h1:f4O56aZ+L3eEEAxJOb6+F628nHoRPsPM5a/EXVgQpL4= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/spiegel-im-spiegel/ics-golang v0.1.1 h1:Cl/SjHoXiDhz/kHaR4qPaZ20/6o3ANacyRLYoZacYJk= 10 | github.com/spiegel-im-spiegel/ics-golang v0.1.1/go.mod h1:KjWn+ePk0dAaMLfQsWF29teHTPIZvaTlugttvY12GXc= 11 | github.com/spiegel-im-spiegel/iso8601duration v0.1.1 h1:jew5S9D9ojQP0lRWS64hzxquciOU3FGPjsW//8dC4OM= 12 | github.com/spiegel-im-spiegel/iso8601duration v0.1.1/go.mod h1:jODhN4x0QTpQATlQXr7eKe9xNi0cVyi47vuQpnyWYqA= 13 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 14 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /jdn/jdn.go: -------------------------------------------------------------------------------- 1 | package jdn 2 | 3 | import ( 4 | "math/big" 5 | "time" 6 | ) 7 | 8 | // GetJD returns Julian Date from time.Time. 9 | func GetJD(dt time.Time) *big.Rat { 10 | dt = dt.In(time.UTC) 11 | y := intRat(int64(dt.Year())) 12 | m := int64(dt.Month()) 13 | d := int64(dt.Day()) 14 | k := floorRat(quoInt(intRat(14-m), 12)) 15 | j := floorRat(mulRat(addInt(subRat(y, k), 4800), fracInt(1461, 4))) 16 | j = addRat(j, floorRat(mulRat(addInt(mulInt(k, 12), m-2), fracInt(367, 12)))) 17 | j = subRat(j, floorRat(mulRat(floorRat(quoInt(addInt(subRat(y, k), 4900), 100)), fracInt(3, 4)))) 18 | j = addInt(j, d-32075) 19 | j = addRat(j, subRat(quoRat(addRat(intRat(int64(dt.Second()+dt.Minute()*60+dt.Hour()*3600)), fracInt(int64(dt.Nanosecond()), 999999999)), floatRat((24*time.Hour).Seconds())), floatRat(0.5))) 20 | return j 21 | } 22 | 23 | // GetJDN returns Julian Day Number from time.Time. 24 | func GetJDN(dt time.Time) int64 { 25 | return floorRat(GetJD(dt)).Num().Int64() 26 | } 27 | 28 | // GetMJD returns Modified Julian Date from time.Time. 29 | func GetMJD(dt time.Time) *big.Rat { 30 | return subRat(GetJD(dt), floatRat(2400000.5)) 31 | } 32 | 33 | // GetMJDN returns Modified Julian Day Number from time.Time. 34 | func GetMJDN(dt time.Time) int64 { 35 | return floorRat(GetMJD(dt)).Num().Int64() 36 | } 37 | 38 | // FromJDN returns time.Time instance form Julian Day Number. 39 | func FromJDN(jdnum int64) time.Time { 40 | l := intRat(jdnum + 68569) 41 | n := floorRat(mulInt(quoInt(l, 146097), 4)) 42 | l = subRat(l, floorRat(quoInt(addInt(mulInt(n, 146097), 3), 4))) 43 | i := floorRat(quoInt(mulInt(addInt(l, 1), 4000), 1461001)) 44 | l = addInt(subRat(l, floorRat(quoInt(mulInt(i, 1461), 4))), 31) 45 | j := floorRat(quoInt(mulInt(l, 80), 2447)) 46 | day := subRat(l, floorRat(quoInt(mulInt(j, 2447), 80))) 47 | l = floorRat(quoInt(j, 11)) 48 | month := subRat(addInt(j, 2), mulInt(l, 12)) 49 | year := addRat(mulInt(addInt(n, -49), 100), addRat(i, l)) 50 | return time.Date(int(year.Num().Int64()), time.Month(int(month.Num().Int64())), int(day.Num().Int64()), 12, 0, 0, 0, time.UTC) 51 | } 52 | 53 | // FromJD returns time.Time instance form Julian Date. 54 | func FromJD(jd float64) time.Time { 55 | jdnum := int64(jd) 56 | dt := FromJDN(jdnum) 57 | return dt.Add(time.Duration((jd - float64(jdnum)) * float64(24*time.Hour))) 58 | } 59 | 60 | // FromJD returns time.Time instance form Julian Date. 61 | func FromMJD(mjd float64) time.Time { 62 | return FromJD(mjd + 2400000.5) 63 | } 64 | 65 | func intRat(x int64) *big.Rat { 66 | return fracInt(x, 1) 67 | } 68 | 69 | func floatRat(x float64) *big.Rat { 70 | return (&big.Rat{}).SetFloat64(x) 71 | } 72 | 73 | func fracInt(x, y int64) *big.Rat { 74 | return big.NewRat(x, y) 75 | } 76 | 77 | func addInt(x *big.Rat, y int64) *big.Rat { 78 | return addRat(x, intRat(y)) 79 | } 80 | 81 | // func subInt(x *big.Rat, y int64) *big.Rat { 82 | // return subRat(x, intRat(y)) 83 | // } 84 | 85 | func mulInt(x *big.Rat, y int64) *big.Rat { 86 | return mulRat(x, intRat(y)) 87 | } 88 | 89 | func quoInt(x *big.Rat, y int64) *big.Rat { 90 | return quoRat(x, intRat(y)) 91 | } 92 | 93 | func addRat(x, y *big.Rat) *big.Rat { 94 | return (&big.Rat{}).Add(x, y) 95 | } 96 | 97 | func subRat(x, y *big.Rat) *big.Rat { 98 | return (&big.Rat{}).Sub(x, y) 99 | } 100 | 101 | func mulRat(x, y *big.Rat) *big.Rat { 102 | return (&big.Rat{}).Mul(x, y) 103 | } 104 | 105 | func quoRat(x, y *big.Rat) *big.Rat { 106 | return (&big.Rat{}).Quo(x, y) 107 | } 108 | 109 | func floorRat(n *big.Rat) *big.Rat { 110 | return (&big.Rat{}).SetInt((&big.Int{}).Div(n.Num(), n.Denom())) 111 | } 112 | 113 | /* Copyright 2022 Spiegel 114 | * 115 | * Licensed under the Apache License, Version 2.0 (the "License"); 116 | * you may not use this file except in compliance with the License. 117 | * You may obtain a copy of the License at 118 | * 119 | * http://www.apache.org/licenses/LICENSE-2.0 120 | * 121 | * Unless required by applicable law or agreed to in writing, software 122 | * distributed under the License is distributed on an "AS IS" BASIS, 123 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 124 | * See the License for the specific language governing permissions and 125 | * limitations under the License. 126 | */ 127 | -------------------------------------------------------------------------------- /jdn/jdn_test.go: -------------------------------------------------------------------------------- 1 | package jdn 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestGetJDN(t *testing.T) { 10 | jst := time.FixedZone("JST", int((9 * time.Hour).Seconds())) // Japan standard Time 11 | testCases := []struct { 12 | inp time.Time 13 | outp1 *big.Rat 14 | outp2 int64 15 | outpDt time.Time 16 | outp3 *big.Rat 17 | outp4 int64 18 | }{ 19 | {inp: time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC), outp1: floatRat(2457023.5), outp2: 2457023, outpDt: time.Date(2015, time.January, 0, 12, 0, 0, 0, time.UTC), outp3: floatRat(57023.0), outp4: 57023}, 20 | {inp: time.Date(2022, time.January, 1, 0, 0, 0, 0, jst), outp1: floatRat(2459580.125), outp2: 2459580, outpDt: time.Date(2022, time.January, 0, 12, 0, 0, 0, time.UTC), outp3: floatRat(59579.625), outp4: 59579}, 21 | {inp: time.Date(2023, time.February, 24, 12, 0, 0, 0, time.UTC), outp1: floatRat(2460000.0), outp2: 2460000, outpDt: time.Date(2023, time.February, 24, 12, 0, 0, 0, time.UTC), outp3: floatRat(59999.5), outp4: 59999}, 22 | } 23 | for _, tc := range testCases { 24 | jd := GetJD(tc.inp) 25 | if jd.Cmp(tc.outp1) != 0 { 26 | t.Errorf("GetJD(%v) is %v, want %v.", tc.inp, jd.FloatString(5), tc.outp1.FloatString(5)) 27 | } 28 | fjd, _ := jd.Float64() 29 | dt := FromJD(fjd) 30 | if !dt.Equal(tc.inp) { 31 | t.Errorf("FromJD(%v) is %v, want %v.", fjd, dt, tc.inp) 32 | } 33 | jn := GetJDN(tc.inp) 34 | if jn != tc.outp2 { 35 | t.Errorf("GetJDN(%v) is %v, want %v.", tc.inp, jn, tc.outp2) 36 | } 37 | dt = FromJDN(jn) 38 | if !dt.Equal(tc.outpDt) { 39 | t.Errorf("FromJDN(%v) is %v, want %v.", jn, dt, tc.outpDt) 40 | } 41 | mjd := GetMJD(tc.inp) 42 | if mjd.Cmp(tc.outp3) != 0 { 43 | t.Errorf("GetMJD(%v) is %v, want %v.", tc.inp, mjd.FloatString(5), tc.outp3.FloatString(5)) 44 | } 45 | mjdn := GetMJDN(tc.inp) 46 | if mjdn != tc.outp4 { 47 | t.Errorf("GetMJDN(%v) is %v, want %v.", tc.inp, mjdn, tc.outp4) 48 | } 49 | fmjd, _ := mjd.Float64() 50 | dt = FromMJD(fmjd) 51 | if !dt.Equal(tc.inp) { 52 | t.Errorf("FromMJD(%v) is %v, want %v.", fjd, dt, tc.inp) 53 | } 54 | } 55 | } 56 | 57 | func TestFloorRat(t *testing.T) { 58 | testCases := []struct { 59 | inp float64 60 | outp float64 61 | }{ 62 | {inp: 1.1, outp: 1}, 63 | {inp: 1.0, outp: 1}, 64 | {inp: 0.9, outp: 0}, 65 | {inp: 0.1, outp: 0}, 66 | {inp: 0.0, outp: 0}, 67 | {inp: -0.1, outp: -1}, 68 | {inp: -0.9, outp: -1}, 69 | {inp: -1.0, outp: -1}, 70 | {inp: -1.1, outp: -2}, 71 | } 72 | for _, tc := range testCases { 73 | f := floorRat(floatRat(tc.inp)) 74 | if ff, _ := f.Float64(); ff != tc.outp { 75 | t.Errorf("floorRat(%v) is %v, want %v.", tc.inp, f, tc.outp) 76 | } 77 | } 78 | } 79 | 80 | /* Copyright 2022 Spiegel 81 | * 82 | * Licensed under the Apache License, Version 2.0 (the "License"); 83 | * you may not use this file except in compliance with the License. 84 | * You may obtain a copy of the License at 85 | * 86 | * http://www.apache.org/licenses/LICENSE-2.0 87 | * 88 | * Unless required by applicable law or agreed to in writing, software 89 | * distributed under the License is distributed on an "AS IS" BASIS, 90 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 91 | * See the License for the specific language governing permissions and 92 | * limitations under the License. 93 | */ 94 | -------------------------------------------------------------------------------- /sample/sample0.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "io" 9 | "os" 10 | "time" 11 | 12 | "github.com/goark/koyomi" 13 | "github.com/goark/koyomi/value" 14 | ) 15 | 16 | func main() { 17 | start, _ := value.DateFrom("2019-05-01") 18 | end := value.NewDate(time.Date(2019, time.May, 31, 0, 0, 0, 0, value.JST)) 19 | td, err := os.MkdirTemp(os.TempDir(), "sample") 20 | if err != nil { 21 | return 22 | } 23 | defer func() { _ = os.RemoveAll(td) }() 24 | k, err := koyomi.NewSource( 25 | koyomi.WithCalendarID(koyomi.Holiday, koyomi.SolarTerm), 26 | koyomi.WithStartDate(start), 27 | koyomi.WithEndDate(end), 28 | koyomi.WithTempDir(td), 29 | ).Get() 30 | if err != nil { 31 | return 32 | } 33 | 34 | csv, err := k.EncodeCSV() 35 | if err != nil { 36 | return 37 | } 38 | io.Copy(os.Stdout, bytes.NewReader(csv)) 39 | } 40 | 41 | /* Copyright 2023 Spiegel 42 | * 43 | * Licensed under the Apache License, Version 2.0 (the "License"); 44 | * you may not use this file except in compliance with the License. 45 | * You may obtain a copy of the License at 46 | * 47 | * http://www.apache.org/licenses/LICENSE-2.0 48 | * 49 | * Unless required by applicable law or agreed to in writing, software 50 | * distributed under the License is distributed on an "AS IS" BASIS, 51 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 | * See the License for the specific language governing permissions and 53 | * limitations under the License. 54 | */ 55 | -------------------------------------------------------------------------------- /sample/sample0b.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | "text/template" 9 | "time" 10 | 11 | "github.com/goark/koyomi" 12 | "github.com/goark/koyomi/value" 13 | ) 14 | 15 | func main() { 16 | start, _ := value.DateFrom("2019-05-01") 17 | end := value.NewDate(time.Date(2019, time.May, 31, 0, 0, 0, 0, value.JST)) 18 | td, err := os.MkdirTemp(os.TempDir(), "sample") 19 | if err != nil { 20 | return 21 | } 22 | defer func() { _ = os.RemoveAll(td) }() 23 | k, err := koyomi.NewSource( 24 | koyomi.WithCalendarID(koyomi.Holiday, koyomi.SolarTerm), 25 | koyomi.WithStartDate(start), 26 | koyomi.WithEndDate(end), 27 | koyomi.WithTempDir(td), 28 | ).Get() 29 | if err != nil { 30 | return 31 | } 32 | 33 | myTemplate := `| 日付 | 曜日 | 内容 | 34 | | ---- |:----:| ---- | 35 | {{ range . }}| {{ .Date.StringJp }} | {{ .Date.WeekdayJp.ShortStringJp }} | {{ .Title }} | 36 | {{ end -}}` 37 | 38 | t, err := template.New("").Parse(myTemplate) 39 | if err != nil { 40 | return 41 | } 42 | if err := t.Execute(os.Stdout, k.Events()); err != nil { 43 | return 44 | } 45 | //Output: 46 | //| 日付 | 曜日 | 内容 | 47 | //| ---- |:----:| ---- | 48 | //| 2019年5月1日 | 水 | 休日 (天皇の即位の日) | 49 | //| 2019年5月2日 | 木 | 休日 | 50 | //| 2019年5月2日 | 木 | 八十八夜 | 51 | //| 2019年5月3日 | 金 | 憲法記念日 | 52 | //| 2019年5月4日 | 土 | みどりの日 | 53 | //| 2019年5月5日 | 日 | こどもの日 | 54 | //| 2019年5月6日 | 月 | 休日 | 55 | //| 2019年5月6日 | 月 | 立夏 | 56 | //| 2019年5月21日 | 火 | 小満 | 57 | } 58 | 59 | /* Copyright 2025 Spiegel 60 | * 61 | * Licensed under the Apache License, Version 2.0 (the "License"); 62 | * you may not use this file except in compliance with the License. 63 | * You may obtain a copy of the License at 64 | * 65 | * http://www.apache.org/licenses/LICENSE-2.0 66 | * 67 | * Unless required by applicable law or agreed to in writing, software 68 | * distributed under the License is distributed on an "AS IS" BASIS, 69 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 70 | * See the License for the specific language governing permissions and 71 | * limitations under the License. 72 | */ 73 | -------------------------------------------------------------------------------- /sample/sample1.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/goark/koyomi/value" 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | argsStr := flag.Args() 19 | tm := time.Now() 20 | if len(argsStr) > 0 { 21 | if len(argsStr) < 3 { 22 | fmt.Fprintln(os.Stderr, "年月日を指定してください") 23 | return 24 | } 25 | args := make([]int, 3) 26 | for i := 0; i < 3; i++ { 27 | num, err := strconv.Atoi(argsStr[i]) 28 | if err != nil { 29 | fmt.Fprintln(os.Stderr, err) 30 | return 31 | } 32 | args[i] = num 33 | } 34 | tm = time.Date(args[0], time.Month(args[1]), args[2], 0, 0, 0, 0, time.Local) 35 | } 36 | te := value.NewDate(tm) 37 | n, y := te.YearEraString() 38 | if len(n) == 0 { 39 | fmt.Fprintln(os.Stderr, "正しい年月日を指定してください") 40 | return 41 | } 42 | fmt.Printf("%s%s%d月%d日\n", n, y, te.Month(), te.Day()) 43 | } 44 | 45 | /* Copyright 2019-2023 Spiegel 46 | * 47 | * Licensed under the Apache License, Version 2.0 (the "License"); 48 | * you may not use this file except in compliance with the License. 49 | * You may obtain a copy of the License at 50 | * 51 | * http://www.apache.org/licenses/LICENSE-2.0 52 | * 53 | * Unless required by applicable law or agreed to in writing, software 54 | * distributed under the License is distributed on an "AS IS" BASIS, 55 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 56 | * See the License for the specific language governing permissions and 57 | * limitations under the License. 58 | */ 59 | -------------------------------------------------------------------------------- /sample/sample2.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/goark/koyomi/value" 14 | ) 15 | 16 | func main() { 17 | flag.Parse() 18 | argsStr := flag.Args() 19 | 20 | if len(argsStr) < 4 { 21 | fmt.Fprintln(os.Stderr, "元号 年 月 日 を指定してください") 22 | return 23 | } 24 | name := argsStr[0] 25 | args := make([]int, 3) 26 | for i := 0; i < 3; i++ { 27 | num, err := strconv.Atoi(argsStr[i+1]) 28 | if err != nil { 29 | fmt.Fprintln(os.Stderr, err) 30 | return 31 | } 32 | args[i] = num 33 | } 34 | te := value.NewDateEra(value.EraName(name), args[0], time.Month(args[1]), args[2]) 35 | fmt.Println(te.Format("西暦2006年1月2日")) 36 | } 37 | 38 | /* Copyright 2019-2023 Spiegel 39 | * 40 | * Licensed under the Apache License, Version 2.0 (the "License"); 41 | * you may not use this file except in compliance with the License. 42 | * You may obtain a copy of the License at 43 | * 44 | * http://www.apache.org/licenses/LICENSE-2.0 45 | * 46 | * Unless required by applicable law or agreed to in writing, software 47 | * distributed under the License is distributed on an "AS IS" BASIS, 48 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 49 | * See the License for the specific language governing permissions and 50 | * limitations under the License. 51 | */ 52 | -------------------------------------------------------------------------------- /sample/sample3.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/goark/koyomi/value" 12 | "github.com/goark/koyomi/zodiac" 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | args := flag.Args() 18 | if len(args) < 1 { 19 | fmt.Fprintln(os.Stderr, os.ErrInvalid) 20 | return 21 | } 22 | for _, s := range args { 23 | t, err := value.DateFrom(s) 24 | if err != nil { 25 | fmt.Fprintln(os.Stderr, err) 26 | continue 27 | } 28 | kan, shi := zodiac.ZodiacYearNumber(t.Year()) 29 | fmt.Printf("Year %v is %v%v (Eho: %v)\n", t.Year(), kan, shi, kan.DirectionJp()) 30 | kan, shi = zodiac.ZodiacDayNumber(t) 31 | fmt.Printf("Day %v is %v%v\n", t.Format("2006-01-02"), kan, shi) 32 | } 33 | } 34 | 35 | /* Copyright 2021-2023 Spiegel 36 | * 37 | * Licensed under the Apache License, Version 2.0 (the "License"); 38 | * you may not use this file except in compliance with the License. 39 | * You may obtain a copy of the License at 40 | * 41 | * http://www.apache.org/licenses/LICENSE-2.0 42 | * 43 | * Unless required by applicable law or agreed to in writing, software 44 | * distributed under the License is distributed on an "AS IS" BASIS, 45 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 46 | * See the License for the specific language governing permissions and 47 | * limitations under the License. 48 | */ 49 | -------------------------------------------------------------------------------- /sample/sample4.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | 11 | "github.com/goark/koyomi/jdn" 12 | "github.com/goark/koyomi/value" 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | args := flag.Args() 18 | if len(args) < 1 { 19 | fmt.Fprintln(os.Stderr, os.ErrInvalid) 20 | return 21 | } 22 | for _, s := range args { 23 | t, err := value.DateFrom(s) 24 | if err != nil { 25 | fmt.Fprintln(os.Stderr, err) 26 | continue 27 | } 28 | j := jdn.GetJDN(t.Time) 29 | fmt.Printf("Julian Day Number of %v is %v\n", t.Format("2006-01-02"), j) 30 | } 31 | } 32 | 33 | /* Copyright 2022 Spiegel 34 | * 35 | * Licensed under the Apache License, Version 2.0 (the "License"); 36 | * you may not use this file except in compliance with the License. 37 | * You may obtain a copy of the License at 38 | * 39 | * http://www.apache.org/licenses/LICENSE-2.0 40 | * 41 | * Unless required by applicable law or agreed to in writing, software 42 | * distributed under the License is distributed on an "AS IS" BASIS, 43 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | * See the License for the specific language governing permissions and 45 | * limitations under the License. 46 | */ 47 | -------------------------------------------------------------------------------- /source.go: -------------------------------------------------------------------------------- 1 | package koyomi 2 | 3 | import ( 4 | "github.com/goark/errs" 5 | "github.com/goark/koyomi/value" 6 | ics "github.com/spiegel-im-spiegel/ics-golang" 7 | ) 8 | 9 | // Source is information of data source for koyomi 10 | type Source struct { 11 | cids []CalendarID 12 | start value.DateJp 13 | end value.DateJp 14 | tempDir string //temporary directory for github.com/spiegel-im-spiegel/ics-golang package 15 | } 16 | 17 | // optFunc is self-referential function for functional options pattern 18 | type optFunc func(*Source) 19 | 20 | // NewSource creates a new Source instance 21 | func NewSource(opts ...optFunc) *Source { 22 | s := &Source{ 23 | cids: []CalendarID{}, 24 | } 25 | for _, opt := range opts { 26 | opt(s) 27 | } 28 | return s 29 | } 30 | 31 | // WithCalendarID returns function for setting Reader 32 | func WithCalendarID(cid ...CalendarID) optFunc { 33 | return func(s *Source) { 34 | s.cids = append(s.cids, cid...) 35 | } 36 | } 37 | 38 | // WithStartDate returns function for setting Reader 39 | func WithStartDate(start value.DateJp) optFunc { 40 | return func(s *Source) { 41 | s.start = start 42 | } 43 | } 44 | 45 | // WithEndDate returns function for setting Reader 46 | func WithEndDate(end value.DateJp) optFunc { 47 | return func(s *Source) { 48 | s.end = end 49 | } 50 | } 51 | 52 | // WithTempDir returns function for setting Reader 53 | func WithTempDir(dir string) optFunc { 54 | return func(s *Source) { 55 | s.tempDir = dir 56 | } 57 | } 58 | 59 | // Get returns koyomi data from calendar dources 60 | func (s *Source) Get() (*Koyomi, error) { 61 | if s == nil { 62 | return nil, errs.Wrap(ErrNullPointer) 63 | } 64 | if len(s.cids) == 0 { 65 | return nil, errs.Wrap(ErrNoData) 66 | } 67 | k := newKoyomi() 68 | if len(s.tempDir) > 0 { 69 | ics.FilePath = s.tempDir 70 | } 71 | for _, cid := range s.cids { 72 | es, err := getFrom(cid, s.start, s.end) 73 | if err != nil { 74 | return nil, errs.Wrap(err) 75 | } 76 | k.append(es...) 77 | } 78 | k.SortByDate() 79 | return k, nil 80 | } 81 | 82 | func getFrom(cid CalendarID, start, end value.DateJp) ([]Event, error) { 83 | url := cid.URL() 84 | if len(url) == 0 { 85 | return nil, errs.Wrap(ErrNoData, errs.WithContext("cid", int(cid)), errs.WithContext("start", start.String()), errs.WithContext("end", end.String())) 86 | } 87 | parser := ics.New() 88 | pch := parser.GetInputChan() 89 | pch <- url 90 | parser.Wait() 91 | 92 | calendars, err := parser.GetCalendars() 93 | if err != nil { 94 | return nil, errs.Wrap(err, errs.WithContext("cid", int(cid)), errs.WithContext("start", start.String()), errs.WithContext("end", end.String())) 95 | } 96 | kevts := []Event{} 97 | for _, calendar := range calendars { 98 | for _, evt := range calendar.GetEvents() { 99 | e := Event{Date: value.NewDate(evt.GetStart()), Title: evt.GetSummary()} 100 | if boundaryIn(e, start, end) { 101 | kevts = append(kevts, e) 102 | } 103 | } 104 | } 105 | return kevts, nil 106 | } 107 | 108 | func boundaryIn(e Event, start, end value.DateJp) bool { 109 | if e.Date.IsZero() { 110 | return false 111 | } 112 | if !start.IsZero() && e.Date.Before(start) { 113 | return false 114 | } 115 | if !end.IsZero() && e.Date.After(end) { 116 | return false 117 | } 118 | return true 119 | } 120 | 121 | /* Copyright 2020-2023 Spiegel 122 | * 123 | * Licensed under the Apache License, Version 2.0 (the "License"); 124 | * you may not use this file except in compliance with the License. 125 | * You may obtain a copy of the License at 126 | * 127 | * http://www.apache.org/licenses/LICENSE-2.0 128 | * 129 | * Unless required by applicable law or agreed to in writing, software 130 | * distributed under the License is distributed on an "AS IS" BASIS, 131 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 132 | * See the License for the specific language governing permissions and 133 | * limitations under the License. 134 | */ 135 | -------------------------------------------------------------------------------- /value/date.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | "github.com/goark/errs" 9 | ) 10 | 11 | // WeekdayJp is a type that represents the days of the week in the Japanese context. 12 | // It is based on the time.Weekday type from the standard library. 13 | type WeekdayJp time.Weekday 14 | 15 | const ( 16 | Sunday WeekdayJp = WeekdayJp(time.Sunday) + iota // 日曜日 17 | Monday // 月曜日 18 | Tuesday // 火曜日 19 | Wednesday // 水曜日 20 | Thursday // 木曜日 21 | Friday // 金曜日 22 | Saturday // 土曜日 23 | ) 24 | 25 | var weekdayNames = [7]string{"日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"} 26 | var weekdayShortNames = [7]string{"日", "月", "火", "水", "木", "金", "土"} 27 | 28 | // String returns the English name of the Japanese weekday (WeekdayJp) 29 | // by converting it to the standard time.Weekday type and calling its String method. 30 | func (w WeekdayJp) String() string { 31 | return time.Weekday(w).String() 32 | } 33 | 34 | // StringJp returns the Japanese name of the WeekdayJp if it is between Sunday and Saturday. 35 | // If the WeekdayJp is out of this range, it returns the standard time.Weekday string representation. 36 | func (w WeekdayJp) StringJp() string { 37 | if w < Sunday || w > Saturday { 38 | return time.Weekday(w).String() 39 | } 40 | return weekdayNames[w] 41 | } 42 | 43 | // ShortStringJp returns the short Japanese name of the WeekdayJp. 44 | // If the WeekdayJp is not within the valid range (Sunday to Saturday), 45 | // it returns the default string representation of the time.Weekday. 46 | func (w WeekdayJp) ShortStringJp() string { 47 | if w < Sunday || w > Saturday { 48 | return time.Weekday(w).String() 49 | } 50 | return weekdayShortNames[w] 51 | } 52 | 53 | var ( 54 | jstoffset = int64((9 * time.Hour).Seconds()) 55 | JST = time.FixedZone("JST", int(jstoffset)) // Japan standard Time 56 | ) 57 | 58 | // DateJp is wrapper class of time.Time 59 | type DateJp struct { 60 | time.Time 61 | } 62 | 63 | // NewDate returns DateJp instance 64 | func NewDate(tm time.Time) DateJp { 65 | if tm.IsZero() { 66 | return DateJp{tm} 67 | } 68 | ut := tm.Unix() 69 | _, offset := tm.Zone() 70 | return DateJp{time.Unix(((ut+int64(offset))/86400)*86400-jstoffset, 0).In(JST)} 71 | } 72 | 73 | var timeTemplate = []string{ 74 | "2006-01-02", 75 | "2006-01", 76 | time.RFC3339, 77 | } 78 | 79 | // DateFrom returns DateJp instance from date string 80 | func DateFrom(s string) (DateJp, error) { 81 | if len(s) == 0 || strings.EqualFold(s, "null") { 82 | return NewDate(time.Time{}), nil 83 | } 84 | var lastErr error 85 | for _, tmplt := range timeTemplate { 86 | if tm, err := time.Parse(tmplt, s); err != nil { 87 | lastErr = errs.Wrap(err, errs.WithContext("time_string", s), errs.WithContext("time_template", tmplt)) 88 | } else { 89 | return NewDate(tm), nil 90 | } 91 | } 92 | return NewDate(time.Time{}), lastErr 93 | } 94 | 95 | // UnmarshalJSON returns result of Unmarshal for json.Unmarshal() 96 | func (t *DateJp) UnmarshalJSON(b []byte) error { 97 | s, err := strconv.Unquote(string(b)) 98 | if err != nil { 99 | s = string(b) 100 | } 101 | tm, err := DateFrom(s) 102 | if err != nil { 103 | return err 104 | } 105 | *t = tm 106 | return nil 107 | } 108 | 109 | // MarshalJSON returns time string with RFC3339 format 110 | func (t *DateJp) MarshalJSON() ([]byte, error) { 111 | if t == nil { 112 | return []byte("\"\""), nil 113 | } 114 | if t.IsZero() { 115 | return []byte("\"\""), nil 116 | } 117 | return []byte(strconv.Quote(t.String())), nil 118 | } 119 | 120 | // String returns the DateJp value formatted as a string in the "2006-01-02" layout. 121 | // It implements the Stringer interface. 122 | func (t DateJp) String() string { 123 | return t.Format("2006-01-02") 124 | } 125 | 126 | // StringJp returns the date in Japanese format (YYYY年M月D日). 127 | func (t DateJp) StringJp() string { 128 | return t.Format("2006年1月2日") 129 | } 130 | 131 | // Equal reports whether t and dt represent the same time instant. 132 | func (t DateJp) Equal(dt DateJp) bool { 133 | return t.Time.Year() == dt.Time.Year() && t.Time.Month() == dt.Time.Month() && t.Time.Day() == dt.Time.Day() 134 | } 135 | 136 | // Before reports whether the DateJp instant t is before dt. 137 | func (t DateJp) Before(dt DateJp) bool { 138 | return !t.Equal(dt) && t.Time.Before(dt.Time) 139 | } 140 | 141 | // After reports whether the DateJp instant t is after dt. 142 | func (t DateJp) After(dt DateJp) bool { 143 | return !t.Equal(dt) && t.Time.After(dt.Time) 144 | } 145 | 146 | // AddDate method adds years/months/days and returns new Date instance. 147 | func (t DateJp) AddDate(years int, months int, days int) DateJp { 148 | return NewDate(t.Time.AddDate(years, months, days)) 149 | } 150 | 151 | // AddDay method adds n days and returns new Date instance. 152 | func (t DateJp) AddDay(days int) DateJp { 153 | return t.AddDate(0, 0, days) 154 | } 155 | 156 | // WeekdayJp returns the Japanese representation of the weekday for the given DateJp. 157 | // It converts the standard weekday to a WeekdayJp type. 158 | func (t DateJp) WeekdayJp() WeekdayJp { 159 | return WeekdayJp(t.Weekday()) 160 | } 161 | 162 | /* Copyright 2020-2025 Spiegel 163 | * 164 | * Licensed under the Apache License, Version 2.0 (the "License"); 165 | * you may not use this file except in compliance with the License. 166 | * You may obtain a copy of the License at 167 | * 168 | * http://www.apache.org/licenses/LICENSE-2.0 169 | * 170 | * Unless required by applicable law or agreed to in writing, software 171 | * distributed under the License is distributed on an "AS IS" BASIS, 172 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 173 | * See the License for the specific language governing permissions and 174 | * limitations under the License. 175 | */ 176 | -------------------------------------------------------------------------------- /value/date_test.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestWeekdayJp(t *testing.T) { 11 | testCases := []struct { 12 | s string 13 | name string 14 | nameJp string 15 | short string 16 | }{ 17 | {s: "2024-06-01T09:00:00+09:00", name: "Saturday", nameJp: "土曜日", short: "土"}, 18 | {s: "2024-06-02T09:00:00+09:00", name: "Sunday", nameJp: "日曜日", short: "日"}, 19 | {s: "2024-06-03T09:00:00+09:00", name: "Monday", nameJp: "月曜日", short: "月"}, 20 | {s: "2024-06-04T09:00:00+09:00", name: "Tuesday", nameJp: "火曜日", short: "火"}, 21 | {s: "2024-06-05T09:00:00+09:00", name: "Wednesday", nameJp: "水曜日", short: "水"}, 22 | {s: "2024-06-06T09:00:00+09:00", name: "Thursday", nameJp: "木曜日", short: "木"}, 23 | {s: "2024-06-07T09:00:00+09:00", name: "Friday", nameJp: "金曜日", short: "金"}, 24 | } 25 | 26 | for _, tc := range testCases { 27 | dt, err := DateFrom(tc.s) 28 | if err != nil { 29 | t.Errorf("value.DateFrom() is \"%v\", want nil.", err) 30 | continue 31 | } 32 | wd := dt.WeekdayJp() 33 | if wd.String() != tc.name { 34 | t.Errorf("DateJp.WeekdayJp() is \"%v\", want \"%v\".", wd.String(), tc.name) 35 | } 36 | if wd.StringJp() != tc.nameJp { 37 | t.Errorf("DateJp.WeekdayJp() is \"%v\", want \"%v\".", wd.StringJp(), tc.nameJp) 38 | } 39 | if wd.ShortStringJp() != tc.short { 40 | t.Errorf("DateJp.WeekdayJp() is \"%v\", want \"%v\".", wd.ShortStringJp(), tc.short) 41 | } 42 | } 43 | } 44 | 45 | func TestWeekdayJpErr(t *testing.T) { 46 | testCases := []struct { 47 | wd WeekdayJp 48 | name string 49 | nameJp string 50 | short string 51 | }{ 52 | {wd: WeekdayJp(7), name: "%!Weekday(7)", nameJp: "%!Weekday(7)", short: "%!Weekday(7)"}, 53 | } 54 | 55 | for _, tc := range testCases { 56 | if tc.wd.String() != tc.name { 57 | t.Errorf("WeekdayJp is \"%v\", want \"%v\".", tc.wd.String(), tc.name) 58 | } 59 | if tc.wd.StringJp() != tc.nameJp { 60 | t.Errorf("WeekdayJp.StringJp() is \"%v\", want \"%v\".", tc.wd.StringJp(), tc.nameJp) 61 | } 62 | if tc.wd.ShortStringJp() != tc.short { 63 | t.Errorf("WeekdayJp.ShortStringJp() is \"%v\", want \"%v\".", tc.wd.ShortStringJp(), tc.short) 64 | } 65 | } 66 | } 67 | 68 | func TestDateJp(t *testing.T) { 69 | testCases := []struct { 70 | s string 71 | str string 72 | strJp string 73 | }{ 74 | {s: "2024-06-01T09:00:00+09:00", str: "2024-06-01", strJp: "2024年6月1日"}, 75 | {s: "2024-06-10T09:00:00+09:00", str: "2024-06-10", strJp: "2024年6月10日"}, 76 | {s: "2024-12-01T09:00:00+09:00", str: "2024-12-01", strJp: "2024年12月1日"}, 77 | {s: "2024-12-31T09:00:00+09:00", str: "2024-12-31", strJp: "2024年12月31日"}, 78 | } 79 | 80 | for _, tc := range testCases { 81 | dt, err := DateFrom(tc.s) 82 | if err != nil { 83 | t.Errorf("value.DateFrom() is \"%v\", want nil.", err) 84 | continue 85 | } 86 | if dt.String() != tc.str { 87 | t.Errorf("DateJp.String() is \"%v\", want \"%v\".", dt.String(), tc.str) 88 | } 89 | if dt.StringJp() != tc.strJp { 90 | t.Errorf("DateJp.StringJp() is \"%v\", want \"%v\".", dt.StringJp(), tc.strJp) 91 | } 92 | } 93 | } 94 | 95 | type ForTestStruct struct { 96 | DateTaken DateJp `json:"date_taken,omitempty"` 97 | } 98 | 99 | func TestUnmarshal(t *testing.T) { 100 | testCases := []struct { 101 | s string 102 | str string 103 | str2 string 104 | jsn string 105 | }{ 106 | {s: `{"date_taken": "2005-03-26T00:00:00+01:00"}`, str: "2005-03-26", str2: "2005-03-26T00:00:00+09:00", jsn: `{"date_taken":"2005-03-26"}`}, 107 | {s: `{"date_taken": "2005-03-26T12:34:56+09:00"}`, str: "2005-03-26", str2: "2005-03-26T00:00:00+09:00", jsn: `{"date_taken":"2005-03-26"}`}, 108 | {s: `{"date_taken": "2005-03-26"}`, str: "2005-03-26", str2: "2005-03-26T00:00:00+09:00", jsn: `{"date_taken":"2005-03-26"}`}, 109 | {s: `{"date_taken": ""}`, str: "0001-01-01", str2: "0001-01-01T00:00:00Z", jsn: `{"date_taken":""}`}, 110 | {s: `{}`, str: "0001-01-01", str2: "0001-01-01T00:00:00Z", jsn: `{"date_taken":""}`}, 111 | } 112 | 113 | for _, tc := range testCases { 114 | tst := &ForTestStruct{} 115 | if err := json.Unmarshal([]byte(tc.s), tst); err != nil { 116 | t.Errorf("json.Unmarshal() is \"%v\", want nil.", err) 117 | continue 118 | } 119 | str := tst.DateTaken.String() 120 | if str != tc.str { 121 | t.Errorf("DateJp = \"%v\", want \"%v\".", str, tc.str) 122 | } 123 | str2 := tst.DateTaken.Time.Format(time.RFC3339) 124 | if str2 != tc.str2 { 125 | t.Errorf("DateJp = \"%v\", want \"%v\".", str2, tc.str2) 126 | } 127 | b, err := json.Marshal(tst) 128 | if err != nil { 129 | t.Errorf("json.Marshal() is \"%v\", want nil.", err) 130 | continue 131 | } 132 | str = string(b) 133 | if str != tc.jsn { 134 | t.Errorf("ForTestStruct = \"%v\", want \"%v\".", str, tc.jsn) 135 | } 136 | } 137 | } 138 | 139 | func TestUnmarshalErr(t *testing.T) { 140 | data := `{"date_taken": "2005/03/26"}` 141 | tst := &ForTestStruct{} 142 | if err := json.Unmarshal([]byte(data), tst); err == nil { 143 | t.Error("Unmarshal() error = nil, not want nil.") 144 | } else { 145 | fmt.Printf("Info: %+v\n", err) 146 | } 147 | } 148 | 149 | func TestEqual(t *testing.T) { 150 | dt1, _ := DateFrom("2020-03-31") 151 | dt2, _ := DateFrom("2020-04-01") 152 | if dt1.Equal(dt2) { 153 | t.Error("DateJp.Equal() is true, want false.") 154 | } 155 | if !dt1.Before(dt2) { 156 | t.Error("DateJp.Before() is false, want true.") 157 | } 158 | if dt1.After(dt2) { 159 | t.Error("DateJp.After() is true, want false.") 160 | } 161 | if !dt1.AddDay(1).Equal(dt2) { 162 | t.Error("DateJp.Equal() is false, want true.") 163 | } 164 | } 165 | 166 | /* Copyright 2020-2025 Spiegel 167 | * 168 | * Licensed under the Apache License, Version 2.0 (the "License"); 169 | * you may not use this file except in compliance with the License. 170 | * You may obtain a copy of the License at 171 | * 172 | * http://www.apache.org/licenses/LICENSE-2.0 173 | * 174 | * Unless required by applicable law or agreed to in writing, software 175 | * distributed under the License is distributed on an "AS IS" BASIS, 176 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 177 | * See the License for the specific language governing permissions and 178 | * limitations under the License. 179 | */ 180 | -------------------------------------------------------------------------------- /value/era.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // eraName は元号名を表す内部型です。 9 | type eraName int 10 | 11 | const ( 12 | EraUnknown eraName = iota //不明な元号 13 | Meiji //明治 14 | Taisho //大正 15 | Showa //昭和 16 | Heisei //平成 17 | Reiwa //令和 18 | ) 19 | 20 | var eraString = map[eraName]string{ 21 | EraUnknown: "", 22 | Meiji: "明治", 23 | Taisho: "大正", 24 | Showa: "昭和", 25 | Heisei: "平成", 26 | Reiwa: "令和", 27 | } 28 | 29 | // value.EraName() 関数は元号の文字列から元号名 eraName を取得します。 30 | // 該当する元号名がない場合は era.Unknown を返します。 31 | func EraName(s string) eraName { 32 | for k, v := range eraString { 33 | if v == s { 34 | return k 35 | } 36 | } 37 | return EraUnknown 38 | } 39 | 40 | func (e eraName) String() string { 41 | if s, ok := eraString[e]; ok { 42 | return s 43 | } 44 | return "" 45 | } 46 | 47 | var ( 48 | eraTrigger = map[eraName]DateJp{ //各元号の起点 49 | Meiji: NewDate(time.Date(1873, time.January, 1, 0, 0, 0, 0, JST)), //明治(の改暦) : 1873-01-01 50 | Taisho: NewDate(time.Date(1912, time.July, 30, 0, 0, 0, 0, JST)), //大正 : 1912-07-30 51 | Showa: NewDate(time.Date(1926, time.December, 25, 0, 0, 0, 0, JST)), //昭和 : 1926-12-25 52 | Heisei: NewDate(time.Date(1989, time.January, 8, 0, 0, 0, 0, JST)), //平成 : 1989-01-08 53 | Reiwa: NewDate(time.Date(2019, time.May, 1, 0, 0, 0, 0, JST)), //令和 : 2019-05-01 54 | } 55 | eraSorted = []eraName{Reiwa, Heisei, Showa, Taisho, Meiji} //ソートされた元号の配列(降順) 56 | ) 57 | 58 | // value.NewDateEra() 関数は 元号・年・月・日・時・分・秒・タイムゾーン を指定して value.DateJp 型のインスタンスを返します。 59 | // 起点が定義されない元号を指定した場合は西暦として処理します。 60 | func NewDateEra(en eraName, year int, month time.Month, day int) DateJp { 61 | ofset := 0 62 | if dt, ok := eraTrigger[en]; ok { 63 | ofset = dt.Year() - 1 64 | } 65 | return NewDate(time.Date(year+ofset, month, day, 0, 0, 0, 0, JST)) 66 | } 67 | 68 | // value.DateJp.Era() メソッドは元号名 value.eraName のインスタンスを返します。 69 | // 元号が不明の場合は era.Unknown を返します。 70 | func (t DateJp) Era() eraName { 71 | for _, es := range eraSorted { 72 | if !t.Before(eraTrigger[es]) { 73 | return es 74 | } 75 | } 76 | return EraUnknown 77 | 78 | } 79 | 80 | // value.DateJp.YearEra() メソッドは元号付きの年の値を返します。 81 | // 元号が不明の場合は (era.Unknown, 0) を返します。 82 | func (t DateJp) YearEra() (eraName, int) { 83 | era := t.Era() 84 | if era == EraUnknown { 85 | return EraUnknown, 0 86 | } 87 | year := t.Year() - eraTrigger[era].Year() + 1 88 | if era == Meiji { //明治のみ5年のオフセットを加算する 89 | return era, year + 5 90 | } 91 | return era, year 92 | } 93 | 94 | // value.DateJp.YearEraString() メソッドは元号付きの年の値を文字列で返します。 95 | // 元号が不明の場合は空文字列を返します。 96 | func (t DateJp) YearEraString() (string, string) { 97 | era, year := t.YearEra() 98 | if era == EraUnknown || year < 1 { 99 | return "", "" 100 | } 101 | if year == 1 { 102 | return era.String(), "元年" 103 | } 104 | return era.String(), fmt.Sprintf("%d年", year) 105 | } 106 | 107 | /* Copyright 2019-2023 Spiegel 108 | * 109 | * Licensed under the Apache License, Version 2.0 (the "License"); 110 | * you may not use this file except in compliance with the License. 111 | * You may obtain a copy of the License at 112 | * 113 | * http://www.apache.org/licenses/LICENSE-2.0 114 | * 115 | * Unless required by applicable law or agreed to in writing, software 116 | * distributed under the License is distributed on an "AS IS" BASIS, 117 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 118 | * See the License for the specific language governing permissions and 119 | * limitations under the License. 120 | */ 121 | -------------------------------------------------------------------------------- /value/era_test.go: -------------------------------------------------------------------------------- 1 | package value 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestNameToString(t *testing.T) { 9 | testCases := []struct { 10 | n eraName 11 | s string 12 | }{ 13 | {n: EraUnknown, s: ""}, 14 | {n: Meiji, s: "明治"}, 15 | {n: Taisho, s: "大正"}, 16 | {n: Showa, s: "昭和"}, 17 | {n: Heisei, s: "平成"}, 18 | {n: Reiwa, s: "令和"}, 19 | {n: eraName(6), s: ""}, 20 | } 21 | for _, tc := range testCases { 22 | if tc.n.String() != tc.s { 23 | t.Errorf("eraName.String(%d) = \"%v\", want \"%v\".", int(tc.n), tc.n, tc.s) 24 | } 25 | } 26 | } 27 | 28 | func TestStringToName(t *testing.T) { 29 | testCases := []struct { 30 | n eraName 31 | s string 32 | }{ 33 | {n: EraUnknown, s: ""}, 34 | {n: EraUnknown, s: "元号"}, 35 | {n: Meiji, s: "明治"}, 36 | {n: Taisho, s: "大正"}, 37 | {n: Showa, s: "昭和"}, 38 | {n: Heisei, s: "平成"}, 39 | {n: Reiwa, s: "令和"}, 40 | } 41 | for _, tc := range testCases { 42 | n := EraName(tc.s) 43 | if tc.n != n { 44 | t.Errorf("GetName(%v) = \"%v\", want \"%v\".", tc.s, n, tc.n) 45 | } 46 | } 47 | } 48 | 49 | func TestTimeToEra(t *testing.T) { 50 | testCases := []struct { 51 | year int 52 | month time.Month 53 | day int 54 | n eraName 55 | yearEra int 56 | eraStr string 57 | yearStr string 58 | }{ 59 | {year: 1872, month: time.December, day: 31, n: EraUnknown, yearEra: 0, eraStr: "", yearStr: ""}, 60 | {year: 1873, month: time.January, day: 1, n: Meiji, yearEra: 6, eraStr: "明治", yearStr: "6年"}, 61 | {year: 1912, month: time.July, day: 29, n: Meiji, yearEra: 45, eraStr: "明治", yearStr: "45年"}, 62 | {year: 1912, month: time.July, day: 30, n: Taisho, yearEra: 1, eraStr: "大正", yearStr: "元年"}, 63 | {year: 1926, month: time.December, day: 24, n: Taisho, yearEra: 15, eraStr: "大正", yearStr: "15年"}, 64 | {year: 1926, month: time.December, day: 25, n: Showa, yearEra: 1, eraStr: "昭和", yearStr: "元年"}, 65 | {year: 1989, month: time.January, day: 7, n: Showa, yearEra: 64, eraStr: "昭和", yearStr: "64年"}, 66 | {year: 1989, month: time.January, day: 8, n: Heisei, yearEra: 1, eraStr: "平成", yearStr: "元年"}, 67 | {year: 2019, month: time.April, day: 30, n: Heisei, yearEra: 31, eraStr: "平成", yearStr: "31年"}, 68 | {year: 2019, month: time.May, day: 1, n: Reiwa, yearEra: 1, eraStr: "令和", yearStr: "元年"}, 69 | {year: 2118, month: time.December, day: 31, n: Reiwa, yearEra: 100, eraStr: "令和", yearStr: "100年"}, 70 | } 71 | for _, tc := range testCases { 72 | tm := time.Date(tc.year, tc.month, tc.day, 0, 0, 0, 0, time.UTC) 73 | n, y := NewDate(tm).YearEra() 74 | if tc.n != n || tc.yearEra != y { 75 | t.Errorf("[%v].Era() = \"%v %d\", want \"%v %d\".", tm, n, y, tc.n, tc.yearEra) 76 | } 77 | ns, ys := NewDate(tm).YearEraString() 78 | if tc.eraStr != ns || tc.yearStr != ys { 79 | t.Errorf("[%v].Era() = \"%v %v\", want \"%v %v\".", tm, ns, ys, tc.eraStr, tc.yearStr) 80 | } 81 | } 82 | } 83 | 84 | func TestEraToDate(t *testing.T) { 85 | testCases := []struct { 86 | n eraName 87 | year int 88 | month time.Month 89 | day int 90 | timeStr string 91 | }{ 92 | {n: EraUnknown, year: 2019, month: time.May, day: 1, timeStr: "2019-05-01"}, 93 | {n: Heisei, year: 31, month: time.May, day: 1, timeStr: "2019-05-01"}, 94 | {n: Reiwa, year: 1, month: time.May, day: 1, timeStr: "2019-05-01"}, 95 | {n: Showa, year: 100, month: time.January, day: 1, timeStr: "2025-01-01"}, 96 | } 97 | for _, tc := range testCases { 98 | tm := NewDateEra(tc.n, tc.year, tc.month, tc.day) 99 | s := tm.Format("2006-01-02") 100 | if tc.timeStr != s { 101 | t.Errorf("Date() = \"%v\", want \"%v\".", s, tc.timeStr) 102 | } 103 | } 104 | } 105 | 106 | /* Copyright 2019-2025 Spiegel 107 | * 108 | * Licensed under the Apache License, Version 2.0 (the "License"); 109 | * you may not use this file except in compliance with the License. 110 | * You may obtain a copy of the License at 111 | * 112 | * http://www.apache.org/licenses/LICENSE-2.0 113 | * 114 | * Unless required by applicable law or agreed to in writing, software 115 | * distributed under the License is distributed on an "AS IS" BASIS, 116 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 117 | * See the License for the specific language governing permissions and 118 | * limitations under the License. 119 | */ 120 | -------------------------------------------------------------------------------- /zodiac/zodiac.go: -------------------------------------------------------------------------------- 1 | package zodiac 2 | 3 | import ( 4 | "github.com/goark/koyomi/jdn" 5 | "github.com/goark/koyomi/value" 6 | ) 7 | 8 | type Kan10 uint 9 | 10 | const ( 11 | Kinoe Kan10 = iota // 甲(木の兄) 12 | Kinoto // 乙(木の弟) 13 | Hinoe // 丙(火の兄) 14 | Hinoto // 丁(火の弟) 15 | Tsutinoe // 戊(土の兄) 16 | Tsutinoto // 己(土の弟) 17 | Kanoe // 庚(金の兄) 18 | Kanoto // 辛(金の弟) 19 | Mizunoe // 壬(水の兄) 20 | Mizunoto // 癸(水の弟) 21 | KanMax 22 | ) 23 | 24 | var ( 25 | kanNames = [KanMax]string{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"} 26 | kanDirection = [KanMax]int{75, 255, 165, 345, 165, 75, 255, 165, 345, 165} 27 | kanDirectionJp = [KanMax]string{"東北東微東", "西南西微西", "南南東微南", "北北西微北", "南南東微南", "東北東微東", "西南西微西", "南南東微南", "北北西微北", "南南東微南"} 28 | ) 29 | 30 | func (k Kan10) String() string { 31 | return kanNames[k%KanMax] 32 | } 33 | 34 | // Direction mehtod reterns Eho (favourable direction). 35 | func (k Kan10) Direction() int { 36 | return kanDirection[k%KanMax] 37 | } 38 | 39 | // DirectionJp mehtod reterns japanese Eho (favourable direction) name. 40 | func (k Kan10) DirectionJp() string { 41 | return kanDirectionJp[k%KanMax] 42 | } 43 | 44 | type Shi12 uint 45 | 46 | const ( 47 | Rat Shi12 = iota // 子 48 | Ox // 丑 49 | Tiger // 寅 50 | Rabbit // 卯 51 | Dragon // 辰 52 | Snake // 巳 53 | Horse // 午 54 | Sheep // 未 55 | Monkey // 申 56 | Rooster // 酉 57 | Dog // 戌 58 | Boar // 亥 59 | ShiMax 60 | ) 61 | 62 | var shiNames = [ShiMax]string{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"} 63 | 64 | func (s Shi12) String() string { 65 | return shiNames[s%ShiMax] 66 | } 67 | 68 | var ( 69 | baseYear = 1984 // Year 1984 is 甲子 70 | ) 71 | 72 | // ZodiacDayNumber function returns japanese zodiac day number. 73 | func ZodiacDayNumber(t value.DateJp) (Kan10, Shi12) { 74 | n := jdn.GetJDN(t.Time) + 50 75 | k := n % int64(KanMax) 76 | if k < 0 { 77 | k += int64(KanMax) 78 | } 79 | s := n % int64(ShiMax) 80 | if s < 0 { 81 | s += int64(ShiMax) 82 | } 83 | return Kan10(k), Shi12(s) 84 | } 85 | 86 | // ZodiacYearNumber function returns japanese zodiac year number from 1984. 87 | func ZodiacYearNumber(y int) (Kan10, Shi12) { 88 | n := y - baseYear 89 | k := n % int(KanMax) 90 | if k < 0 { 91 | k += int(KanMax) 92 | } 93 | s := n % int(ShiMax) 94 | if s < 0 { 95 | s += int(ShiMax) 96 | } 97 | return Kan10(k), Shi12(s) 98 | } 99 | 100 | /* Copyright 2021-2023 Spiegel 101 | * 102 | * Licensed under the Apache License, Version 2.0 (the "License"); 103 | * you may not use this file except in compliance with the License. 104 | * You may obtain a copy of the License at 105 | * 106 | * http://www.apache.org/licenses/LICENSE-2.0 107 | * 108 | * Unless required by applicable law or agreed to in writing, software 109 | * distributed under the License is distributed on an "AS IS" BASIS, 110 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 111 | * See the License for the specific language governing permissions and 112 | * limitations under the License. 113 | */ 114 | -------------------------------------------------------------------------------- /zodiac/zodiac_test.go: -------------------------------------------------------------------------------- 1 | package zodiac_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/goark/koyomi/value" 8 | "github.com/goark/koyomi/zodiac" 9 | ) 10 | 11 | func TestKan10(t *testing.T) { 12 | testCases := []struct { 13 | kan zodiac.Kan10 14 | name string 15 | dir int 16 | dirJp string 17 | }{ 18 | {kan: zodiac.Kinoe, name: "甲", dir: 75, dirJp: "東北東微東"}, 19 | {kan: zodiac.Kinoto, name: "乙", dir: 255, dirJp: "西南西微西"}, 20 | {kan: zodiac.Hinoe, name: "丙", dir: 165, dirJp: "南南東微南"}, 21 | {kan: zodiac.Hinoto, name: "丁", dir: 345, dirJp: "北北西微北"}, 22 | {kan: zodiac.Tsutinoe, name: "戊", dir: 165, dirJp: "南南東微南"}, 23 | {kan: zodiac.Tsutinoto, name: "己", dir: 75, dirJp: "東北東微東"}, 24 | {kan: zodiac.Kanoe, name: "庚", dir: 255, dirJp: "西南西微西"}, 25 | {kan: zodiac.Kanoto, name: "辛", dir: 165, dirJp: "南南東微南"}, 26 | {kan: zodiac.Mizunoe, name: "壬", dir: 345, dirJp: "北北西微北"}, 27 | {kan: zodiac.Mizunoto, name: "癸", dir: 165, dirJp: "南南東微南"}, 28 | {kan: zodiac.Kan10(10), name: "甲", dir: 75, dirJp: "東北東微東"}, 29 | } 30 | 31 | for _, tc := range testCases { 32 | str := tc.kan.String() 33 | if str != tc.name { 34 | t.Errorf("zodiac.Kan10(%v) is \"%v\", want %v", uint(tc.kan), str, tc.name) 35 | } 36 | dir := tc.kan.Direction() 37 | if dir != tc.dir { 38 | t.Errorf("zodiac.Kan10(%v) is \"%v\", want %v", uint(tc.kan), dir, tc.dir) 39 | } 40 | dirJp := tc.kan.DirectionJp() 41 | if dirJp != tc.dirJp { 42 | t.Errorf("zodiac.Kan10(%v) is \"%v\", want %v", uint(tc.kan), dirJp, tc.dirJp) 43 | } 44 | } 45 | } 46 | 47 | func TestShi12(t *testing.T) { 48 | testCases := []struct { 49 | shi zodiac.Shi12 50 | name string 51 | }{ 52 | {shi: zodiac.Rat, name: "子"}, 53 | {shi: zodiac.Ox, name: "丑"}, 54 | {shi: zodiac.Tiger, name: "寅"}, 55 | {shi: zodiac.Rabbit, name: "卯"}, 56 | {shi: zodiac.Dragon, name: "辰"}, 57 | {shi: zodiac.Snake, name: "巳"}, 58 | {shi: zodiac.Horse, name: "午"}, 59 | {shi: zodiac.Sheep, name: "未"}, 60 | {shi: zodiac.Monkey, name: "申"}, 61 | {shi: zodiac.Rooster, name: "酉"}, 62 | {shi: zodiac.Dog, name: "戌"}, 63 | {shi: zodiac.Boar, name: "亥"}, 64 | {shi: zodiac.Shi12(12), name: "子"}, 65 | } 66 | 67 | for _, tc := range testCases { 68 | str := tc.shi.String() 69 | if str != tc.name { 70 | t.Errorf("zodiac.Shi12(%v) is \"%v\", want %v", uint(tc.shi), str, tc.name) 71 | } 72 | } 73 | } 74 | 75 | func TestZodiac(t *testing.T) { 76 | testCases := []struct { 77 | t value.DateJp 78 | kanYear zodiac.Kan10 79 | shiYear zodiac.Shi12 80 | kanDay zodiac.Kan10 81 | shiDay zodiac.Shi12 82 | }{ 83 | {t: value.NewDate(time.Date(1983, time.January, 1, 0, 0, 0, 0, value.JST)), kanYear: zodiac.Mizunoto, shiYear: zodiac.Boar, kanDay: zodiac.Tsutinoto, shiDay: zodiac.Ox}, 84 | {t: value.NewDate(time.Date(1984, time.January, 1, 0, 0, 0, 0, value.JST)), kanYear: zodiac.Kinoe, shiYear: zodiac.Rat, kanDay: zodiac.Kinoe, shiDay: zodiac.Horse}, 85 | {t: value.NewDate(time.Date(1985, time.January, 1, 0, 0, 0, 0, value.JST)), kanYear: zodiac.Kinoto, shiYear: zodiac.Ox, kanDay: zodiac.Kanoe, shiDay: zodiac.Rat}, 86 | {t: value.NewDate(time.Date(2000, time.January, 1, 0, 0, 0, 0, value.JST)), kanYear: zodiac.Kanoe, shiYear: zodiac.Dragon, kanDay: zodiac.Tsutinoe, shiDay: zodiac.Horse}, 87 | {t: value.NewDate(time.Date(2001, time.January, 1, 0, 0, 0, 0, value.JST)), kanYear: zodiac.Kanoto, shiYear: zodiac.Snake, kanDay: zodiac.Kinoe, shiDay: zodiac.Rat}, 88 | {t: value.NewDate(time.Date(2002, time.January, 1, 0, 0, 0, 0, value.JST)), kanYear: zodiac.Mizunoe, shiYear: zodiac.Horse, kanDay: zodiac.Tsutinoto, shiDay: zodiac.Snake}, 89 | } 90 | 91 | for _, tc := range testCases { 92 | kanYear, shiYear := zodiac.ZodiacYearNumber(tc.t.Year()) 93 | if kanYear != tc.kanYear { 94 | t.Errorf("result of ZodiacYearNumber(\"%v\") is \"%v\" (kan), want %v", tc.t, kanYear, tc.kanYear) 95 | } 96 | if shiYear != tc.shiYear { 97 | t.Errorf("result of ZodiacYearNumber(\"%v\") is \"%v\" (shi), want %v", tc.t, shiYear, tc.shiYear) 98 | } 99 | kanDay, shiDay := zodiac.ZodiacDayNumber(tc.t) 100 | if kanDay != tc.kanDay { 101 | t.Errorf("result of ZodiacDayNumber(\"%v\") is \"%v\" (kan), want %v", tc.t, kanDay, tc.kanDay) 102 | } 103 | if shiYear != tc.shiYear { 104 | t.Errorf("result of ZodiacDayNumber(\"%v\") is \"%v\" (shi), want %v", tc.t, shiDay, tc.shiDay) 105 | } 106 | } 107 | } 108 | 109 | /* Copyright 2021-2023 Spiegel 110 | * 111 | * Licensed under the Apache License, Version 2.0 (the "License"); 112 | * you may not use this file except in compliance with the License. 113 | * You may obtain a copy of the License at 114 | * 115 | * http://www.apache.org/licenses/LICENSE-2.0 116 | * 117 | * Unless required by applicable law or agreed to in writing, software 118 | * distributed under the License is distributed on an "AS IS" BASIS, 119 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 120 | * See the License for the specific language governing permissions and 121 | * limitations under the License. 122 | */ 123 | --------------------------------------------------------------------------------