├── .gitignore ├── LICENSE ├── README.md ├── VERSION ├── astronomy.go ├── astronomy_test.go ├── calendar.go ├── calendar_test.go ├── chinesecalendar.go ├── chinesecalendar_test.go ├── clone.go ├── config.go ├── config_test.go ├── datetime.go ├── datetime_test.go ├── go.mod ├── julian.go ├── julian_test.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 phpu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 日历 2 | 3 | calendar、日历、中国农历、阴历、节气、干支、生肖、星座 4 | 5 | 通过天文计算和民间推算方法,准确计算出公历-1000年至3000年的农历、干支、节气等。 6 | 7 | > 天文计算方法参考Jean Meeus的《Astronomical Algorithms》、[NASA](https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html "NASA")网站、[天文与历法](http://www.bieyu.com/ "天文与历法")网站等相关的天文历法计算方法。 8 | 9 | 当前稳定版本(Current Stable Version):v1.1.0 10 | 11 | 推荐版本(Recommended Version):v1.1.0 12 | 13 | `go get github.com/liujiawm/gocalendar@v1.1.0` 14 | 15 | - [Installation 安装](#installation-安装) 16 | - [示例](#示例) 17 | - [日历表](#日历表) 18 | - [日历配置](#日历配置) 19 | - [其它接口](#其它接口) 20 | - [节气](#节气) 21 | - [农历与公历互换](#农历与公历互换) 22 | - [早晚子时示例说明](#早晚子时示例说明) 23 | - [公历转换干支](#公历转换干支) 24 | - [星座](#星座) 25 | - [儒略日(Julian Day)](#儒略日julian-day) 26 | - [Modified Julian Day](#modified-julian-day) 27 | - [Documentation 更多详细说明](https://pkg.go.dev/github.com/liujiawm/gocalendar) 28 | - [帮助](https://github.com/liujiawm/gocalendar) 29 | - 联系 30 | - QQ:194088 31 | - Email:liujiawm@msn.com 32 | 33 | ## Installation 安装 ## 34 | 35 | ``` 36 | go get github.com/liujiawm/gocalendar 37 | ``` 38 | 39 | ## 示例 ## 40 | 41 | ### 日历表 ### 42 | 43 | #### 生成一个日历表 #### 44 | 45 | `(*Calendar) Generate()` 46 | 47 | ``` go 48 | // 用默认的Calendar生成日历表,当前时间是2021年2月8日 49 | items := DefaultCalendar().Generate() 50 | for _,item := range items { 51 | fmt.Println(item) 52 | } 53 | 54 | ``` 55 | 56 | ``` 57 | 公历日期(*)周 农历日期 干支 节气、节日 58 | ------------------------------------------------------------------------ 59 | 2021-01-31 周日 2020庚子(鼠)年腊月十九 庚子年己丑月己卯日 60 | 2021-02-01 周一 2020庚子(鼠)年腊月二十 庚子年己丑月庚辰日 61 | 2021-02-02 周二 2020庚子(鼠)年腊月廿一 庚子年己丑月辛巳日 62 | 2021-02-03 周三 2020庚子(鼠)年腊月廿二 辛丑年庚寅月壬午日 立春 定立春:2021-02-03T22:59:23+08:00 63 | 2021-02-04 周四 2020庚子(鼠)年腊月廿三 辛丑年庚寅月癸未日 64 | 2021-02-05 周五 2020庚子(鼠)年腊月廿四 辛丑年庚寅月甲申日 小年 65 | 2021-02-06 周六 2020庚子(鼠)年腊月廿五 辛丑年庚寅月乙酉日 66 | 2021-02-07 周日 2020庚子(鼠)年腊月廿六 辛丑年庚寅月丙戌日 67 | 2021-02-08*周一 2020庚子(鼠)年腊月廿七 辛丑年庚寅月丁亥日 68 | 2021-02-09 周二 2020庚子(鼠)年腊月廿八 辛丑年庚寅月戊子日 69 | 2021-02-10 周三 2020庚子(鼠)年腊月廿九 辛丑年庚寅月己丑日 70 | 2021-02-11 周四 2020庚子(鼠)年腊月三十 辛丑年庚寅月庚寅日 除夕 71 | 2021-02-12 周五 2021辛丑(牛)年正月初一 辛丑年庚寅月辛卯日 春节 72 | 2021-02-13 周六 2021辛丑(牛)年正月初二 辛丑年庚寅月壬辰日 73 | 2021-02-14 周日 2021辛丑(牛)年正月初三 辛丑年庚寅月癸巳日 情人节 74 | 2021-02-15 周一 2021辛丑(牛)年正月初四 辛丑年庚寅月甲午日 75 | 2021-02-16 周二 2021辛丑(牛)年正月初五 辛丑年庚寅月乙未日 76 | 2021-02-17 周三 2021辛丑(牛)年正月初六 辛丑年庚寅月丙申日 77 | 2021-02-18 周四 2021辛丑(牛)年正月初七 辛丑年庚寅月丁酉日 雨水 定雨水:2021-02-18T18:44:29+08:00 78 | 2021-02-19 周五 2021辛丑(牛)年正月初八 辛丑年庚寅月戊戌日 79 | 2021-02-20 周六 2021辛丑(牛)年正月初九 辛丑年庚寅月己亥日 80 | 2021-02-21 周日 2021辛丑(牛)年正月初十 辛丑年庚寅月庚子日 81 | 2021-02-22 周一 2021辛丑(牛)年正月十一 辛丑年庚寅月辛丑日 82 | 2021-02-23 周二 2021辛丑(牛)年正月十二 辛丑年庚寅月壬寅日 83 | 2021-02-24 周三 2021辛丑(牛)年正月十三 辛丑年庚寅月癸卯日 84 | 2021-02-25 周四 2021辛丑(牛)年正月十四 辛丑年庚寅月甲辰日 85 | 2021-02-26 周五 2021辛丑(牛)年正月十五 辛丑年庚寅月乙巳日 元宵节 86 | 2021-02-27 周六 2021辛丑(牛)年正月十六 辛丑年庚寅月丙午日 87 | 2021-02-28 周日 2021辛丑(牛)年正月十七 辛丑年庚寅月丁未日 88 | 2021-03-01 周一 2021辛丑(牛)年正月十八 辛丑年庚寅月戊申日 89 | 2021-03-02 周二 2021辛丑(牛)年正月十九 辛丑年庚寅月己酉日 90 | 2021-03-03 周三 2021辛丑(牛)年正月二十 辛丑年庚寅月庚戌日 91 | 2021-03-04 周四 2021辛丑(牛)年正月廿一 辛丑年庚寅月辛亥日 92 | 2021-03-05 周五 2021辛丑(牛)年正月廿二 辛丑年辛卯月壬子日 惊蛰 定惊蛰:2021-03-05T16:53:57+08:00 93 | 2021-03-06 周六 2021辛丑(牛)年正月廿三 辛丑年辛卯月癸丑日 94 | 2021-03-07 周日 2021辛丑(牛)年正月廿四 辛丑年辛卯月甲寅日 95 | 2021-03-08 周一 2021辛丑(牛)年正月廿五 辛丑年辛卯月乙卯日 妇女节 96 | 2021-03-09 周二 2021辛丑(牛)年正月廿六 辛丑年辛卯月丙辰日 97 | 2021-03-10 周三 2021辛丑(牛)年正月廿七 辛丑年辛卯月丁巳日 98 | 2021-03-11 周四 2021辛丑(牛)年正月廿八 辛丑年辛卯月戊午日 99 | 2021-03-12 周五 2021辛丑(牛)年正月廿九 辛丑年辛卯月己未日 植树节 100 | 2021-03-13 周六 2021辛丑(牛)年二月初一 辛丑年辛卯月庚申日 101 | ``` 102 | 103 | #### 指定日期的日历表 #### 104 | 105 | `(*Calendar) GenerateWithDate(year, month, day int, timeParts ...int)` 106 | 107 | ``` go 108 | items := DefaultCalendar().GenerateWithDate(2021,5,1) 109 | for _,item := range items { 110 | fmt.Println(item) 111 | } 112 | ``` 113 | 114 | ``` 115 | 2021-04-25 周日 2021辛丑(牛)年三月十四 辛丑年壬辰月癸卯日 116 | 2021-04-26 周一 2021辛丑(牛)年三月十五 辛丑年壬辰月甲辰日 117 | 2021-04-27 周二 2021辛丑(牛)年三月十六 辛丑年壬辰月乙巳日 118 | 2021-04-28 周三 2021辛丑(牛)年三月十七 辛丑年壬辰月丙午日 119 | 2021-04-29 周四 2021辛丑(牛)年三月十八 辛丑年壬辰月丁未日 120 | 2021-04-30 周五 2021辛丑(牛)年三月十九 辛丑年壬辰月戊申日 121 | 2021-05-01 周六 2021辛丑(牛)年三月二十 辛丑年壬辰月己酉日 劳动节 122 | 2021-05-02 周日 2021辛丑(牛)年三月廿一 辛丑年壬辰月庚戌日 123 | 2021-05-03 周一 2021辛丑(牛)年三月廿二 辛丑年壬辰月辛亥日 124 | 2021-05-04 周二 2021辛丑(牛)年三月廿三 辛丑年壬辰月壬子日 青年节 125 | 2021-05-05 周三 2021辛丑(牛)年三月廿四 辛丑年癸巳月癸丑日 立夏 定立夏:2021-05-05T14:46:29+08:00 126 | 2021-05-06 周四 2021辛丑(牛)年三月廿五 辛丑年癸巳月甲寅日 127 | 2021-05-07 周五 2021辛丑(牛)年三月廿六 辛丑年癸巳月乙卯日 128 | 2021-05-08 周六 2021辛丑(牛)年三月廿七 辛丑年癸巳月丙辰日 129 | 2021-05-09 周日 2021辛丑(牛)年三月廿八 辛丑年癸巳月丁巳日 母亲节 130 | 2021-05-10 周一 2021辛丑(牛)年三月廿九 辛丑年癸巳月戊午日 131 | 2021-05-11 周二 2021辛丑(牛)年三月三十 辛丑年癸巳月己未日 132 | 2021-05-12 周三 2021辛丑(牛)年四月初一 辛丑年癸巳月庚申日 护士节 133 | 2021-05-13 周四 2021辛丑(牛)年四月初二 辛丑年癸巳月辛酉日 134 | 2021-05-14 周五 2021辛丑(牛)年四月初三 辛丑年癸巳月壬戌日 135 | 2021-05-15 周六 2021辛丑(牛)年四月初四 辛丑年癸巳月癸亥日 136 | 2021-05-16 周日 2021辛丑(牛)年四月初五 辛丑年癸巳月甲子日 137 | 2021-05-17 周一 2021辛丑(牛)年四月初六 辛丑年癸巳月乙丑日 138 | 2021-05-18 周二 2021辛丑(牛)年四月初七 辛丑年癸巳月丙寅日 139 | 2021-05-19 周三 2021辛丑(牛)年四月初八 辛丑年癸巳月丁卯日 140 | 2021-05-20 周四 2021辛丑(牛)年四月初九 辛丑年癸巳月戊辰日 141 | 2021-05-21 周五 2021辛丑(牛)年四月初十 辛丑年癸巳月己巳日 小满 定小满:2021-05-21T03:36:22+08:00 142 | 2021-05-22 周六 2021辛丑(牛)年四月十一 辛丑年癸巳月庚午日 143 | 2021-05-23 周日 2021辛丑(牛)年四月十二 辛丑年癸巳月辛未日 144 | 2021-05-24 周一 2021辛丑(牛)年四月十三 辛丑年癸巳月壬申日 145 | 2021-05-25 周二 2021辛丑(牛)年四月十四 辛丑年癸巳月癸酉日 146 | 2021-05-26 周三 2021辛丑(牛)年四月十五 辛丑年癸巳月甲戌日 147 | 2021-05-27 周四 2021辛丑(牛)年四月十六 辛丑年癸巳月乙亥日 148 | 2021-05-28 周五 2021辛丑(牛)年四月十七 辛丑年癸巳月丙子日 149 | 2021-05-29 周六 2021辛丑(牛)年四月十八 辛丑年癸巳月丁丑日 150 | 2021-05-30 周日 2021辛丑(牛)年四月十九 辛丑年癸巳月戊寅日 151 | 2021-05-31 周一 2021辛丑(牛)年四月二十 辛丑年癸巳月己卯日 152 | 2021-06-01 周二 2021辛丑(牛)年四月廿一 辛丑年癸巳月庚辰日 儿童节 153 | 2021-06-02 周三 2021辛丑(牛)年四月廿二 辛丑年癸巳月辛巳日 154 | 2021-06-03 周四 2021辛丑(牛)年四月廿三 辛丑年癸巳月壬午日 155 | 2021-06-04 周五 2021辛丑(牛)年四月廿四 辛丑年癸巳月癸未日 156 | 2021-06-05 周六 2021辛丑(牛)年四月廿五 辛丑年甲午月甲申日 芒种 定芒种:2021-06-05T18:51:32+08:00 157 | ``` 158 | 159 | #### 日历变换 #### 160 | 161 | 下一月 162 | 163 | `(*Calendar) NextMonth` 164 | 165 | ``` go 166 | c := DefaultCalendar() 167 | 168 | // 下一月的日历表 169 | items := c.NextMonth() 170 | 171 | ``` 172 | 173 | 上一月 174 | 175 | `(*Calendar) PreviousMonth` 176 | 177 | ``` go 178 | c := DefaultCalendar() 179 | 180 | // 上一月的日历表 181 | items := c.PreviousMonth() 182 | 183 | ``` 184 | 185 | 下一年当月 186 | 187 | `(*Calendar) NextYear` 188 | 189 | ``` go 190 | c := DefaultCalendar() 191 | 192 | // 下一年当月的日历表 193 | items := c.NextYear() 194 | 195 | ``` 196 | 197 | 上一年当月 198 | 199 | `(*Calendar) PreviousYear` 200 | 201 | ``` go 202 | c := DefaultCalendar() 203 | 204 | // 上一年的当月日历表 205 | items := c.PreviousYear() 206 | 207 | ``` 208 | 209 | ### 日历配置 ### 210 | 211 | ``` go 212 | type CalendarConfig struct { 213 | Grid int // 取日历方式,GridDay按天取日历,GridWeek按周取日历,GridMonth按月取日历 214 | FirstWeek int // 日历显示时第一列显示周几,(日历表第一列是周几,0周日,依次最大值6) 215 | TimeZoneName string // 时区名称,需zoneinfo支持的时区名称 216 | SolarTerms bool // 读取节气 bool 217 | Lunar bool // 读取农历 bool 218 | HeavenlyEarthly bool // 读取干支 bool 219 | NightZiHour bool // 区分早晚子时,true则 23:00-24:00 00:00-01:00为子时,否则00:00-02:00为子时 220 | StarSign bool // 读取星座 221 | } 222 | 223 | ``` 224 | 225 | #### 自定义日历 #### 226 | 227 | `NewCalendar(CalendarConfig)` 228 | 229 | ``` go 230 | c := NewCalendar(CalendarConfig{ 231 | Grid:GridWeek, 232 | FirstWeek:0, 233 | SolarTerms:true, 234 | Lunar:true, 235 | HeavenlyEarthly:true, 236 | NightZiHour:true, 237 | StarSign:true, 238 | }) 239 | 240 | items :=c.GenerateWithDate(2021,12,22) 241 | for _,item := range result { 242 | fmt.Println(item) 243 | } 244 | 245 | ``` 246 | 247 | ``` 248 | 2021-12-19 周日 2021辛丑(牛)年十一月十六 辛丑年庚子月辛丑日 249 | 2021-12-20 周一 2021辛丑(牛)年十一月十七 辛丑年庚子月壬寅日 250 | 2021-12-21 周二 2021辛丑(牛)年十一月十八 辛丑年庚子月癸卯日 冬至 定冬至:2021-12-21T23:59:05+08:00 251 | 2021-12-22 周三 2021辛丑(牛)年十一月十九 辛丑年庚子月甲辰日 252 | 2021-12-23 周四 2021辛丑(牛)年十一月二十 辛丑年庚子月乙巳日 253 | 2021-12-24 周五 2021辛丑(牛)年十一月廿一 辛丑年庚子月丙午日 254 | 2021-12-25 周六 2021辛丑(牛)年十一月廿二 辛丑年庚子月丁未日 圣诞节 255 | ``` 256 | 257 | ### 其它接口 ### 258 | 259 | #### 节气 #### 260 | 261 | > 注意:节气依春分点计算,不同时区因时差不同,定节气时间也会不同 262 | 263 | 全年节气 264 | 265 | `(*Calendar) SolarTerms(year int) []*SolarTerm` 266 | 267 | ``` go 268 | // c := DefaultCalendar() 269 | 270 | // 该package默认为本地时区,如自定议时区 Asia/Shanghai ,这将按中国时区计算节气 271 | c := NewCalendar(CalendarConfig{TimeZoneName:"Asia/Shanghai"}) 272 | sts:= c.SolarTerms(2021) 273 | fmt.Println("2021年节气:") 274 | for _,v := range sts{ 275 | fmt.Printf(" %s 定%s:%s \n", v.Name, v.Name, v.Time.Format(time.RFC3339)) 276 | } 277 | 278 | ``` 279 | 280 | ``` 281 | 2021年节气: 282 | 冬至 定冬至:2020-12-21T18:02:36+08:00 283 | 小寒 定小寒:2021-01-05T11:23:50+08:00 284 | 大寒 定大寒:2021-01-20T04:40:31+08:00 285 | 立春 定立春:2021-02-03T22:59:23+08:00 286 | 雨水 定雨水:2021-02-18T18:44:29+08:00 287 | 惊蛰 定惊蛰:2021-03-05T16:53:57+08:00 288 | 春分 定春分:2021-03-20T17:37:28+08:00 289 | 清明 定清明:2021-04-04T21:34:48+08:00 290 | 谷雨 定谷雨:2021-04-20T04:32:43+08:00 291 | 立夏 定立夏:2021-05-05T14:46:29+08:00 292 | 小满 定小满:2021-05-21T03:36:22+08:00 293 | 芒种 定芒种:2021-06-05T18:51:32+08:00 294 | 夏至 定夏至:2021-06-21T11:31:47+08:00 295 | 小暑 定小暑:2021-07-07T05:05:28+08:00 296 | 大暑 定大暑:2021-07-22T22:26:42+08:00 297 | 立秋 定立秋:2021-08-07T14:54:28+08:00 298 | 处暑 定处暑:2021-08-23T05:35:23+08:00 299 | 白露 定白露:2021-09-07T17:53:16+08:00 300 | 秋分 定秋分:2021-09-23T03:20:56+08:00 301 | 寒露 定寒露:2021-10-08T09:38:45+08:00 302 | 霜降 定霜降:2021-10-23T12:50:30+08:00 303 | 立冬 定立冬:2021-11-07T12:58:14+08:00 304 | 小雪 定小雪:2021-11-22T10:33:05+08:00 305 | 大雪 定大雪:2021-12-07T05:56:49+08:00 306 | 冬至 定冬至:2021-12-21T23:59:05+08:00 307 | 小寒 定小寒:2022-01-05T17:14:07+08:00 308 | ``` 309 | 310 | #### 农历与公历互换 #### 311 | 312 | > 农历以中国(东八区)时区对应公历 313 | 314 | 公历转农历 315 | 316 | `(*Calendar) GregorianToLunar(year, month, day int) LunarDate` 317 | 318 | ``` go 319 | ld := DefaultCalendar().GregorianToLunar(2020,6,5) 320 | fmt.Printf("%d%s%s(%s)年%s%s月%s\n",ld.Year,ld.YearGZ.HSN,ld.YearGZ.EBN,ld.AnimalName,ld.LeapStr,ld.MonthName,ld.DayName) 321 | 322 | ``` 323 | 324 | ``` 325 | 2020庚子(鼠)年闰四月十四 326 | ``` 327 | 328 | 农历转公历 329 | 330 | `(*Calendar) LunarToGregorian(lunarYear,lunarMonth,lunarDay int, isLeap bool) (time.Time, error)` 331 | 332 | ``` go 333 | c := DefaultCalendar() 334 | gd,_ := c.LunarToGregorian(2020,4,14,false) 335 | fmt.Println("农历2020年四月十四转换成公历是:", gd.Format("2006-01-02")) 336 | 337 | gd,_ = c.LunarToGregorian(2020,4,14,true) 338 | fmt.Println("农历2020年闰四月十四转换成公历是:", gd.Format("2006-01-02")) 339 | 340 | ``` 341 | 342 | ``` 343 | 农历2020年四月十四转换成公历是: 2020-05-06 344 | 农历2020年闰四月十四转换成公历是: 2020-06-05 345 | ``` 346 | 347 | #### 早晚子时示例说明 #### 348 | 349 | 干支四柱的子时是23:00-00:00 00:00-01:00 说明每日开始是从上一日的23点开始 350 | 351 | 我们在该日历中引进早晚子时,允许把子时分成晚子时和早子时 352 | 353 | 如果不区分早晚子时,每日将依23点划分,如果区分早晚子时,则依0点划分 354 | 355 | 看早晚子时区分的结果 356 | 357 | ``` go 358 | // NightZiHour:false 不区分早晚子时 359 | c := NewCalendar(CalendarConfig{NightZiHour:false}) 360 | rt := time.Date(2021,5,6,23,50,0,0,time.Local) 361 | gz := c.ChineseSexagenaryCycle(rt) 362 | fmt.Printf("%s%s年%s%s月(%s%s日)%s%s时\n", gz.Year.HSN, gz.Year.EBN, gz.Month.HSN, gz.Month.EBN, gz.Day.HSN, gz.Day.EBN, gz.Hour.HSN, gz.Hour.EBN) 363 | 364 | // NightZiHour:true 区分早晚子时 365 | c = NewCalendar(CalendarConfig{NightZiHour:true}) 366 | rt = time.Date(2021,5,6,23,50,0,0,time.Local) 367 | gz = c.ChineseSexagenaryCycle(rt) 368 | fmt.Printf("%s%s年%s%s月(%s%s日)%s%s时\n", gz.Year.HSN, gz.Year.EBN, gz.Month.HSN, gz.Month.EBN, gz.Day.HSN, gz.Day.EBN, gz.Hour.HSN, gz.Hour.EBN) 369 | 370 | ``` 371 | 372 | > 结果中括号内有不同,该结果只在23点至00点会有所表现 373 | 374 | ``` 375 | 辛丑年癸巳月(乙卯日)丙子时 376 | 辛丑年癸巳月(甲寅日)丙子时 377 | ``` 378 | 379 | #### 公历转换干支 #### 380 | 381 | > 四柱干支以中国(东八区)时区对应公历 382 | 383 | `(*Calendar) ChineseSexagenaryCycle(time.Time)GZ{}` 日期时间对应的干支 384 | 385 | ``` go 386 | rt := time.Date(2021,5,6,23,50,0,0,time.Local) 387 | gz := DefaultCalendar().ChineseSexagenaryCycle(rt) 388 | fmt.Printf("%s%s年%s%s月%s%s日%s%s时\n", gz.Year.HSN, gz.Year.EBN, gz.Month.HSN, gz.Month.EBN, gz.Day.HSN, gz.Day.EBN, gz.Hour.HSN, gz.Hour.EBN) 389 | ``` 390 | 391 | ``` 392 | 辛丑年癸巳月甲寅日丙子时 393 | ``` 394 | 395 | #### 星座 #### 396 | 397 | `StarSign(month,day int)(int, string, error)` 398 | 399 | ``` go 400 | // i,ss,err := StarSign(5,6) 401 | 402 | i,ss,_ := StarSign(5,6) 403 | fmt.Println(ss) 404 | // 金牛 405 | ``` 406 | 407 | #### 儒略日(Julian Day) #### 408 | 409 | 日期时间转儒略日 410 | 411 | `JulianDay(year, month, day float64, timeParts ...float64) float64` 412 | 413 | ``` go 414 | jd := JulianDay(2021,12,6) 415 | // 2459554.5 416 | 417 | jd = JulianDay(2021,12,6,12) 418 | // 2459555 419 | 420 | jd = JulianDay(2021,12,6,12,10,10) 421 | // 2459555.007060185 422 | ``` 423 | 424 | 儒略日转日期时间 425 | 426 | 方法一:`JdToTimeMap(jd float64) map[string]int` 返回一个日期时间map 427 | 428 | 方法二:`JdToTime(jd float64, loc *time.Location) time.Time` 返回time.Time 429 | 430 | ``` go 431 | datetime := JdToTimeMap(2459554.5) 432 | fmt.Printf("%d年%d月%d日\n",datetime["year"], datetime["month"],datetime["day"]) 433 | // 2021年12月6日 434 | 435 | datetime = JdToTimeMap(2459555) 436 | fmt.Printf("%d年%d月%d日%d时\n",datetime["year"], datetime["month"],datetime["day"],datetime["hour"]) 437 | // 2021年12月6日12时 438 | 439 | datetime = JdToTimeMap(2459555.007060185) 440 | fmt.Printf("%d年%d月%d日%d时%d分%d秒\n",datetime["year"], datetime["month"],datetime["day"],datetime["hour"],datetime["minute"],datetime["second"]) 441 | // 2021年12月6日12时10分10秒 442 | 443 | // 方法二 444 | // datetime := JdToTime(2459555.007060185,nil) 445 | datetime := JdToTime(2459555.007060185,time.Local) 446 | fmt.Println(datetime.Format(time.RFC3339)) 447 | // 2021-12-06T20:10:10+08:00 448 | ``` 449 | 450 | #### Modified Julian Day #### 451 | 452 | `Mjd(year, month, day float64, timeParts ...float64) float64` 453 | 454 | ``` go 455 | mjd := Mjd(2021,12,6) 456 | // 59554 457 | 458 | mjd = Mjd(2021,12,6,12) 459 | // 59554.5 460 | 461 | mjd = Mjd(2021,12,6,12,10,10) 462 | // 59554.5070601851 463 | 464 | ``` 465 | 466 | `MjdToTimeMap(mjd float64) map[string]int` 467 | 468 | ``` go 469 | datetime := MjdToTimeMap(59554) 470 | fmt.Printf("%d年%d月%d日\n",datetime["year"], datetime["month"],datetime["day"]) 471 | // 2021年12月6日 472 | 473 | datetime = MjdToTimeMap(59554.5) 474 | fmt.Printf("%d年%d月%d日%d时\n",datetime["year"], datetime["month"],datetime["day"],datetime["hour"]) 475 | // 2021年12月6日12时 476 | 477 | datetime = MjdToTimeMap(59554.507060185075) 478 | fmt.Printf("%d年%d月%d日%d时%d分%d秒\n",datetime["year"], datetime["month"],datetime["day"],datetime["hour"],datetime["minute"],datetime["second"]) 479 | // 2021年12月6日12时10分10秒 480 | ``` 481 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.1.1.211225_beta -------------------------------------------------------------------------------- /astronomy.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | ) 7 | 8 | const ( 9 | // 均值朔望月长(mean length of Synodic Month) 10 | cMSM float64 = 29.530588853 11 | 12 | // 以2000年的第一个均值新月点为基准点,此基准点为2000年1月6日14时20分37秒(TT),其对应真实新月点为2000年1月6日18时13分42秒(TT) 13 | cBNM float64 = 2451550.0976504628 14 | ) 15 | 16 | // deltaTDays 地球自转速度调整值Delta T(以∆T表示) 17 | // 18 | // 地球时和UTC的时差 单位:天(days) 19 | func deltaTDays(year,month float64) float64 { 20 | dt,err := deltaTSeconds(year,month) 21 | if err != nil { 22 | return 0 23 | } 24 | 25 | return Round(dt / 60.0 / 60.0 / 24.0,16) 26 | } 27 | 28 | // deltaTMinutes 地球自转速度调整值Delta T(以∆T表示) 29 | // 30 | // 地球时和UTC的时差 单位:分(minutes) 31 | func deltaTMinutes(year,month float64) float64 { 32 | dt,err := deltaTSeconds(year,month) 33 | if err != nil { 34 | return 0 35 | } 36 | 37 | return Round(dt / 60.0,16) 38 | } 39 | 40 | // deltaTSeconds 地球自转速度调整值Delta T(以∆T表示) 41 | // 42 | // 地球时和UTC的时差 单位:秒(seconds) 43 | // 精确至月份 44 | func deltaTSeconds(year,month float64) (float64,error) { 45 | // 计算方法参考: https://eclipse.gsfc.nasa.gov/SEhelp/deltatpoly2004.html 46 | // 此算法在-1999年到3000年之间有效 47 | 48 | if year < -1999 || year > 3000 { 49 | return 0,errors.New("计算DeltaT值限-1999年至3000年之间有效") 50 | } 51 | 52 | y := year + (month - 0.5) / 12 53 | 54 | var dt float64 55 | 56 | switch { 57 | case year <= -500: 58 | u := (year - 1820) / 100 59 | dt = -20 + 32 * math.Pow(u, 2) 60 | case year < 500: 61 | u := y / 100 62 | dt = 10583.6 - 1014.41*u + 33.78311*math.Pow(u, 2) - 5.952053*math.Pow(u, 3) - 0.1798452*math.Pow(u, 4) + 0.022174192*math.Pow(u, 5) + 0.0090316521*math.Pow(u, 6) 63 | case year < 1600: 64 | u := (y - 1000) / 100 65 | dt = 1574.2 - 556.01*u + 71.23472*math.Pow(u, 2) + 0.319781*math.Pow(u, 3) - 0.8503463*math.Pow(u, 4) - 0.005050998*math.Pow(u, 5) + 0.0083572073*math.Pow(u, 6) 66 | case year < 1700: 67 | t := y - 1600 68 | dt = 120 - 0.9808*t - 0.01532*math.Pow(t, 2) + math.Pow(t, 3)/7129 69 | case year < 1800: 70 | t := y - 1700 71 | dt = 8.83 + 0.1603*t - 0.0059285*math.Pow(t, 2) + 0.00013336*math.Pow(t, 3) - math.Pow(t, 4)/1174000 72 | case year < 1860: 73 | t := y - 1800 74 | dt = 13.72 - 0.332447*t + 0.0068612*math.Pow(t, 2) + 0.0041116*math.Pow(t, 3) - 0.00037436*math.Pow(t, 4) + 0.0000121272*math.Pow(t, 5) - 0.0000001699*math.Pow(t, 6) + 0.000000000875*math.Pow(t, 7) 75 | case year < 1900: 76 | t := y - 1860 77 | dt = 7.62 + 0.5737*t - 0.251754*math.Pow(t, 2) + 0.01680668*math.Pow(t, 3) - 0.0004473624*math.Pow(t, 4) + math.Pow(t, 5)/233174 78 | case year < 1920: 79 | t := y - 1900 80 | dt = -2.79 + 1.494119*t - 0.0598939*math.Pow(t, 2) + 0.0061966*math.Pow(t, 3) - 0.000197*math.Pow(t, 4) 81 | case year < 1941: 82 | t := y - 1920 83 | dt = 21.2 + 0.84493*t - 0.0761*math.Pow(t, 2) + 0.0020936*math.Pow(t, 3) 84 | case year < 1961: 85 | t := y - 1950 86 | dt = 29.07 + 0.407*t - math.Pow(t, 2)/233 + math.Pow(t, 3)/2547 87 | case year < 1986: 88 | t := y - 1975 89 | dt = 45.45 + 1.067*t - math.Pow(t, 2)/260 - math.Pow(t, 3)/718 90 | case year < 2005: 91 | t := y - 2000 92 | dt = 63.86 + 0.3345*t - 0.060374*math.Pow(t, 2) + 0.0017275*math.Pow(t, 3) + 0.000651814*math.Pow(t, 4) + 0.00002373599*math.Pow(t, 5) 93 | case year < 2050: 94 | t := y - 2000 95 | dt = 62.92 + 0.32217*t + 0.005589*math.Pow(t, 2) 96 | case year < 2150: 97 | u := (y - 1820) / 100 98 | dt = -20 + 32*math.Pow(u, 2) - 0.5628*(2150-y) 99 | default: 100 | u := (y - 1820) / 100 101 | dt = -20 + 32*math.Pow(u, 2) 102 | } 103 | 104 | // 以上的∆T值均假定月球的长期加速度为-26弧秒/cy^2 105 | // 而Canon中使用的ELP-2000/82月历使用的值略有不同,为-25.858弧秒/cy^2 106 | // 因此,必须在∆T多项式表达式得出的值上加上一个小的修正“c”,然后才能将其用于标准中 107 | // 由于1955年至2005年期间的ΔT值是独立于任何月历而得出的,因此该期间无需校正。 108 | if year < 1955 || year >= 2005 { 109 | c := -0.000012932 * (y - 1955) * (y - 1955) 110 | dt += c 111 | } 112 | 113 | return dt, nil 114 | } 115 | 116 | 117 | // perturbation 地球在绕日运行时会因受到其他星球之影响而产生摄动(perturbation) 118 | // 119 | // 返回某时刻(儒略日历)的摄动偏移量 120 | func perturbation(jd float64) float64 { 121 | // 算法公式摘自Jean Meeus在1991年出版的《Astronomical Algorithms》第27章 Equinoxes and solsticesq (第177页) 122 | // http://www.agopax.it/Libri_astronomia/pdf/Astronomical%20Algorithms.pdf 123 | // 公式: 0.00001S/∆λ 124 | // S = Σ[A cos(B+CT)] 125 | // B和C的单位是度 126 | // T = JDE0 - J2000 / 36525 127 | // J2000 = 2451545.0 128 | // 36525是儒略历一个世纪的天数 129 | // ∆λ = 1 + 0.0334cosW+0.0007cos2W 130 | // W = (35999.373T - 2.47)π/180 131 | // 注释: Liu Min https://github.com/liujiawm 132 | 133 | // 公式中A,B,C的值 134 | ptsA := [24]float64{485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12, 12, 9, 8} 135 | ptsB := [24]float64{324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81, 297.17, 21.02, 247.54, 325.15,60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73, 15.45} 136 | ptsC := [24]float64{1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906, 9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029, 31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074} 137 | 138 | T := julianCentury(jd) // T是以儒略世纪(36525日)为单位,以J2000(儒略日2451545.0)为0点 139 | 140 | var s float64 = 0 141 | 142 | for k := 0; k <= 23; k++ { 143 | s += ptsA[k] * math.Cos(ptsB[k]*2*math.Pi/360+ptsC[k]*2*math.Pi/360*T) 144 | } 145 | 146 | W := (35999.373*T - 2.47)*2*math.Pi/360 147 | 148 | L := 1 + 0.0334*math.Cos(W) + 0.0007*math.Cos(2*W) 149 | 150 | return Round(0.00001*s/L, 16) 151 | } 152 | 153 | // vernalEquinox 计算指定年的春分点 154 | func vernalEquinox(year float64) float64 { 155 | // 算法公式摘自Jean Meeus在1991年出版的《Astronomical Algorithms》第27章 Equinoxes and solsticesq (第177页) 156 | // http://www.agopax.it/Libri_astronomia/pdf/Astronomical%20Algorithms.pdf 157 | // 此公式在-1000年至3000年之间比较准确 158 | // 在公元前1000年之前或公元3000年之后也可以延申使用,但因外差法求值,年代越远,算出的结果误差就越大。 159 | 160 | var ve float64 161 | if year >= 1000 && year <= 3000 { 162 | m := (year - 2000) / 1000 163 | ve = 2451623.80984 + 365242.37404*m + 0.05169*math.Pow(m, 2) - 0.00411*math.Pow(m, 3) - 0.00057*math.Pow(m, 4) 164 | }else{ 165 | m := year / 1000 166 | ve = 1721139.29189 + 365242.1374*m + 0.06134*math.Pow(m, 2) + 0.00111*math.Pow(m, 3) - 0.00071*math.Pow(m, 4) 167 | } 168 | 169 | return Round(ve, 10) 170 | } 171 | 172 | // trueNewMoon 求出实际新月点 173 | // 174 | // 以2000年初的第一个均值新月点为0点求出的均值新月点和其朔望月之序數 k 代入此副程式來求算实际新月点 175 | func trueNewMoon(k float64) float64 { 176 | // 对于指定的日期时刻JD值jd,算出其为相对于基准点(之后或之前)的第k个朔望月之内。 177 | // k=INT(jd-bnm)/msm 178 | // 新月点估值(new moon estimated)为:nme=bnm+msm×k 179 | // 估计的世纪变数值为:t = (nme - J2000) / 36525 180 | // 此t是以2000年1月1日12时(TT)为0点,以100年为单位的时间变数, 181 | // 由于朔望月长每个月都不同,msm所代表的只是其均值,所以算出新月点后,还需要加上一个调整值。 182 | // adj = 0.0001337×t×t - 0.00000015×t×t×t + 0.00000000073×t×t×t×t 183 | // 指定日期时刻所属的均值新月点JD值(mean new moon):mnm=nme+adj 184 | 185 | nme := newMoonEstimated(k) 186 | 187 | t := julianCentury(nme) 188 | t2 := math.Pow(t, 2) // square for frequent use 189 | t3 := math.Pow(t, 3) // cube for frequent use 190 | t4 := math.Pow(t, 4) // to the fourth 191 | 192 | // mean time of phase 193 | mnm := nme + 0.0001337 * t2 - 0.00000015 * t3 + 0.00000000073 * t4 194 | 195 | // Sun's mean anomaly(地球绕太阳运行均值近点角)(从太阳观察) 196 | m := 2.5534 + 29.10535669*k - 0.0000218*t2 - 0.00000011*t3 197 | 198 | // Moon's mean anomaly(月球绕地球运行均值近点角)(从地球观察) 199 | ms := 201.5643 + 385.81693528*k + 0.0107438*t2 + 0.00001239*t3 - 0.000000058*t4 200 | 201 | // Moon's argument of latitude(月球的纬度参数) 202 | f := 160.7108 + 390.67050274*k - 0.0016341*t2 - 0.00000227*t3 + 0.000000011*t4 203 | 204 | // Longitude of the ascending node of the lunar orbit(月球绕日运行轨道升交点之经度) 205 | omega := 124.7746 - 1.5637558*k + 0.0020691*t2 + 0.00000215*t3 206 | 207 | // 乘式因子 208 | e := 1 - 0.002516*t - 0.0000074*t2 209 | 210 | // 因perturbation造成的偏移 211 | pi180 := math.Pi / 180 212 | apt1 := -0.4072 * math.Sin(pi180*ms) 213 | apt1 += 0.17241 * e * math.Sin(pi180*m) 214 | apt1 += 0.01608 * math.Sin(pi180*2*ms) 215 | apt1 += 0.01039 * math.Sin(pi180*2*f) 216 | apt1 += 0.00739 * e * math.Sin(pi180*(ms-m)) 217 | apt1 -= 0.00514 * e * math.Sin(pi180*(ms+m)) 218 | apt1 += 0.00208 * e * e * math.Sin(pi180*(2*m)) 219 | apt1 -= 0.00111 * math.Sin(pi180*(ms-2*f)) 220 | apt1 -= 0.00057 * math.Sin(pi180*(ms+2*f)) 221 | apt1 += 0.00056 * e * math.Sin(pi180*(2*ms+m)) 222 | apt1 -= 0.00042 * math.Sin(pi180*3*ms) 223 | apt1 += 0.00042 * e * math.Sin(pi180*(m+2*f)) 224 | apt1 += 0.00038 * e * math.Sin(pi180*(m-2*f)) 225 | apt1 -= 0.00024 * e * math.Sin(pi180*(2*ms-m)) 226 | apt1 -= 0.00017 * math.Sin(pi180*omega) 227 | apt1 -= 0.00007 * math.Sin(pi180*(ms+2*m)) 228 | apt1 += 0.00004 * math.Sin(pi180*(2*ms-2*f)) 229 | apt1 += 0.00004 * math.Sin(pi180*(3*m)) 230 | apt1 += 0.00003 * math.Sin(pi180*(ms+m-2*f)) 231 | apt1 += 0.00003 * math.Sin(pi180*(2*ms+2*f)) 232 | apt1 -= 0.00003 * math.Sin(pi180*(ms+m+2*f)) 233 | apt1 += 0.00003 * math.Sin(pi180*(ms-m+2*f)) 234 | apt1 -= 0.00002 * math.Sin(pi180*(ms-m-2*f)) 235 | apt1 -= 0.00002 * math.Sin(pi180*(3*ms+m)) 236 | apt1 += 0.00002 * math.Sin(pi180*(4*ms)) 237 | 238 | apt2 := 0.000325 * math.Sin(pi180*(299.77+0.107408*k-0.009173*t2)) 239 | apt2 += 0.000165 * math.Sin(pi180*(251.88+0.016321*k)) 240 | apt2 += 0.000164 * math.Sin(pi180*(251.83+26.651886*k)) 241 | apt2 += 0.000126 * math.Sin(pi180*(349.42+36.412478*k)) 242 | apt2 += 0.00011 * math.Sin(pi180*(84.66+18.206239*k)) 243 | apt2 += 0.000062 * math.Sin(pi180*(141.74+53.303771*k)) 244 | apt2 += 0.00006 * math.Sin(pi180*(207.14+2.453732*k)) 245 | apt2 += 0.000056 * math.Sin(pi180*(154.84+7.30686*k)) 246 | apt2 += 0.000047 * math.Sin(pi180*(34.52+27.261239*k)) 247 | apt2 += 0.000042 * math.Sin(pi180*(207.19+0.121824*k)) 248 | apt2 += 0.00004 * math.Sin(pi180*(291.34+1.844379*k)) 249 | apt2 += 0.000037 * math.Sin(pi180*(161.72+24.198154*k)) 250 | apt2 += 0.000035 * math.Sin(pi180*(239.56+25.513099*k)) 251 | apt2 += 0.000023 * math.Sin(pi180*(331.55+3.592518*k)) 252 | 253 | return Round(mnm + apt1 + apt2,10) 254 | } 255 | 256 | // referenceLunarMonthNum 对于指定的日期时刻JD值jd,算出其为相对于基准点(之后或之前)的第几个朔望月 257 | // 258 | // 为从2000年1月6日14时20分36秒起至指定年月日之农历月数,以synodic month为单位 259 | func referenceLunarMonthNum(jd float64) float64{ 260 | return math.Floor((jd - cBNM) / cMSM) 261 | } 262 | 263 | // newMoonEstimated 新月点估值(new moon estimated) 264 | func newMoonEstimated(k float64) float64 { 265 | // 新月点估值(new moon estimated)为:nme=bnm+msm×k 266 | return cBNM + cMSM * k 267 | } 268 | 269 | // meanNewMoon 对于指定日期时刻所属的朔望月,求出其均值新月点的月序数 270 | func meanNewMoon(jd float64) (float64, float64) { 271 | k := referenceLunarMonthNum(jd) 272 | 273 | nme := newMoonEstimated(k) 274 | 275 | // Time in Julian centuries from 2000 January 0.5. 276 | t := julianCentury(nme) 277 | theJd := nme + 0.0001337*math.Pow(t, 2) - 0.00000015*math.Pow(t, 3) + 0.00000000073*math.Pow(t, 4) 278 | 279 | return k, Round(theJd,10) 280 | } 281 | 282 | // meanSolarTermsJd 获取指定年以春分开始的24节气(为了确保覆盖完一个公历年,该方法多取2个节气) 283 | // 284 | // 注意:该方法取出的节气时间是未经微调的 285 | func meanSolarTermsJd(year float64) [26]float64 { 286 | 287 | // 该年的春分点jd 288 | ve := vernalEquinox(year) 289 | 290 | // 该年的回归年长(天) 291 | // 两个春分点之间为一个回归年长 292 | ty := vernalEquinox(year + 1) - ve 293 | 294 | ath := 2 * math.Pi / 24 295 | 296 | T := julianThousandYear(ve) 297 | e := 0.0167086342 - 0.0004203654*T - 0.0000126734*math.Pow(T,2) + 0.0000001444*math.Pow(T,3) - 0.0000000002*math.Pow(T,4) + 0.0000000003*math.Pow(T,5) 298 | 299 | TT := year / 1000 300 | d := 111.25586939 - 17.0119934518333*TT - 0.044091890166673*math.Pow(TT,2) - 4.37356166661345E-04*math.Pow(TT,3) + 8.16716666602386E-06*math.Pow(TT,4) 301 | 302 | rvp := d * 2 * math.Pi / 360 303 | 304 | var peri [26]float64 305 | 306 | for i := 0; i < cap(peri); i++ { 307 | flag := 0 308 | th := ath*float64(i) + rvp 309 | 310 | if th > math.Pi && th <= 3*math.Pi { 311 | th = 2*math.Pi - th 312 | flag = 1 313 | } 314 | 315 | if th > 3*math.Pi { 316 | th = 4*math.Pi - th 317 | flag = 2 318 | } 319 | 320 | f1 := 2 * math.Atan(math.Sqrt((1-e)/(1+e))*math.Tan(th/2)) 321 | 322 | f2 := (e * math.Sqrt(1-e*e) * math.Sin(th)) / (1 + e*math.Cos(th)) 323 | 324 | f := (f1 - f2) * ty / 2 / math.Pi 325 | 326 | if flag == 1 { 327 | f = ty - f 328 | } 329 | if flag == 2 { 330 | f = 2*ty - f 331 | } 332 | peri[i] = f 333 | } 334 | 335 | var mst [26]float64 336 | 337 | for i := 0; i < cap(peri); i++ { 338 | mst[i] = Round(ve+peri[i]-peri[0], 10) 339 | } 340 | 341 | return mst 342 | } 343 | 344 | // adjustedSolarTermsJd 获取指定年以春分开始的节气 345 | // 346 | // 经过摄动值和deltaT调整后的jd 347 | func adjustedSolarTermsJd(year float64,start, end int) [26]float64 { 348 | mst := meanSolarTermsJd(year) 349 | 350 | var jqs [26]float64 351 | 352 | for i, jd := range mst { 353 | if i < start { 354 | continue 355 | } 356 | if i > end { 357 | continue 358 | } 359 | 360 | // 取得受perturbation影响所需微调 361 | pert := perturbation(jd) // perturbation 362 | 363 | // 修正dynamical time to Universal time 364 | month := math.Floor((float64(i)+1)/2) + 3 365 | dtd := deltaTDays(year, month) // delta T(天) 366 | 367 | jqs[i] = Round(jd+pert-dtd, 10) // 加上摄动调整值ptb,减去对应的Delta T值(分钟转换为日) 368 | } 369 | 370 | return jqs 371 | } 372 | 373 | // lastYearSolarTerms 取出上一年从冬至开始的6个节气 374 | func lastYearSolarTerms(year float64) [26]float64 { 375 | return adjustedSolarTermsJd(year-1,18,23) 376 | } 377 | 378 | 379 | -------------------------------------------------------------------------------- /astronomy_test.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | 8 | func TestDeltaTDays(t *testing.T) { 9 | dtd := deltaTDays(2021,12) 10 | // 0.0008406386097956 11 | if Round(dtd,10) == 0.0008406386 { 12 | t.Log("passed") 13 | }else{ 14 | t.Error(dtd) 15 | } 16 | 17 | } 18 | 19 | 20 | func TestDeltaTMinutes(t *testing.T) { 21 | dtmi := deltaTMinutes(2021,12) 22 | // 1.2105195981056707 23 | if Round(dtmi,10) == 1.2105195981 { 24 | t.Log("passed") 25 | }else{ 26 | t.Error(dtmi) 27 | } 28 | 29 | } 30 | 31 | 32 | func TestPerturbation(t *testing.T){ 33 | var jd float64 = 2298519 34 | pt := perturbation(jd) 35 | // -0.0056141748866219 36 | if Round(pt,10) == -0.0056141749 { 37 | t.Log("passed") 38 | }else{ 39 | t.Error(pt) 40 | } 41 | } 42 | 43 | func TestVernalEquinox(t *testing.T){ 44 | ve := vernalEquinox(2021) 45 | if Round(ve,10) == 2459293.8997175973 { 46 | t.Log("passed") 47 | }else{ 48 | t.Error(ve) 49 | } 50 | } 51 | 52 | func TestMeanSolarTerms(t *testing.T){ 53 | mst := meanSolarTermsJd(2021) 54 | if Round(mst[0],10) == 2459293.8997175973 && Round(mst[1],10) == 2459309.060708575 && mst[2] == 2459324.354092278 && Round(mst[24],10) == 2459659.142093854 && Round(mst[25],10) == 2459674.3030848317 { 55 | t.Log("passed") 56 | }else{ 57 | t.Error(mst) 58 | } 59 | } 60 | 61 | func TestTrueNewMoon(t *testing.T){ 62 | var jd float64 = 2298519 63 | k := referenceLunarMonthNum(jd) 64 | tnm := trueNewMoon(k) 65 | if Round(tnm,10) == 2298493.2989711817 { 66 | t.Log("passed") 67 | }else{ 68 | t.Error(tnm) 69 | } 70 | } 71 | 72 | func TestAdjustedSolarTermsJd(t *testing.T){ 73 | jqs := adjustedSolarTermsJd(2021,0,25) 74 | 75 | if Round(jqs[0],10) == 2459293.9010286564 && Round(jqs[1],10) == 2459309.0658356417 && jqs[2] == 2459324.356054907 && Round(jqs[24],10) == 2459659.1481248834 && Round(jqs[25],10) == 2459674.3054912435 { 76 | t.Log("passed") 77 | }else{ 78 | t.Error(jqs) 79 | } 80 | } 81 | 82 | func TestLastYearSolarTerms(t *testing.T){ 83 | ljqs := lastYearSolarTerms(2021) 84 | if ljqs[0] == 0 && ljqs[17] == 0 && ljqs[24] == 0 && Round(ljqs[18],10) == 2459204.9184778044 && Round(ljqs[23],10) == 2459278.8707997804 { 85 | t.Log("passed") 86 | }else{ 87 | t.Error(ljqs) 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /calendar.go: -------------------------------------------------------------------------------- 1 | /** 2 | 一个用golang写的日历,有公历转农历,农历转公历,节气,干支,星座,生肖等功能 3 | 中国的农历历法综合了太阳历和月亮历,为中国的生活生产提供了重要的帮助,是中国古人智慧与中国传统文化的一个重要体现 4 | 5 | 程序比较准确的计算出农历与二十四节气(精确到分),时间限制在-1000至3000年间,在实际使用中注意限制年份 6 | */ 7 | 8 | package gocalendar 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "math" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | "sync" 18 | "time" 19 | ) 20 | 21 | const Author = "liujiawm@gmail.com" 22 | const Version = "1.1.1.211225_beta" 23 | 24 | // type SolarTermItem struct 节气 25 | type SolarTermItem struct { 26 | Index int `json:"index"` // 节气索引 27 | Name string `json:"name"` // 节气名称 28 | Time *time.Time `json:"time"` // 定节气时间 29 | } 30 | 31 | // type yearSolarTermTemp struct 节气缓存年表 32 | type yearSolarTermTemp struct { 33 | data map[int][]*SolarTermItem 34 | mu sync.RWMutex 35 | } 36 | 37 | // type StarSignItem struct 星座单元 38 | type StarSignItem struct { 39 | Index int `json:"index"` // 星座索引 40 | Name string `json:"name"` // 星座名称 41 | } 42 | 43 | // type FestivalItem struct 节日单元 44 | type FestivalItem struct { 45 | Show []string `json:"show"` // 在日历表上显示的节日 46 | Secondary []string `json:"scdr"` // 其它次要节日 47 | } 48 | 49 | // type yearFestivalTemp struct 节日缓存年表 50 | type yearFestivalTemp struct { 51 | data map[int]map[string][]string 52 | mu sync.RWMutex 53 | } 54 | 55 | // type CalendarItem struct 日历单元 56 | type CalendarItem struct { 57 | Time *time.Time `json:"time"` // 格里高历(公历)时间 58 | IsAccidental int `json:"isam"` // 0为本月日期,-1为上一个月的日期,1为下一个月的日期, 59 | IsToday int `json:"istoday"` // 是否是今天,0不是,1是 60 | Festival *FestivalItem `json:"festival"` // 公历节日 61 | SolarTerm *SolarTermItem `json:"st"` // 节气 62 | GZ *GZ `json:"gz"` // 干支 63 | LunarDate *LunarDate `json:"ld"` // 农历 64 | StarSign *StarSignItem `json:"ss"` // 星座 65 | } 66 | 67 | // Calendar的一些临时数据 68 | type CalendarTempData struct { 69 | st *yearSolarTermTemp // 一整年的节气(24)加上一年最后一个和下一年第一个,共26个节气 70 | jSS *pureJieQi16Temp // 对应公历某年立春点开始的16个节 71 | qSS *pureJieQi16Temp // 对应公历某年冬至开始的16个中气 72 | tNM *trueNewMoon20Temp // 以应公历某年连续20个朔望月 73 | lMC *lunarMonthCode15Temp // 对应农历某年的月份表 74 | lMD *lunarMonthDays15Temp // 对应农历某年的月份对应天数表 75 | lFD *yearFestivalTemp // 对应农历某年的节日表 76 | gFD *yearFestivalTemp // 对应公历某年的节日表 77 | } 78 | 79 | // 初始Calendar的临时数据 80 | func newCalendarTempData() *CalendarTempData { 81 | return &CalendarTempData{ 82 | st: new(yearSolarTermTemp), 83 | jSS: new(pureJieQi16Temp), 84 | qSS: new(pureJieQi16Temp), 85 | tNM: new(trueNewMoon20Temp), 86 | lMC: new(lunarMonthCode15Temp), 87 | lMD: new(lunarMonthDays15Temp), 88 | lFD: new(yearFestivalTemp), 89 | gFD: new(yearFestivalTemp), 90 | } 91 | } 92 | 93 | // type Calendar struct 日历表 94 | type Calendar struct { 95 | Items []*CalendarItem `json:"items"` // 日历表中所有的日期单元 96 | config *CalendarConfig // 配置 97 | loc *time.Location // time.Location 默认time.Local 98 | rawTime *time.Time // 初始时间,指定的时间 99 | tempData *CalendarTempData // 缓存数据 100 | } 101 | 102 | var ( 103 | lunarLeapString = "闰" // 农历闰月标志 104 | 105 | // 星期 106 | weekNameArray = [7]string{"日", "一", "二", "三", "四", "五", "六"} 107 | 108 | // 农历整十日期常用称呼 109 | lunarWholeTensArray = [4]string{"初", "十", "廿", "卅"} 110 | 111 | // 农历日期数字 112 | lunarNumberArray = [11]string{"日", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十"} 113 | 114 | // 农历月份常用称呼 115 | lunarMonthNameArray = [12]string{"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"} 116 | 117 | // 天干 118 | heavenlyStemsNameArray = [10]string{"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"} 119 | 120 | // 地支 121 | earthlyBranchesNameArray = [12]string{"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"} 122 | 123 | // 生肖 124 | symbolicAnimalsNameArray = [12]string{"鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"} 125 | 126 | // 星座名称 127 | starSignsNameArray = [12]string{"水瓶", "双鱼", "白羊", "金牛", "双子", "巨蟹", "狮子", "处女", "天秤", "天蝎", "射手", "摩羯"} 128 | 129 | // 节气名称 130 | solarTermsNameArray = [24]string{"春分", "清明", "谷雨", "立夏", "小满", "芒种", "夏至", "小暑", "大暑", "立秋", "处暑", "白露", 131 | "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至", "小寒", "大寒", "立春", "雨水", "惊蛰"} 132 | 133 | // 农历节日,map的索引M表示月,D表示日,如8M15D表示8月15日,节日名称前加"*"号表示重要且在日历表上显示,同一天多个节日用","分隔 134 | // 支持两个特殊字符$和@, 用M$表示该月最后一日,如12M$ 表示12月最后一日, 用@M表示闰月,如 5@M12D 表示闰5月12日. 135 | lunarFestivalArray = map[string]string{"1M1D": "*春节", "1M15D": "*元宵节", "5M5D": "*端午节", "7M7D": "*七夕节", "7M15D": "*中元节", 136 | "8M15D": "*中秋节", "9M9D": "*重阳节", "12M8D": "*腊八节", "12M24D": "*小年", "12M$": "*除夕"} 137 | 138 | // 公历节日,map的索引M表示月,D表示日,如2M14D表示2月14日,节日名称前加"*"号表示重要且在日历表上显示,同一天多个节日用","分隔 139 | // 支持某月第几个周几这种方式定义节日,如"5M2W0"表示5月第2个周日;0周日,1周一,2周二...6周六 140 | gregorianFestivalArray = map[string]string{"1M1D": "*元旦", "2M14D": "*情人节", "3M8D": "*妇女节", "3M12D": "*植树节", "3M15D": "世界消费者权益日", 141 | "4M1D": "*愚人节", "5M1D": "*劳动节", "5M4D": "*青年节", "5M12D": "*护士节", "5M2W0": "*母亲节", "5M31D": "世界无烟日", "6M1D": "*儿童节", "6M5D": "世界环境日", 142 | "6M3W0": "*父亲节", "6M26D": "国际禁毒日", "7M7D": "抗战纪念日", "9M10D": "*教师节", "10M1D": "*国庆节", "11M1D": "*万圣节", "12M1D": "世界爱滋病日", 143 | "12M25D": "*圣诞节"} 144 | ) 145 | 146 | // DefaultCalendar 默认日历设置 147 | func DefaultCalendar() *Calendar { 148 | 149 | return NewCalendar(defaultConfig()) 150 | } 151 | 152 | // NewCalendar 日历设置 153 | func NewCalendar(cfg CalendarConfig) *Calendar { 154 | 155 | cfg.Grid = int(math.Mod(math.Abs(float64(cfg.Grid)), 3)) 156 | cfg.FirstWeek = int(math.Mod(math.Abs(float64(cfg.FirstWeek)), 7)) 157 | 158 | // 默认时区 159 | var loc *time.Location 160 | 161 | // TimeZoneName的有效性 162 | if cfg.TimeZoneName == "" { 163 | loc = time.Local 164 | cfg.TimeZoneName = loc.String() 165 | } else { 166 | var err error 167 | loc, err = time.LoadLocation(cfg.TimeZoneName) 168 | if err != nil { 169 | loc = time.Local 170 | cfg.TimeZoneName = loc.String() 171 | } 172 | } 173 | 174 | // 默认 rawTime 175 | rawTime := time.Now().In(loc) 176 | 177 | return &Calendar{ 178 | Items: nil, 179 | config: &cfg, 180 | loc: loc, 181 | rawTime: &rawTime, 182 | tempData: newCalendarTempData(), 183 | } 184 | } 185 | 186 | // (*Calendar) SetRawTime 设置rawTime 187 | // 188 | // 该方法返回的*Calendar是清除与rawTime相关值的c *Calendar,这样做是为了支持链接使用, 189 | // 因此该方法必需在相关取值之前使用 190 | func (c *Calendar) SetRawTime(year, month, day int, timeParts ...int) *Calendar { 191 | var hour, minute,second, millisecond = 0, 0, 0, 0 192 | for timeIndex, timePart := range timeParts { 193 | switch timeIndex { 194 | case 0: 195 | hour = timePart 196 | case 1: 197 | minute = timePart 198 | case 2: 199 | second = timePart 200 | case 3: 201 | millisecond = timePart 202 | } 203 | } 204 | 205 | t := time.Date(year, time.Month(month), day, hour, minute, second, millisecond, c.loc) 206 | 207 | return c.setRawTime(t) 208 | } 209 | 210 | // (*Calendar) setRawTime 设置rawTime 211 | func (c *Calendar) setRawTime(t time.Time) *Calendar { 212 | rawYear := c.GetRawTime().Year() 213 | 214 | t = t.In(c.loc) // 使用c.loc重新设置t的时区 215 | c.rawTime = &t 216 | 217 | // 重新设置rawTime后,请除原相关数据 218 | c.Items = nil 219 | 220 | // 清临时数据 221 | if rawYear != t.Year() { 222 | c.tempData = newCalendarTempData() 223 | } 224 | 225 | return c 226 | } 227 | 228 | // (*Calendar) GetRawTime 取出rawTime 229 | // 230 | // 返回的将是c.rawTime的一个clone 231 | func (c *Calendar) GetRawTime() time.Time { 232 | if c.rawTime == nil { 233 | now := time.Now() 234 | c.rawTime = &now 235 | } 236 | 237 | return c.rawTime.AddDate(0, 0, 0) // 使用Time.AddDate(0,0,0)的目的是为了clone一个时间 238 | } 239 | 240 | // (*Calendar) GenerateWithDate 用指定的年月日时分秒生成日历表 241 | func (c *Calendar) GenerateWithDate(year, month, day int, timeParts ...int) []*CalendarItem { 242 | return c.SetRawTime(year, month, day, timeParts...).Generate() 243 | } 244 | 245 | // (*Calendar) NextMonth 下一个月 246 | func (c *Calendar) NextMonth() []*CalendarItem { 247 | t := c.GetRawTime().AddDate(0,1,0) 248 | c.setRawTime(t) 249 | return c.Generate() 250 | } 251 | 252 | // (*Calendar) PreviousMonth 上一个月 253 | func (c *Calendar) PreviousMonth() []*CalendarItem { 254 | t := c.GetRawTime().AddDate(0,-1,0) 255 | c.setRawTime(t) 256 | return c.Generate() 257 | } 258 | 259 | // (*Calendar) NextYear 下一年 260 | func (c *Calendar) NextYear() []*CalendarItem { 261 | t := c.GetRawTime().AddDate(1,0,0) 262 | c.setRawTime(t) 263 | return c.Generate() 264 | } 265 | 266 | // (*Calendar) PreviousYear 上一年 267 | func (c *Calendar) PreviousYear() []*CalendarItem { 268 | t := c.GetRawTime().AddDate(-1,0,0) 269 | c.setRawTime(t) 270 | return c.Generate() 271 | } 272 | 273 | // (*Calendar) Generate 生成日历表 274 | func (c *Calendar) Generate() []*CalendarItem { 275 | grid := c.config.Grid 276 | var result []*CalendarItem 277 | switch grid { 278 | case GridDay: 279 | // 天日历表 280 | rt := c.GetRawTime() 281 | ry := rt.Year() 282 | rm := int(rt.Month()) 283 | ci := c.createItem(rt,ry,rm) 284 | result = append(result,ci) 285 | case GridWeek: 286 | // 周日历表 287 | result = c.weekCalendar() 288 | case GridMonth: 289 | // 月日历表 290 | result = c.monthCalendar() 291 | default: 292 | // 月日历表 293 | result = c.monthCalendar() 294 | } 295 | 296 | return result 297 | } 298 | 299 | // (*Calendar) weekGregorianCalendar 一周的日历表 300 | func (c *Calendar) weekCalendar() []*CalendarItem { 301 | 302 | rawTime := c.GetRawTime() 303 | 304 | // 本月 305 | currentMonth := int(rawTime.Month()) 306 | currentYear := rawTime.Year() 307 | 308 | // 日历表首日 309 | _, firstDayTime := c.firstDay(rawTime) 310 | 311 | // item 312 | var itemsArray [7]*CalendarItem 313 | var wg = sync.WaitGroup{} 314 | wg.Add(cap(itemsArray)) 315 | for i := 0; i < cap(itemsArray); i++ { 316 | go func(i int) { 317 | defer wg.Done() 318 | 319 | gt := firstDayTime.AddDate(0, 0, i) 320 | itemsArray[i] = c.createItem(gt, currentYear, currentMonth) 321 | }(i) 322 | } 323 | wg.Wait() 324 | 325 | var itemsSlice []*CalendarItem 326 | itemsSlice = itemsArray[:] 327 | 328 | // 附值给Calendar.Items 329 | c.Items = itemsSlice 330 | 331 | // copy一个返回 332 | var resultItems = make([]*CalendarItem, len(itemsSlice)) 333 | copy(resultItems, itemsSlice) 334 | 335 | return itemsSlice 336 | } 337 | 338 | // (*Calendar) monthGregorianCalendar 一个月的日历表 339 | func (c *Calendar) monthCalendar() []*CalendarItem { 340 | 341 | rawTime := c.GetRawTime() 342 | 343 | // 本月第一天time 344 | t := BeginningOfMonth(rawTime) 345 | 346 | // 本月 347 | currentMonth := int(t.Month()) 348 | currentYear := t.Year() 349 | 350 | // 日历表首日 351 | _, firstDayTime := c.firstDay(t) 352 | 353 | // item 354 | var itemsArray [42]*CalendarItem 355 | var wg = sync.WaitGroup{} 356 | wg.Add(cap(itemsArray)) 357 | for i := 0; i < cap(itemsArray); i++ { 358 | go func(i int) { 359 | defer wg.Done() 360 | 361 | gt := firstDayTime.AddDate(0, 0, i) 362 | itemsArray[i] = c.createItem(gt, currentYear, currentMonth) 363 | }(i) 364 | } 365 | wg.Wait() 366 | 367 | var itemsSlice []*CalendarItem 368 | itemsSlice = itemsArray[:] 369 | 370 | // 附值给Calendar.Items 371 | c.Items = itemsSlice 372 | 373 | // copy一个返回 374 | var resultItems = make([]*CalendarItem, len(itemsSlice)) 375 | copy(resultItems, itemsSlice) 376 | 377 | return itemsSlice 378 | } 379 | 380 | // (*Calendar) firstDate 计算t与c.config.FirstWeek相关几日,并同时返回首日time 381 | func (c *Calendar) firstDay(t time.Time) (int, time.Time) { 382 | // t与日历表首日相差几日,该值根据c.config.FirstWeek计算得出 383 | var differenceDays int 384 | 385 | // 该日历表的首日 386 | var firstDayTime time.Time 387 | 388 | // t是周几 389 | tDayWeek := int(t.Weekday()) 390 | 391 | // 根据c.config.FirstWeek算出该日历表的首日 392 | if tDayWeek >= c.config.FirstWeek { 393 | differenceDays = tDayWeek - c.config.FirstWeek 394 | } else { 395 | differenceDays = 7 - c.config.FirstWeek + tDayWeek 396 | } 397 | 398 | firstDayTime = t.AddDate(0, 0, -differenceDays) 399 | 400 | return differenceDays, firstDayTime 401 | } 402 | 403 | // (*Calendar) createItem 用t计算出单元其它相关值 404 | func (c *Calendar) createItem(t time.Time, currentYear, currentMonth int) *CalendarItem { 405 | 406 | year, _month, day := t.Date() 407 | month := int(_month) 408 | 409 | item := new(CalendarItem) 410 | 411 | item.Time = &t 412 | 413 | var wg = sync.WaitGroup{} 414 | wg.Add(7) // 在修改时要注意这里定义goroutine次数 415 | 416 | // 是否非本月的日期,0是本月日期,-1为上一月日期,1为下一月日期 417 | go func() { 418 | defer wg.Done() 419 | 420 | isAccidental := 0 421 | if currentYear > year { 422 | isAccidental = -1 423 | } else if currentYear < year { 424 | isAccidental = 1 425 | } else { 426 | if currentMonth > month { 427 | isAccidental = -1 428 | } else if currentMonth < month { 429 | isAccidental = 1 430 | } 431 | } 432 | item.IsAccidental = isAccidental 433 | }() 434 | 435 | // 是否是今天 436 | go func() { 437 | defer wg.Done() 438 | 439 | now := time.Now().In(c.loc) 440 | nY, nM, nD := now.Date() 441 | 442 | if nD != day || nM != _month || nY != year { 443 | item.IsToday = 0 444 | } else { 445 | item.IsToday = 1 446 | } 447 | 448 | }() 449 | 450 | // 公历节日 451 | go func() { 452 | defer wg.Done() 453 | 454 | gf := c.gregorianFestival(t) 455 | item.Festival = &gf 456 | }() 457 | 458 | // 节气 459 | go func() { 460 | defer wg.Done() 461 | 462 | if c.config.SolarTerms { 463 | // sts := c.SolarTerms(c.rawTime.Year()) 464 | sts := c.SolarTerms(year) 465 | 466 | stkStr := "2006-1-2" 467 | for _, stv := range sts { 468 | if t.Format(stkStr) == stv.Time.Format(stkStr) { 469 | item.SolarTerm = stv 470 | break 471 | } 472 | } 473 | } 474 | }() 475 | 476 | // 干支 477 | go func() { 478 | defer wg.Done() 479 | 480 | if c.config.HeavenlyEarthly { 481 | gz := c.ChineseSexagenaryCycle(t) 482 | item.GZ = &gz 483 | } 484 | }() 485 | 486 | // 农历 487 | go func() { 488 | defer wg.Done() 489 | 490 | if c.config.Lunar { 491 | ld := c.gregorianToLunar(t, true) 492 | item.LunarDate = &ld 493 | } 494 | }() 495 | 496 | // 星座 497 | go func() { 498 | defer wg.Done() 499 | 500 | if c.config.StarSign { 501 | ssi, ss, _ := StarSign(month, day) 502 | item.StarSign = &StarSignItem{Index: ssi, Name: ss} 503 | } 504 | }() 505 | 506 | wg.Wait() 507 | 508 | return item 509 | } 510 | 511 | // (*Calendar) gregorianFestival 取公历节日 512 | func (c *Calendar) gregorianFestival(t time.Time) FestivalItem { 513 | gregorianYear, gregorianMonth, gregorianDay := t.Date() 514 | 515 | fds := c.tempData.gFD.getData(gregorianYear) 516 | 517 | if len(fds) == 0 { 518 | 519 | for gfK, gfV := range gregorianFestivalArray { 520 | var err error 521 | trueK := "" // 经过处理转换成月日的K 522 | m := "" // 月 523 | d := "" // 日 524 | n := "" // 第几周 525 | w := "" // 周几 526 | month := 0 527 | day := 0 528 | 529 | // 某月第几周几 索引正则 530 | if strings.Index(gfK, "W") > -1 { 531 | re := regexp.MustCompile("^([0-9]{1,2})M(?:D)?([1-4])W([0-6])$").FindStringSubmatch(gfK) 532 | if len(re) != 4 { 533 | continue // 索引格式不正确 534 | } 535 | // 月数 536 | if re[1] == "" { 537 | continue // 索引格式不正确,没指明月份 538 | } 539 | m = re[1] 540 | // 月数string转int 541 | month, err = strconv.Atoi(m) 542 | if err != nil || month < 1 || month > 12 { 543 | continue // 月份为空或数字不正确 544 | } 545 | 546 | // 第几周 547 | if re[2] == "" { 548 | continue // 索引格式不正确,没指明第几周 549 | } 550 | n = re[2] 551 | // 第几周string转int 552 | num, err := strconv.Atoi(n) 553 | if err != nil || num < 1 || num > 4 { 554 | continue // 第几周为空或数字不正确 555 | } 556 | 557 | // 周几 558 | if re[3] == "" { 559 | continue // 索引格式不正确,没指明周几 560 | } 561 | w = re[3] 562 | // 周几string转int 563 | week, err := strconv.Atoi(w) 564 | if err != nil || week < 0 || week > 6 { 565 | continue // 第几周为空或数字不正确 566 | } 567 | 568 | tw := time.Date(gregorianYear, time.Month(month), 1, 0, 0, 0, 100, c.loc) 569 | tww := int(tw.Weekday()) 570 | 571 | differenceDays := 0 572 | if week >= tww { 573 | differenceDays = week - tww 574 | } else { 575 | differenceDays = 7 - (tww - week) 576 | } 577 | 578 | day = (num-1)*7 + 1 + differenceDays 579 | 580 | if GregorianMonthDays(gregorianYear, month) < day { 581 | continue // 日数超过该年该月总天数 582 | } 583 | 584 | } else { 585 | re := regexp.MustCompile("^([0-9]{1,2})M([0-9]{1,2})D$").FindStringSubmatch(gfK) 586 | if len(re) != 3 { 587 | continue // 索引格式不正确 588 | } 589 | // 月数 590 | if re[1] == "" { 591 | continue // 索引格式不正确,没指明月份 592 | } 593 | m = re[1] 594 | // 月数string转int 595 | month, err = strconv.Atoi(m) 596 | if err != nil || month < 1 || month > 12 { 597 | continue // 月份为空或数字不正确 598 | } 599 | 600 | // 日 601 | if re[2] == "" { 602 | continue // 索引格式不正确,没有指明日 603 | } 604 | d = re[2] 605 | // 日数string转int 606 | day, err = strconv.Atoi(d) 607 | if err != nil || day < 1 || day > GregorianMonthDays(gregorianYear, month) { 608 | continue // 日为空或数字不正确 609 | } 610 | 611 | } 612 | 613 | if month == 0 || day == 0 { 614 | continue 615 | } 616 | 617 | trueK = strconv.Itoa(month) + "M" + strconv.Itoa(day) + "D" 618 | // 对应值 619 | if _, ok := fds[trueK]; ok { 620 | fds[trueK] = append(fds[trueK], strings.Split(gfV, ",")...) 621 | } else { 622 | fds[trueK] = strings.Split(gfV, ",") 623 | } 624 | } 625 | 626 | c.tempData.gFD.setData(gregorianYear, fds) 627 | } 628 | 629 | festivalIndex := strconv.Itoa(int(gregorianMonth)) + "M" + strconv.Itoa(gregorianDay) + "D" 630 | 631 | var fi FestivalItem // 该日的节日 632 | if fv, ok := fds[festivalIndex]; ok { 633 | for _, v := range fv { 634 | v = strings.TrimSpace(v) 635 | if svs := strings.Split(v, "*"); len(svs) > 1 { 636 | fi.Show = append(fi.Show, svs[1]) 637 | } else { 638 | fi.Secondary = append(fi.Secondary, v) 639 | } 640 | } 641 | } 642 | 643 | return fi 644 | } 645 | 646 | // (*Calendar) SolarTerms 一整年的节气 647 | // 648 | // 从上一年的冬至开始到下一年的小寒共26个节气对应的日期时间, 649 | // 设置c.stMap的值 map[string]*SolarTerm是一个以"年-月-日"为索引的map, 650 | // 返回[]*SolarTerm是一个有序切片 651 | func (c *Calendar) SolarTerms(year int) []*SolarTermItem { 652 | sts := c.tempData.st.getData(year) 653 | if sts != nil && len(sts) == 26 { 654 | return sts 655 | } 656 | 657 | ji := -1 658 | 659 | lastYearAsts := lastYearSolarTerms(float64(year)) 660 | 661 | for i, v := range lastYearAsts { 662 | if v == 0 { 663 | continue 664 | } 665 | if i < 18 { 666 | continue 667 | } 668 | if i > 23 { 669 | continue 670 | } 671 | 672 | ji++ 673 | 674 | // var stItem SolarTerm 675 | stItem := new(SolarTermItem) 676 | 677 | stTime := JdToTime(v, c.loc) 678 | stItem.Index = (ji + 18) % 24 // 节气名称的索引 679 | stItem.Name = solarTermsNameArray[stItem.Index] 680 | stItem.Time = &stTime 681 | 682 | sts = append(sts, stItem) 683 | } 684 | 685 | asts := adjustedSolarTermsJd(float64(year), 0, 19) 686 | for i, v := range asts { 687 | 688 | if v == 0 { 689 | continue 690 | } 691 | 692 | if i > 19 { 693 | continue 694 | } 695 | 696 | ji++ 697 | 698 | stItem := new(SolarTermItem) 699 | 700 | stTime := JdToTime(v, c.loc) 701 | stItem.Index = (ji + 18) % 24 // 节气名称的索引 702 | stItem.Name = solarTermsNameArray[stItem.Index] 703 | stItem.Time = &stTime 704 | 705 | sts = append(sts, stItem) 706 | } 707 | 708 | c.tempData.st.setData(year, sts) 709 | 710 | return sts 711 | } 712 | 713 | // StarSign 根据月和日取星座 714 | func StarSign(month, day int) (int, string, error) { 715 | 716 | if month < 1 || month > 12 || day < 1 || day > 31 { 717 | return 0, "", errors.New("日期错误!") 718 | } 719 | 720 | // 星座的起始日期 721 | ZodiacDayArray := [12]int{20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 22, 22} 722 | 723 | i := month - 1 724 | if day < ZodiacDayArray[i] { 725 | i = ((i + 12) - 1) % 12 726 | } 727 | 728 | return i, starSignsNameArray[i], nil 729 | } 730 | 731 | // (SolarTermItem) String 节气显示 732 | func (sti SolarTermItem) String() string { 733 | return fmt.Sprintf("%s 定%s:%s", sti.Name, sti.Name, sti.Time.Format(time.RFC3339)) 734 | } 735 | 736 | // (CalendarItem) String 日历单元显示 737 | func (ci CalendarItem) String() string { 738 | dateString := ci.Time.Format("2006-01-02") 739 | todayStr := " " 740 | if ci.IsToday == 1 { 741 | todayStr = "*" 742 | } 743 | weekIndex := ci.Time.Weekday() 744 | weekString := fmt.Sprintf("%s周%s", todayStr, weekNameArray[weekIndex]) 745 | 746 | festivalString := "" 747 | if ci.Festival != nil && ci.Festival.Show != nil { 748 | festivalString = " " + strings.Join(ci.Festival.Show, ",") 749 | } 750 | 751 | var lunarString = "" 752 | if ci.LunarDate != nil { 753 | // lunarString = " " + ci.LunarDate.String() 754 | // 农历节日覆盖公历节日 755 | if ci.LunarDate.Festival != nil && ci.LunarDate.Festival.Show != nil { 756 | festivalString = " " + strings.Join(ci.LunarDate.Festival.Show, ",") 757 | } 758 | 759 | lunarString = fmt.Sprintf(" %d%s%s(%s)年%s%s月%s", ci.LunarDate.Year, ci.LunarDate.YearGZ.HSN, ci.LunarDate.YearGZ.EBN, ci.LunarDate.AnimalName, ci.LunarDate.LeapStr, ci.LunarDate.MonthName, ci.LunarDate.DayName) 760 | } 761 | 762 | var gzString = "" 763 | if ci.GZ != nil { 764 | gzStr := ci.GZ.String() 765 | subByte := "日" 766 | el := strings.Index(gzStr, subByte) + len(subByte) 767 | gzString = " " + string([]byte(gzStr)[:el]) 768 | } 769 | 770 | var solarTermString = "" 771 | if ci.SolarTerm != nil { 772 | solarTermString = " " + ci.SolarTerm.String() 773 | } 774 | 775 | return StringSplice(dateString, weekString, lunarString, gzString, solarTermString, festivalString) 776 | } 777 | 778 | // (*yearSolarTermTemp) getData 读节气缓存年表 779 | func (ystt *yearSolarTermTemp) getData(k int) []*SolarTermItem { 780 | ystt.mu.RLock() 781 | defer ystt.mu.RUnlock() 782 | var rv []*SolarTermItem 783 | if ystt.data == nil { 784 | ystt.data = make(map[int][]*SolarTermItem) 785 | } 786 | if v, ok := ystt.data[k]; ok { 787 | rv = v 788 | } 789 | 790 | return rv 791 | } 792 | 793 | // (*yearSolarTermTemp) getData 写节气缓存年表 794 | func (ystt *yearSolarTermTemp) setData(k int, v []*SolarTermItem) { 795 | ystt.mu.Lock() 796 | defer ystt.mu.Unlock() 797 | if ystt.data == nil { 798 | ystt.data = make(map[int][]*SolarTermItem) 799 | } 800 | ystt.data[k] = v 801 | } 802 | 803 | // (*yearFestivalTemp) getData 读节日缓存年表 804 | func (yft *yearFestivalTemp) getData(k int) map[string][]string { 805 | yft.mu.RLock() 806 | defer yft.mu.RUnlock() 807 | 808 | rv := make(map[string][]string) 809 | if yft.data == nil { 810 | yft.data = make(map[int]map[string][]string) 811 | } 812 | if v, ok := yft.data[k]; ok { 813 | rv = v 814 | } 815 | 816 | return rv 817 | } 818 | 819 | // (*yearFestivalTemp) setData 写节日缓存年表 820 | func (yft *yearFestivalTemp) setData(k int, v map[string][]string) { 821 | yft.mu.Lock() 822 | defer yft.mu.Unlock() 823 | if yft.data == nil { 824 | yft.data = make(map[int]map[string][]string) 825 | } 826 | yft.data[k] = v 827 | } 828 | -------------------------------------------------------------------------------- /calendar_test.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestDefaultCalendar(t *testing.T) { 11 | c := DefaultCalendar() 12 | t.Log(c.config.Grid) 13 | } 14 | 15 | func TestNewCalendar(t *testing.T) { 16 | cfg := CalendarConfig{ 17 | Grid:1, 18 | TimeZoneName:"Europe/Berlin", 19 | } 20 | 21 | c := NewCalendar(cfg) 22 | 23 | ti := c.GetRawTime() 24 | 25 | t.Log(ti.Zone()) 26 | t.Log(ti.String()) 27 | } 28 | 29 | // 一整年节气 30 | func TestCalendar_SolarTerms(t *testing.T) { 31 | c := NewCalendar(CalendarConfig{TimeZoneName:"Asia/Shanghai"}) 32 | 33 | sts:= c.SolarTerms(2021) 34 | for _,v := range sts{ 35 | st := fmt.Sprintf(" %s 定%s:%s", v.Name, v.Name, v.Time.Format(time.RFC3339)) 36 | t.Log(st) 37 | } 38 | } 39 | 40 | func TestCalendar_GenerateWithDate(t *testing.T) { 41 | beforeTime := time.Now() 42 | defer func() { 43 | str := time.Since(beforeTime) 44 | t.Logf("本次执行用时:%s\n",str) 45 | }() 46 | 47 | c := NewCalendar(CalendarConfig{ 48 | Grid:GridWeek, 49 | FirstWeek:0, 50 | SolarTerms:true, 51 | Lunar:true, 52 | HeavenlyEarthly:true, 53 | NightZiHour:true, 54 | StarSign:true, 55 | }) 56 | result := c.GenerateWithDate(2021,12,22) 57 | for _,item := range result { 58 | fmt.Println(item) 59 | } 60 | t.Log("----------------------------") 61 | items := c.NextMonth() 62 | for _,item := range items { 63 | fmt.Println(item) 64 | } 65 | } 66 | 67 | func TestCalendar_Generate(t *testing.T) { 68 | beforeTime := time.Now() 69 | defer func() { 70 | str := time.Since(beforeTime) 71 | t.Logf("本次执行用时:%s\n",str) 72 | }() 73 | 74 | c := NewCalendar(CalendarConfig{ 75 | Grid:GridMonth, 76 | FirstWeek:0, 77 | SolarTerms:true, 78 | Lunar:true, 79 | HeavenlyEarthly:true, 80 | NightZiHour:true, 81 | StarSign:true, 82 | }) 83 | 84 | c.SetRawTime(2021,2,1) 85 | result := c.Generate() 86 | 87 | 88 | for _,item := range result { 89 | // t.Log(item) 90 | fmt.Println(item) 91 | } 92 | } 93 | 94 | // 一周日历表 95 | func TestCalendar_weekCalendar(t *testing.T) { 96 | beforeTime := time.Now() 97 | defer func() { 98 | str := time.Since(beforeTime) 99 | t.Logf("本次执行用时:%s\n",str) 100 | }() 101 | 102 | c := NewCalendar(CalendarConfig{Grid:GridWeek,FirstWeek:0,SolarTerms:true,Lunar:true,HeavenlyEarthly:true,NightZiHour:true}) 103 | c.weekCalendar() 104 | 105 | for _,item := range c.Items { 106 | t.Log(item) 107 | } 108 | } 109 | 110 | // 一月日历表 111 | func TestCalendar_monthCalendar(t *testing.T) { 112 | beforeTime := time.Now() 113 | defer func() { 114 | str := time.Since(beforeTime) 115 | t.Logf("本次执行用时:%s\n",str) 116 | }() 117 | 118 | c := NewCalendar(CalendarConfig{Grid:GridMonth,FirstWeek:0,TimeZoneName:"Asia/Shanghai",SolarTerms:true,Lunar:true,HeavenlyEarthly:true,NightZiHour:true,StarSign:true}) 119 | 120 | c.SetRawTime(2021,12,1) 121 | items := c.monthCalendar() 122 | 123 | 124 | itemsJson,_ := json.Marshal(items) 125 | t.Log(string(itemsJson)) 126 | 127 | for _,item := range items { 128 | t.Log(item) 129 | } 130 | 131 | 132 | t.Logf("st len=%d jSS len=%d qSS len=%d tNM len=%d lMC len=%d lMD len=%d lFD len=%d gFD len=%d",len(c.tempData.st.data),len(c.tempData.jSS.data),len(c.tempData.qSS.data),len(c.tempData.tNM.data),len(c.tempData.lMC.data),len(c.tempData.lMD.data),len(c.tempData.lFD.data),len(c.tempData.gFD.data)) 133 | } 134 | 135 | // 星座 136 | func TestStarSign(t *testing.T) { 137 | i,ss,_ := StarSign(5,6) 138 | 139 | if i == 3 { 140 | t.Log("passed") 141 | }else{ 142 | t.Error(i,ss) 143 | } 144 | } 145 | 146 | // Benchmark 147 | func BenchmarkCalendar_monthCalendar(b *testing.B) { 148 | c := DefaultCalendar() 149 | c.monthCalendar() 150 | } 151 | 152 | // Benchmark 153 | func BenchmarkCalendar_SolarTerms(b *testing.B) { 154 | c := NewCalendar(CalendarConfig{TimeZoneName:"Asia/Shanghai"}) 155 | c.SolarTerms(2021) 156 | } 157 | 158 | 159 | -------------------------------------------------------------------------------- /chinesecalendar.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // 中国(东八区)时间相对UTC的偏移量(单位:天days) 15 | const cChineseTimeOffsetDays float64 = 8 / 24.0 16 | 17 | // type GZItem 天干地支单元 18 | type GZItem struct { 19 | HSI int `json:"hsi"` // 天干索引 20 | HSN string `json:"hsn"` // 天干名称 21 | EBI int `json:"ebi"` // 地支索引 22 | EBN string `json:"ebn"` // 地支名称 23 | } 24 | 25 | // type GZ 日期时间干支 26 | type GZ struct { 27 | Year *GZItem `json:"ygz"` // 年天干地支 28 | Month *GZItem `json:"mgz"` // 月天干地支 29 | Day *GZItem `json:"dgz"` // 日天干地支 30 | Hour *GZItem `json:"hgz"` // 时天干地支 31 | } 32 | 33 | // type LunarDate 农历 34 | type LunarDate struct { 35 | Year int `json:"year"` // 年 36 | Month int `json:"month"` // 月 37 | Day int `json:"day"` // 日 38 | MonthName string `json:"monthName"` // 月份名称 39 | DayName string `json:"dayName"` // 日名称 40 | LeapStr string `json:"leapStr"` // 闰字,可以用该值是否为空来判断该月是否为闰月 41 | YearLeapMonth int `json:"ylm"` // 该年闰几月,如果该年无闰月,则0,该年闰几月该值就是几,也可以用LeapMonth == Month判断该月是否闰月 42 | AnimalIndex int `json:"sai"` // 年生肖索引 43 | AnimalName string `json:"san"` // 年生肖名称 44 | YearGZ *GZItem `json:"ygz"` // 年干支 45 | Festival *FestivalItem `json:"festival"` // 农历节日 46 | } 47 | 48 | // type pureJieQi16Temp struct pureJieSinceSpring和qiSinceWinterSolstice的缓存 49 | type pureJieQi16Temp struct { 50 | data map[int][16]float64 51 | mu sync.RWMutex 52 | } 53 | 54 | // type trueNewMoon20Temp struct 20个新月点年表缓存 55 | type trueNewMoon20Temp struct { 56 | data map[int][20]float64 57 | mu sync.RWMutex 58 | } 59 | 60 | // type lunarMonthCode15Temp struct农历月名称年表缓存 61 | type lunarMonthCode15Temp struct { 62 | data map[int][15]float64 63 | mu sync.RWMutex 64 | } 65 | 66 | // type lunarMonthDays15Temp struct 农历月份对应的天数年表缓存 67 | type lunarMonthDays15Temp struct { 68 | data map[int][15]int 69 | mu sync.RWMutex 70 | } 71 | 72 | // (*Calendar) ChineseSexagenaryCycle 日期时间对应的干支 73 | // 74 | // 特别提醒:干支推算的日干支存在早晚子时的区别 75 | // NightZiHour默认为false是不区分早晚子时00:00-02:00为子时,NightZiHour为true时,23:00-24:00 00:00-01:00为子时 76 | func (c *Calendar) ChineseSexagenaryCycle(t time.Time) GZ { 77 | year, month, day := t.Date() 78 | hour, minute, second := t.Clock() 79 | 80 | // t时间时区与UTC的时差 81 | _,offset := t.Zone() 82 | var offsetDays = float64(offset) / 86400 83 | 84 | // t的儒略日 85 | // 该儒略日是按t的时区转换的 86 | jd := JulianDay(float64(year), float64(month), float64(day), float64(hour), float64(minute), float64(second)) 87 | 88 | // 年月日时四柱 89 | // hsaeb := make(map[string]*GZItem) 90 | var gzs GZ 91 | 92 | // 立春点开始的节,年干支以立春开始(本方法为了日历使用,在计算中以立春当天为准,不考虑详细时间) 93 | // jss中儒略日是TT时间(这里强制为UTC时间),是未经时区修改的儒略日 94 | // 在与jd比较时,应加上时区时差 95 | jss := c.pureJieSinceSpring(year) 96 | 97 | // 以立春当天0时作比较,不考虑定立春的时分秒,所以用math.Floor向下取整数部分 98 | if math.Floor(jd+0.5) < math.Floor(jss[1] + 0.5 + offsetDays) { // $jss[1]为立春,约在2月5日前后。 99 | year-- // 若小于jss[1]则属于前一个节气年 100 | 101 | // 取得自立春开始的节(不包含中气),该数组长度固定为16 102 | jss = c.pureJieSinceSpring(year) 103 | } 104 | 105 | // 年干支 106 | ygz := ((year+4712+24)%60 + 60) % 60 107 | gzs.Year = &GZItem{ 108 | HSI: ygz % 10, // 年干 109 | EBI: ygz % 12, // 年支 110 | } 111 | 112 | ix := 0 113 | 114 | // 比较求算节气月,求出月干支 115 | for j := 0; j <= len(jss); j++ { 116 | if math.Floor(jss[j] + 0.5 + offsetDays) > math.Floor(jd+0.5) { 117 | // 已超过指定时刻,故应取前一个节气,用jd的0时比较,不考虑时分秒 118 | ix = j - 1 119 | break 120 | } 121 | } 122 | 123 | tmm := ((year + 4712) * 12 + (ix - 1) + 60) % 60 // 数组0为前一年的小寒所以这里再减一 124 | mgz := (tmm + 50) % 60 125 | gzs.Month = &GZItem{ 126 | HSI: mgz % 10, // 月干 127 | EBI: mgz % 12, // 月支 128 | } 129 | 130 | jdn := jd + 0.5 // 计算日柱的干支,加0.5是将起始点从正午改为0时开始 131 | thes := ((jdn - math.Floor(jdn)) * 86400) + 3600 // 将jd的小数部分化为秒,并加上起始点前移的一小时(3600秒) 132 | dayJd := math.Floor(jdn) + thes/86400 // 将秒数化为日数,加回到jd的整数部分 133 | dgz := (int(math.Floor(dayJd+49))%60 + 60) % 60 134 | gzs.Day = &GZItem{ 135 | HSI: dgz % 10, // 日干 136 | EBI: dgz % 12, // 日支 137 | } 138 | 139 | // 区分早晚子时,日柱前移一柱 140 | if c.config.NightZiHour && (hour >= 23) { 141 | gzs.Day = &GZItem{ 142 | HSI: (gzs.Day.HSI + 10 - 1) % 10, // 日干 143 | EBI: (gzs.Day.EBI + 12 - 1) % 12, // 日支 144 | } 145 | } 146 | 147 | dh := (dayJd) * 12 // 计算时柱的干支 148 | hgz := (int(math.Floor(dh+48))%60 + 60) % 60 149 | gzs.Hour = &GZItem{ 150 | HSI: hgz % 10, // 时干 151 | EBI: hgz % 12, // 时支 152 | } 153 | 154 | // 为干支附中文名称 155 | gzs.Year.HSN = heavenlyStemsNameArray[gzs.Year.HSI] 156 | gzs.Year.EBN = earthlyBranchesNameArray[gzs.Year.EBI] 157 | gzs.Month.HSN = heavenlyStemsNameArray[gzs.Month.HSI] 158 | gzs.Month.EBN = earthlyBranchesNameArray[gzs.Month.EBI] 159 | gzs.Day.HSN = heavenlyStemsNameArray[gzs.Day.HSI] 160 | gzs.Day.EBN = earthlyBranchesNameArray[gzs.Day.EBI] 161 | gzs.Hour.HSN = heavenlyStemsNameArray[gzs.Hour.HSI] 162 | gzs.Hour.EBN = earthlyBranchesNameArray[gzs.Hour.EBI] 163 | 164 | return gzs 165 | } 166 | 167 | 168 | 169 | // (*Calendar) pureJieSinceSpring 求出以某年立春点开始的节 170 | func (c *Calendar) pureJieSinceSpring(year int) [16]float64 { 171 | // jss 16个节的jd数据 172 | 173 | // 如果c.jSS记录了该年的数据,则直接返回 174 | jss,err := c.tempData.jSS.getData(year) 175 | if err == nil { 176 | return jss 177 | } 178 | 179 | lastYearAsts := lastYearSolarTerms(float64(year)) 180 | 181 | ki := -1 // 数组索引 182 | 183 | // 19小寒;21立春;23惊蛰 184 | for i := 19; i <= 23; i += 2 { 185 | // if i%2 == 0 { 186 | // continue 187 | // } 188 | if lastYearAsts[i] == 0 { 189 | continue 190 | } 191 | 192 | ki++ 193 | // jss[ki] = Round(lastYearAsts[i]+cChineseTimeOffsetDays, 10) // 农历计算需要,加上中国(东八区)时差 194 | jss[ki] = lastYearAsts[i] // 中国(东八区)时差放在具体计算时调整,此处不做调整 195 | } 196 | 197 | asts := adjustedSolarTermsJd(float64(year), 0, 25) 198 | for i := 1; i <= 25; i += 2 { 199 | // if i%2 == 0 { 200 | // continue 201 | // } 202 | if asts[i] == 0 { 203 | continue 204 | } 205 | 206 | ki++ 207 | // jss[ki] = Round(asts[i]+cChineseTimeOffsetDays, 10) // 农历计算需要,加上中国(东八区)时差 208 | jss[ki] = asts[i] // 中国(东八区)时差放在具体计算时调整,此处不做调整 209 | } 210 | 211 | c.tempData.jSS.setData(year,jss) 212 | 213 | return jss 214 | } 215 | 216 | // (*Calendar) qiSinceWinterSolstice 求出自上一年冬至点为起点的连续中气 217 | func (c *Calendar) qiSinceWinterSolstice(year int) [16]float64 { 218 | // qss 16个中气的jd 219 | 220 | // 如果c.qSS记录了该年的数据,则直接返回 221 | qss,err := c.tempData.qSS.getData(year) 222 | if err == nil { 223 | return qss 224 | } 225 | 226 | lastYearAsts := lastYearSolarTerms(float64(year)) 227 | 228 | ki := -1 // 数组索引 229 | 230 | // 18冬至(上一年);20大寒;22雨水 231 | for i := 18; i <= 22; i += 2 { 232 | if lastYearAsts[i] == 0 { 233 | continue 234 | } 235 | 236 | ki++ 237 | // qss[ki] = Round(lastYearAsts[i]+cChineseTimeOffsetDays, 10) // 农历计算需要,加上中国(东八区)时差 238 | qss[ki] = lastYearAsts[i] // 中国(东八区)时差放在具体计算时调整,此处不做调整 239 | } 240 | 241 | asts := adjustedSolarTermsJd(float64(year), 0, 25) 242 | for i := 0; i <= 24; i += 2 { 243 | if asts[i] == 0 { 244 | continue 245 | } 246 | 247 | ki++ 248 | // qss[ki] = Round(asts[i] + cChineseTimeOffsetDays, 10) // 农历计算需要,加上中国(东八区)时差 249 | qss[ki] = asts[i] // 中国(东八区)时差放在具体计算时调整,此处不做调整 250 | } 251 | 252 | c.tempData.qSS.setData(year,qss) 253 | 254 | return qss 255 | } 256 | 257 | 258 | // (*Calendar) GregorianToLunar 公历转农历 259 | // 260 | // @param int year 公历年份 261 | // @param int month 公历月份 262 | // @param int day 公历日 263 | func (c *Calendar) GregorianToLunar(year, month, day int) LunarDate { 264 | t := time.Date(year,time.Month(month),day,0,0,0,0,c.loc) 265 | 266 | return c.gregorianToLunar(t,false) // festival:false时不取农历节日 267 | } 268 | 269 | // (*Calendar) gregorianToLunar 公历转农历 270 | func (c *Calendar) gregorianToLunar(t time.Time,festival bool) LunarDate{ 271 | year, month, day := t.Date() 272 | hour, minute, second := t.Clock() 273 | 274 | lunarYear := year // 初始农历年等于公历年 275 | 276 | prev := 0 // 是否跨年了,跨年了则减一 277 | isLeap := false // 是否闰月 278 | 279 | // t的儒略日 280 | jd := JulianDay(float64(year), float64(month), float64(day), float64(hour), float64(minute), float64(second)) 281 | 282 | jdn := jd + 0.5 // 加0.5是将起始点从正午改为0时开始 283 | 284 | nm, lmc := c.zqAndSMandLunarMonthCode(year) 285 | 286 | // 如果公历日期的jd小于第一个朔望月新月点,表示农历年份是在公历年份的上一年 287 | if math.Floor(jdn) < math.Floor(nm[0] + 0.5 + cChineseTimeOffsetDays) { 288 | 289 | prev = 1 290 | nm, lmc = c.zqAndSMandLunarMonthCode(year-1) 291 | 292 | } 293 | 294 | // 查询对应的农历月份索引 295 | var mi = 0 296 | for i := 0; i <= 14; i++ { // 指令中加0.5是为了改为从0时算起而不从正午算起 297 | if math.Floor(jdn) >= math.Floor(nm[i]+0.5 + cChineseTimeOffsetDays) && math.Floor(jdn) < math.Floor(nm[i+1]+0.5 + cChineseTimeOffsetDays) { 298 | mi = i 299 | break 300 | } 301 | } 302 | 303 | // 农历的年 304 | // 如果月份属于上一年的11月或12月,或者农历年在上一年时 305 | if lmc[mi] < 2 || prev == 1 { // 年 306 | lunarYear -- 307 | } 308 | 309 | // 农历月份是否是闰月 310 | if (lmc[mi] - math.Floor(lmc[mi])) * 2 + 1 != 1 { // 因mc(mi)=0对应到前一年农历11月,mc(mi)=1对应到前一年农历12月,mc(mi)=2对应到本年1月,依此类推 311 | isLeap = true 312 | } 313 | 314 | // 农历的月 315 | lunarMonth := int(math.Floor(lmc[mi] + 10)) % 12 + 1 316 | 317 | // 农历的日 318 | lunarDay := int(math.Floor(jdn) - math.Floor(nm[mi] + 0.5 + cChineseTimeOffsetDays) + 1) // 此处加1是因为每月初一从1开始而非从0开始 319 | 320 | 321 | // 整理年月日农历表示 322 | monthName := lunarMonthNameArray[lunarMonth - 1] 323 | dayName := DayChinese(lunarDay) 324 | ygz := ((lunarYear+4712+24)%60 + 60) % 60 325 | yhsi := ygz % 10 326 | yebi := ygz % 12 327 | animalIndex := yebi 328 | animalName := symbolicAnimalsNameArray[yebi] 329 | yearGZ := &GZItem{ 330 | HSI: yhsi, 331 | HSN: heavenlyStemsNameArray[yhsi], 332 | EBI: yebi, 333 | EBN: earthlyBranchesNameArray[yebi], 334 | } 335 | 336 | // 整理闰月相关 337 | leapStr := "" // 338 | leapMonth := 0 // 闰几月 339 | if isLeap { 340 | leapStr = lunarLeapString 341 | leapMonth = lunarMonth 342 | } 343 | 344 | // 农历节日 345 | var lf FestivalItem 346 | if festival { 347 | lf = c.lunarFestival(lunarYear,lunarMonth,lunarDay,isLeap) 348 | } 349 | 350 | 351 | // 返回 352 | return LunarDate{ 353 | Year: lunarYear, 354 | Month: lunarMonth, 355 | Day: lunarDay, 356 | MonthName: monthName, 357 | DayName: dayName, 358 | LeapStr: leapStr, 359 | YearLeapMonth: leapMonth, 360 | AnimalIndex: animalIndex, 361 | AnimalName: animalName, 362 | YearGZ: yearGZ, 363 | Festival: &lf, 364 | } 365 | } 366 | 367 | 368 | // (*Calendar) LunarToGregorian 农历转公历 369 | // 370 | // demo: 371 | // c := DefaultCalendar() 372 | // gd,_ := dc.LunarToGregorian(2020,4,14,true) 373 | // fmt.Println(gd.Format(time.RFC3339)) 374 | // 375 | // @param int lunarYear 农历年份 376 | // @param int lunarMonth 农历月份 377 | // @param int lunarDay 农历日 378 | // @param bool isLeap 输入的日期是否是闰月的农历日期 379 | func (c *Calendar) LunarToGregorian(lunarYear,lunarMonth,lunarDay int, isLeap bool) (time.Time, error){ 380 | 381 | ld := LunarDate{Year: lunarYear, Month: lunarMonth,Day: lunarDay} 382 | if isLeap { 383 | ld.YearLeapMonth = lunarMonth 384 | } 385 | 386 | return c.lunarToGregorian(ld) 387 | } 388 | 389 | // (*Calendar) lunarToGregorian 农历转公历 390 | func (c *Calendar) lunarToGregorian(ld LunarDate) (time.Time, error){ 391 | 392 | lunarYear,lunarMonth,lunarDay := ld.Year, ld.Month, ld.Day 393 | 394 | isLeap := false 395 | if ld.YearLeapMonth > 0 && ld.YearLeapMonth == ld.Month { 396 | isLeap = true 397 | } 398 | 399 | nm, lmc := c.zqAndSMandLunarMonthCode(lunarYear) 400 | 401 | // 该年闰几月,0无闰月 402 | leapMonth := mcLeap(lmc) 403 | 404 | // 11月对应到1,12月对应到2,1月对应到3,2月对应到4,依此类推 405 | lunarMonth += 2 406 | 407 | var nofd [15]int 408 | for i := 0; i <= 14; i++ { 409 | nofd[i] = int(math.Floor(nm[i+1] + 0.5 + cChineseTimeOffsetDays) - math.Floor(nm[i] + 0.5 + cChineseTimeOffsetDays)) // 每月天数,加0.5是因JD以正午起算 410 | } 411 | 412 | var jd float64 413 | 414 | if isLeap { // 闰月 415 | 416 | if leapMonth < 3 { // 而旗标非闰月或非本年闰月,则表示此年不含闰月.leap=0代表无闰月,=1代表闰月为前一年的11月,=2代表闰月为前一年的12月 417 | return time.Time{}, errors.New("此年非闰年") // 此年非闰年 418 | } else { // 若本年內有闰月 419 | if leapMonth != lunarMonth { // 但不为指入的月份 420 | return time.Time{}, errors.New("该月非闰月") // 则指定的月份非闰月,此月非闰月 421 | } else { // 若输入的月份即为闰月 422 | if lunarDay <= nofd[lunarMonth] { // 若指定的日期不大于当月的天數 423 | jd = nm[lunarMonth] + float64(lunarDay) - 1 // 则将当月之前的JD值加上日期之前的天數 424 | } else { // 日期超出范围 425 | return time.Time{}, errors.New("日期超出范围") 426 | } 427 | } 428 | } 429 | 430 | } else { 431 | if leapMonth == 0 { // 若旗标非闰月,则表示此年不含闰月(包括前一年的11月起之月份) 432 | if lunarDay <= nofd[lunarMonth-1] { // 若日期不大于当月天数 433 | jd = nm[lunarMonth-1] + float64(lunarDay) - 1 // 则将当月之前的JD值加上日期之前的天数 434 | } else { // 日期超出范围 435 | return time.Time{}, errors.New("日期超出范围") 436 | } 437 | } else { // 若旗标为本年有闰月(包括前一年的11月起之月份) 公式nofd(lunarMonth - (lunarMonth > leapMonth) - 1)的用意为:若指定月大于闰月,则索引用lunarMonth,否则索引用lunarMonth-1 438 | k := lunarMonth -1 439 | if lunarMonth > leapMonth { 440 | k = lunarMonth 441 | } 442 | if lunarDay <= nofd[k] { // 若输入的日期不大于当月天数 443 | jd = nm[k] + float64(lunarDay) - 1 // 则将当月之前的JD值加上日期之前的天数 444 | } else { // 日期超出范围 445 | return time.Time{}, errors.New("日期超出范围") 446 | } 447 | } 448 | } 449 | 450 | jd = math.Floor(jd) + 0.5 451 | return JdToTime(jd, c.loc), nil 452 | } 453 | 454 | // (*Calendar) LunarMonthDay 农历某个月有多少天 455 | // 456 | // @param int lunarYear 农历年数字 457 | // @param int lunarMonth 农历月数字 458 | // @param bool isLeap 是否是闰月 459 | func (c *Calendar) LunarMonthDays(lunarYear,lunarMonth int, isLeap bool) (int,error) { 460 | 461 | var lmc [15]float64 462 | 463 | // 如果c.lMC记录了该年的数据,则直接赋值 464 | lMC,err := c.tempData.lMC.getData(lunarYear) 465 | if err == nil{ 466 | lmc = lMC 467 | }else{ 468 | _, lmc = c.zqAndSMandLunarMonthCode(lunarYear) 469 | } 470 | 471 | // 闰几月,0无闰月 472 | leapMonth := mcLeap(lmc) 473 | 474 | // 11月对应到1,12月对应到2,1月对应到3,2月对应到4,依此类推 475 | lunarMonth += 2 476 | 477 | lmd := c.mdList(lunarYear) 478 | 479 | dy := 0 // 当月天数 480 | 481 | if isLeap { 482 | if leapMonth < 3 { // 而旗标非闰月或非本年闰月,则表示此年不含闰月.leapMonth=0代表无闰月,=1代表闰月为前一年的11月,=2代表闰月为前一年的12月 483 | return 0, errors.New("该年非闰年") 484 | } 485 | // 若本年內有闰月 486 | if leapMonth != lunarMonth { // 但不为指定的月份 487 | return 0, errors.New("该月非该年的闰月") 488 | } else { // 若指定的月份即为闰月 489 | dy = lmd[lunarMonth] 490 | } 491 | } else { // 若没有指明是闰月 492 | k := lunarMonth -1 493 | if leapMonth != 0 { 494 | // 若旗标为本年有闰月(包括前一年的11月起之月份) 公式nofd(lunarMonth - (lunarMonth > leapMonth) - 1)的用意为:若指定月大于闰月,则索引用lunarMonth,否则索引用lunarMonth-1 495 | if lunarMonth > leapMonth { 496 | k = lunarMonth 497 | } 498 | } 499 | 500 | dy = lmd[k] 501 | } 502 | 503 | return dy, nil 504 | } 505 | 506 | 507 | 508 | // 农历一年的月份对应天数表 509 | func (c *Calendar)mdList(lunarYear int) [15]int { 510 | // 如果c.lMD记录了该年的数据,则直接返回 511 | lmd,err := c.tempData.lMD.getData(lunarYear) 512 | if err == nil { 513 | return lmd 514 | } 515 | 516 | nm, _ := c.zqAndSMandLunarMonthCode(lunarYear) 517 | 518 | for i := 0; i <= 14; i++ { 519 | lmd[i] = int(math.Floor(nm[i+1] + 0.5 + cChineseTimeOffsetDays) - math.Floor(nm[i] + 0.5 + cChineseTimeOffsetDays)) // 每月天数,加0.5是因JD以正午起算 520 | } 521 | 522 | c.tempData.lMD.setData(lunarYear,lmd) 523 | 524 | return lmd 525 | } 526 | 527 | // (*Calendar) LunarLeap 取农历某年的闰月 528 | // 529 | // 0表示无闰月 530 | func (c *Calendar) LunarLeap(lunarYear int) int { 531 | _,lmc := c.zqAndSMandLunarMonthCode(lunarYear) 532 | 533 | leap := mcLeap(lmc) 534 | 535 | return int(math.Max(0, float64(leap-2))) 536 | } 537 | 538 | // mcLeap 从农历的月代码lmc中找出闰月 539 | // 540 | // 0表示无闰月 541 | func mcLeap (lmc [15]float64) int { 542 | 543 | var leap float64 = 0 // 若闰月旗标为0代表无闰月 544 | 545 | for j := 1; j <= 14; j++ { // 确认指定年前一年11月开始各月是否闰月 546 | if lmc[j]-math.Floor(lmc[j]) > 0 { // 若是,则将此闰月代码放入闰月旗标內 547 | leap = math.Floor(lmc[j] + 0.5) // leap = 0对应农历11月,1对应农历12月,2对应农历隔年1月,依此类推. 548 | break 549 | } 550 | } 551 | 552 | return int(leap) 553 | } 554 | 555 | 556 | 557 | // (*Calendar) zqAndSMandLunarMonthCode 以比较日期法求算冬月及其余各月名称代码,包含闰月,冬月为0,腊月为1,正月为2,其余类推.闰月多加0.5 558 | // 559 | // 农历按中国时间(东八区)计算 560 | func (c *Calendar) zqAndSMandLunarMonthCode(year int) ([16]float64, [15]float64) { 561 | 562 | // 取得以前一年冬至为起点之连续16个中气 563 | qss := c.qiSinceWinterSolstice(year) 564 | 565 | // 求出以含冬至中气为阴历11月(冬月)开始的连续16个朔望月的新月点 566 | nm := c.sMsinceWinterSolstice(year, qss[0]) 567 | 568 | // 如果c.lMC记录了该年的数据,则直接返回 569 | lmc,err := c.tempData.lMC.getData(year) 570 | if err == nil { 571 | return nm,lmc 572 | } 573 | 574 | // 设定旗标,0表示未遇到闰月,1表示已遇到闰月 575 | yz := 0 576 | 577 | if math.Floor(qss[12]+0.5 + cChineseTimeOffsetDays) >= math.Floor(nm[13]+0.5 + cChineseTimeOffsetDays) { 578 | 579 | for i := 1; i <= 14; i++ { 580 | 581 | // 至少有一个朔望月不含中气,第一个不含中气的月即为闰月 582 | // 若阴历腊月起始日大於冬至中气日,且阴历正月起始日小于或等于大寒中气日,则此月为闰月,其余同理 583 | if (nm[i]+0.5 + cChineseTimeOffsetDays) > math.Floor(qss[i-1-yz]+0.5 + cChineseTimeOffsetDays) && math.Floor(nm[i+1]+0.5 + cChineseTimeOffsetDays) <= math.Floor(qss[i-yz]+0.5 + cChineseTimeOffsetDays) { 584 | lmc[i] = float64(i) - 0.5 585 | yz = 1 // 标示遇到闰月 586 | } else { 587 | lmc[i] = float64(i - yz) // 遇到闰月开始,每个月号要减1 588 | } 589 | } 590 | } else { // 否则表示两个连续冬至之间只有11个整月,故无闰月 591 | 592 | for i := 0; i <= 12; i++ { // 直接赋予这12个月月代码 593 | lmc[i] = float64(i) 594 | } 595 | for i := 13; i <= 14; i++ { // 处理次一置月年的11月与12月,亦有可能含闰月 596 | // 若次一阴历腊月起始日大于附近的冬至中气日,且阴历正月起始日小于或等于大寒中气日,则此月为腊月,次一正月同理. 597 | if (nm[i]+0.5 + cChineseTimeOffsetDays) > math.Floor(qss[i-1-yz]+0.5 + cChineseTimeOffsetDays) && math.Floor(nm[i+1]+0.5 + cChineseTimeOffsetDays) <= math.Floor(qss[i-yz]+0.5 + cChineseTimeOffsetDays) { 598 | lmc[i] = float64(i) - 0.5 599 | yz = 1 // 标示遇到闰月 600 | } else { 601 | lmc[i] = float64(i - yz) // 遇到闰月开始,每个月号要减1 602 | } 603 | } 604 | } 605 | 606 | c.tempData.lMC.setData(year,lmc) 607 | 608 | return nm, lmc 609 | } 610 | 611 | 612 | 613 | // (*Calendar) sMsinceWinterSolstice 求算以含冬至中气为阴历11月开始的连续16个朔望月 614 | func (c *Calendar) sMsinceWinterSolstice (year int, dzJd float64) [16]float64 { 615 | 616 | tnm := [20]float64{} 617 | nm := [16]float64{} 618 | 619 | // 如果c.tNM记录了该年的数据,则直接赋值给tnm 620 | tNM,err := c.tempData.tNM.getData(year) 621 | if err == nil{ 622 | tnm = tNM 623 | }else{ 624 | 625 | // 求年初前两个月附近的新月点(即前一年的11月初) 626 | novemberJd := JulianDay(float64(year) - 1, 11, 1) 627 | 628 | // 求得自2000年1月起第kn个平均朔望日及其JD值 629 | // kn,thejd := meanNewMoon(novemberJd) 630 | kn := referenceLunarMonthNum(novemberJd) 631 | 632 | // 求出连续20个朔望月 633 | for i := 0; i <= 19; i++ { 634 | k := kn + float64(i) 635 | 636 | // 以k值代入求瞬时朔望日 637 | // tnm[i] = trueNewMoon(k) + cChineseTimeOffsetDays // 农历计算需要,加上中国(东八区)时差 638 | tnm[i] = trueNewMoon(k) // 中国(东八区)时差放在具体计算时调整,此处不做调整 639 | 640 | // 下式为修正 dynamical time to Universal time 641 | // 1为1月,0为前一年12月,-1为前一年11月(当i=0时,i-1代表前一年11月) 642 | tnm[i] = Round(tnm[i] - deltaTDays(float64(year), float64(i - 1)), 10) 643 | } 644 | 645 | c.tempData.tNM.setData(year,tnm) 646 | } 647 | 648 | var jj = 0 649 | for j := 0; j <= 18; j++ { 650 | if math.Floor(tnm[j] + 0.5) > math.Floor(dzJd + 0.5) { 651 | jj = j 652 | break 653 | } // 已超过冬至中气(比较日期法) 654 | } 655 | 656 | for k := 0; k <= 15; k++ { // 取上一步的索引值 657 | nm[k] = tnm[jj-1+k] // 重排索引,使含冬至朔望月的索引为0 658 | } 659 | 660 | return nm 661 | } 662 | 663 | // (*Calendar) lunarFestival 取农历节日 664 | func (c *Calendar) lunarFestival (lunarYear,lunarMonth,lunarDay int, isLeap bool) FestivalItem { 665 | 666 | fds := c.tempData.lFD.getData(lunarYear) 667 | 668 | if len(fds) == 0 { 669 | 670 | // 根据lunarFestivalArray重新格式一个准确的月日为索引的节日map 671 | for lfK,lfV := range lunarFestivalArray{ 672 | trueK := "" // 经过处理转换成月日的K 673 | isleap := false // 索引中是否指明为闰月 674 | isLastDay := false // 索此中是否指明为某月最后一天 675 | m := "" // 月 676 | d := "" // 日 677 | 678 | // 索引正则 679 | // re := regexp.MustCompile("([0-9]{1,2})?(@?)(M)?(?:([0-9]{1,2})D)?(?:([1-4])W([0-6]))?(\\$)?$").FindStringSubmatch(lfK) 680 | // 农历节日索引正则 681 | re := regexp.MustCompile("([0-9]{1,2})(@?)M(?:([0-9]{1,2})D)?(\\$)?$").FindStringSubmatch(lfK) 682 | 683 | if len(re) != 5{ 684 | continue // 索引格式不正确 685 | } 686 | // 月数 687 | if re[1] == "" { 688 | continue // 索引格式不正确,没指明月份 689 | } 690 | m = re[1] 691 | // 月数string转int 692 | month,err := strconv.Atoi(m) 693 | if err != nil || month < 1 || month > 12 { 694 | continue // 月份为空或数字不正确 695 | } 696 | 697 | // 开始拼接trueK 698 | trueK = m 699 | 700 | // 闰月 701 | if re[2] != "" { 702 | isleap = true 703 | 704 | lm := c.LunarLeap(lunarYear) 705 | if lm == 0 && lm != month { 706 | continue // 当前年中无该闰月,不用记录该节日 707 | } 708 | 709 | trueK += "@" 710 | } 711 | 712 | trueK += "M" 713 | 714 | // 日数 715 | if re[3] != "" { 716 | d = re[3] 717 | } 718 | // 最后一日 719 | if re[4] != "" && re[3] == "" { 720 | isLastDay = true 721 | } 722 | 723 | // 如果没指明日数也没有指明是最后一天,则索引是无效的 724 | if d == "" && !isLastDay { 725 | continue 726 | } 727 | 728 | // m月有多少天,验证d是否大于这个值以及将用该值表示最后一天 729 | days,err := c.LunarMonthDays(lunarYear,month,isleap) 730 | if err != nil { 731 | continue 732 | } 733 | 734 | day := 0 735 | if d != "" { 736 | day,err = strconv.Atoi(d) 737 | if err != nil || day < 1 || day > days { 738 | continue // 日为空或数字不正确 739 | } 740 | }else { 741 | day = days // 最后一日 742 | } 743 | 744 | trueK += strconv.Itoa(day) + "D" 745 | 746 | // 对应值 747 | if _, ok := fds[trueK]; ok { 748 | fds[trueK] = append(fds[trueK], strings.Split(lfV,",")...) 749 | }else{ 750 | fds[trueK] = strings.Split(lfV,",") 751 | } 752 | } 753 | 754 | c.tempData.lFD.setData(lunarYear,fds) 755 | } 756 | 757 | festivalIndex := strconv.Itoa(lunarMonth) 758 | if isLeap { 759 | festivalIndex += "@" 760 | } 761 | 762 | festivalIndex += "M" + strconv.Itoa(lunarDay) + "D" 763 | 764 | 765 | var fi FestivalItem // 该日的节日 766 | if fv, ok := fds[festivalIndex]; ok { 767 | for _, v := range fv { 768 | v = strings.TrimSpace(v) 769 | if svs := strings.Split(v,"*"); len(svs) > 1 { 770 | fi.Show = append(fi.Show,svs[1]) 771 | }else{ 772 | fi.Secondary = append(fi.Secondary,v) 773 | } 774 | } 775 | } 776 | 777 | return fi 778 | } 779 | 780 | // DayChinese 农历日汉字表示法 781 | func DayChinese(d int) string { 782 | daystr := "" 783 | if d < 1 || d > 30 { 784 | return "" 785 | } 786 | switch d { 787 | case 10: 788 | daystr = lunarWholeTensArray[0] + lunarNumberArray[10] 789 | case 20: 790 | daystr = lunarNumberArray[2] + lunarNumberArray[10] 791 | case 30: 792 | daystr = lunarNumberArray[3] + lunarNumberArray[10] 793 | default: 794 | k := d / 10 795 | m := d % 10 796 | daystr = lunarWholeTensArray[k] + lunarNumberArray[m] 797 | } 798 | return daystr 799 | } 800 | 801 | // (GZ) String 干支显示 802 | func (gz GZ) String() string{ 803 | return fmt.Sprintf("%s%s年%s%s月%s%s日%s%s时", gz.Year.HSN, gz.Year.EBN, gz.Month.HSN, gz.Month.EBN, gz.Day.HSN, gz.Day.EBN, gz.Hour.HSN, gz.Hour.EBN) 804 | } 805 | 806 | // (LunarDate) String 农历显示 807 | func (ld LunarDate)String()string{ 808 | festivalStr := "" 809 | if ld.Festival != nil && ld.Festival.Show != nil{ 810 | festivalStr = " " + strings.Join(ld.Festival.Show, ",") 811 | } 812 | 813 | return fmt.Sprintf("%d%s%s(%s)年%s%s月%s%s",ld.Year, ld.YearGZ.HSN, ld.YearGZ.EBN, ld.AnimalName, ld.LeapStr, ld.MonthName, ld.DayName, festivalStr) 814 | } 815 | 816 | 817 | 818 | 819 | // (*pureJieQi16) getData 读节气缓存年表 820 | func (pjq *pureJieQi16Temp) getData(k int) ([16]float64, error) { 821 | pjq.mu.RLock() 822 | defer pjq.mu.RUnlock() 823 | var rv [16]float64 824 | if pjq.data == nil { 825 | pjq.data = make(map[int][16]float64) 826 | } 827 | if v, ok := pjq.data[k]; ok { 828 | return v,nil 829 | } 830 | 831 | return rv,errors.New("缓存数据不存在!") 832 | } 833 | 834 | // (*pureJieQi16) getData 写节气缓存年表 835 | func (pjq *pureJieQi16Temp) setData (k int, v [16]float64){ 836 | pjq.mu.Lock() 837 | defer pjq.mu.Unlock() 838 | if pjq.data == nil { 839 | pjq.data = make(map[int][16]float64) 840 | } 841 | pjq.data[k] = v 842 | } 843 | 844 | // (*trueNewMoon20Temp) getData 读20个新月点缓存年表 845 | func (tnm *trueNewMoon20Temp) getData(k int) ([20]float64, error) { 846 | tnm.mu.RLock() 847 | defer tnm.mu.RUnlock() 848 | var rv [20]float64 849 | if tnm.data == nil { 850 | tnm.data = make(map[int][20]float64) 851 | } 852 | if v, ok := tnm.data[k]; ok { 853 | return v,nil 854 | } 855 | 856 | return rv,errors.New("缓存数据不存在!") 857 | } 858 | 859 | // (*trueNewMoon20Temp) getData 写20个新月点缓存年表 860 | func (tnm *trueNewMoon20Temp) setData (k int, v [20]float64){ 861 | tnm.mu.Lock() 862 | defer tnm.mu.Unlock() 863 | if tnm.data == nil { 864 | tnm.data = make(map[int][20]float64) 865 | } 866 | tnm.data[k] = v 867 | } 868 | 869 | // (*lunarMonthCode15Temp) getData 读农历月份代码缓存年表 870 | func (mc *lunarMonthCode15Temp) getData(k int) ([15]float64, error) { 871 | mc.mu.RLock() 872 | defer mc.mu.RUnlock() 873 | var rv [15]float64 874 | if mc.data == nil { 875 | mc.data = make(map[int][15]float64) 876 | } 877 | if v, ok := mc.data[k]; ok { 878 | return v,nil 879 | } 880 | 881 | return rv,errors.New("缓存数据不存在!") 882 | } 883 | 884 | // (*lunarMonthCode15Temp) getData 写农历月份代码缓存年表 885 | func (mc *lunarMonthCode15Temp) setData (k int, v [15]float64){ 886 | mc.mu.Lock() 887 | defer mc.mu.Unlock() 888 | if mc.data == nil { 889 | mc.data = make(map[int][15]float64) 890 | } 891 | mc.data[k] = v 892 | } 893 | 894 | // 895 | // (*lunarMonthDays15Temp) getData 读农历月份天数缓存年表 896 | func (md *lunarMonthDays15Temp) getData(k int) ([15]int, error) { 897 | md.mu.RLock() 898 | defer md.mu.RUnlock() 899 | var rv [15]int 900 | if md.data == nil { 901 | md.data = make(map[int][15]int) 902 | } 903 | if v, ok := md.data[k]; ok { 904 | return v,nil 905 | } 906 | 907 | return rv,errors.New("缓存数据不存在!") 908 | } 909 | 910 | // (*lunarMonthDays15Temp) getData 写农历月份天数缓存年表 911 | func (md *lunarMonthDays15Temp) setData (k int, v [15]int){ 912 | md.mu.Lock() 913 | defer md.mu.Unlock() 914 | if md.data == nil { 915 | md.data = make(map[int][15]int) 916 | } 917 | md.data[k] = v 918 | } -------------------------------------------------------------------------------- /chinesecalendar_test.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // 干支 10 | func TestCalendar_ChineseSexagenaryCycle(t *testing.T) { 11 | c := NewCalendar(CalendarConfig{NightZiHour: false}) 12 | rt := time.Date(2021, 5, 6, 23, 50, 0, 0, time.Local) 13 | gz := c.ChineseSexagenaryCycle(rt) 14 | if gz.Year.HSI == 7 && gz.Year.EBI == 1 && gz.Month.HSI == 9 && gz.Month.EBI == 5 && gz.Day.HSI == 1 && gz.Day.EBI == 3 { 15 | t.Log("passed") 16 | } else { 17 | t.Error(gz.Year, gz.Month, gz.Day, gz.Hour) 18 | } 19 | 20 | c = NewCalendar(CalendarConfig{NightZiHour: true}) 21 | rt = time.Date(2021, 5, 6, 23, 50, 0, 0, time.Local) 22 | gz = c.ChineseSexagenaryCycle(rt) 23 | if gz.Day.HSI == 0 && gz.Day.EBI == 2 { 24 | t.Log("passed") 25 | } else { 26 | t.Error(gz) 27 | } 28 | 29 | } 30 | 31 | // 公历转农历 32 | func TestCalendar_GregorianToLunar(t *testing.T) { 33 | ld := DefaultCalendar().GregorianToLunar(1000, 6, 5) 34 | t.Log(ld) 35 | } 36 | 37 | // 农历转公历 38 | func TestCalendar_LunarToGregorian(t *testing.T) { 39 | c := DefaultCalendar() 40 | gd, _ := c.LunarToGregorian(2020, 4, 14, false) 41 | fmt.Println("农历2020年四月十四转换成公历是:", gd.Format("2006-01-02")) 42 | 43 | gd, _ = c.LunarToGregorian(2020, 4, 14, true) 44 | fmt.Println("农历2020年闰四月十四转换成公历是:", gd.Format("2006-01-02")) 45 | 46 | t.Log(gd.Format(time.RFC3339)) 47 | } 48 | 49 | func TestCalendar_lunarToGregorian(t *testing.T) { 50 | dc := DefaultCalendar() 51 | 52 | gd, _ := dc.lunarToGregorian(dc.GregorianToLunar(2020, 6, 5)) 53 | t.Log(gd.Format(time.RFC3339)) 54 | } 55 | 56 | // 农历某月天数 57 | func TestCalendar_LunarMonthDay(t *testing.T) { 58 | dc := DefaultCalendar() 59 | d, e := dc.LunarMonthDays(2018, 12, false) 60 | 61 | if e != nil { 62 | t.Log(e.Error()) 63 | } else { 64 | if d == 30 { 65 | t.Log("passed") 66 | } else { 67 | t.Error(d) 68 | } 69 | } 70 | } 71 | 72 | func TestLunarFestival(t *testing.T) { 73 | c := DefaultCalendar() 74 | lf := c.lunarFestival(2021, 12, 29, false) 75 | t.Log(lf) 76 | 77 | lf = c.lunarFestival(2021, 7, 7, false) 78 | t.Log(lf) 79 | } 80 | 81 | func TestCalendar_pureJieSinceSpring(t *testing.T) { 82 | dc := DefaultCalendar() 83 | 84 | year := 2021 85 | jss := dc.pureJieSinceSpring(year) 86 | 87 | for i, v := range jss { 88 | t.Logf("%d %.10f", i, v) 89 | } 90 | } 91 | 92 | func TestCalendar_qiSinceWinterSolstice(t *testing.T) { 93 | dc := DefaultCalendar() 94 | 95 | year := 2021 96 | qss := dc.qiSinceWinterSolstice(year) 97 | 98 | for i, v := range qss { 99 | t.Logf("%d %.10f", i, v) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /clone.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | // (*SolarTermItem) clone 4 | func (sti *SolarTermItem) clone() *SolarTermItem { 5 | if sti == nil { 6 | return nil 7 | } 8 | 9 | t := sti.Time.AddDate(0, 0, 0) 10 | return &SolarTermItem{ 11 | Index: sti.Index, 12 | Name: sti.Name, 13 | Time: &t, 14 | } 15 | } 16 | 17 | // (*StarSignItem) clone 18 | func (ssi *StarSignItem) clone() *StarSignItem { 19 | if ssi == nil { 20 | return nil 21 | } 22 | 23 | return &StarSignItem{ 24 | Index: ssi.Index, 25 | Name: ssi.Name, 26 | } 27 | } 28 | 29 | // (*FestivalItem) clone 30 | func (fi *FestivalItem) clone() *FestivalItem { 31 | if fi == nil { 32 | return nil 33 | } 34 | 35 | var shows []string 36 | var secondary []string 37 | 38 | shows = append(shows, fi.Show...) 39 | secondary = append(secondary, fi.Secondary...) 40 | 41 | return &FestivalItem{ 42 | Show: shows, 43 | Secondary: secondary, 44 | } 45 | } 46 | 47 | // (*GZItem) clone 48 | func (gzi *GZItem) clone() *GZItem { 49 | if gzi == nil { 50 | return nil 51 | } 52 | 53 | return &GZItem{ 54 | HSI: gzi.HSI, 55 | HSN: gzi.HSN, 56 | EBI: gzi.EBI, 57 | EBN: gzi.EBN, 58 | } 59 | } 60 | 61 | // (*GZ)clone 62 | func (gz *GZ) clone() *GZ { 63 | if gz == nil { 64 | return nil 65 | } 66 | 67 | return &GZ{ 68 | Year: gz.Year.clone(), 69 | Month: gz.Month.clone(), 70 | Day: gz.Day.clone(), 71 | Hour: gz.Hour.clone(), 72 | } 73 | } 74 | 75 | // (*LunarDate) clone 76 | func (ld *LunarDate) clone() *LunarDate { 77 | if ld == nil { 78 | return nil 79 | } 80 | 81 | return &LunarDate{ 82 | Year: ld.Year, 83 | Month: ld.Month, 84 | Day: ld.Day, 85 | MonthName: ld.MonthName, 86 | DayName: ld.DayName, 87 | LeapStr: ld.LeapStr, 88 | YearLeapMonth: ld.YearLeapMonth, 89 | AnimalIndex: ld.AnimalIndex, 90 | AnimalName: ld.AnimalName, 91 | YearGZ: ld.YearGZ.clone(), 92 | Festival: ld.Festival.clone(), 93 | } 94 | } 95 | 96 | // (*CalendarItem) clone 97 | func (ci *CalendarItem) clone() *CalendarItem { 98 | if ci == nil { 99 | return nil 100 | } 101 | 102 | t := ci.Time.AddDate(0, 0, 0) 103 | return &CalendarItem{ 104 | Time: &t, 105 | IsAccidental: ci.IsAccidental, 106 | IsToday: ci.IsToday, 107 | Festival: ci.Festival.clone(), 108 | SolarTerm: ci.SolarTerm.clone(), 109 | GZ: ci.GZ.clone(), 110 | LunarDate: ci.LunarDate.clone(), 111 | StarSign: ci.StarSign.clone(), 112 | } 113 | } 114 | 115 | // (*Calendar) clone 克隆一个Calendar 116 | // 该克隆不对临时数据克隆,而是清空临时数据 117 | func (c *Calendar) Clone() *Calendar { 118 | if c == nil { 119 | return nil 120 | } 121 | 122 | rawT := c.rawTime.AddDate(0, 0, 0) 123 | 124 | var items []*CalendarItem 125 | if c.Items != nil { 126 | for _, itemv := range c.Items { 127 | items = append(items, itemv.clone()) 128 | } 129 | } 130 | 131 | return &Calendar{ 132 | Items: items, 133 | config: c.config.clone(), 134 | loc: c.loc, 135 | rawTime: &rawT, 136 | tempData: newCalendarTempData(), 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | // "math" 5 | // "reflect" 6 | "time" 7 | ) 8 | 9 | // 取日历方式 10 | const ( 11 | GridDay int = iota 12 | GridWeek 13 | GridMonth 14 | ) 15 | 16 | // type CalendarConfig struct 配置 17 | type CalendarConfig struct { 18 | Grid int // 取日历方式,GridDay按天取日历,GridWeek按周取日历,GridMonth按月取日历 19 | FirstWeek int // 日历显示时第一列显示周几,(日历表第一列是周几,0周日,依次最大值6) 20 | TimeZoneName string // 时区名称,需zoneinfo支持的时区名称 21 | SolarTerms bool // 读取节气 bool 22 | Lunar bool // 读取农历 bool 23 | HeavenlyEarthly bool // 读取干支 bool 24 | NightZiHour bool // 区分早晚子时,true则 23:00-24:00 00:00-01:00为子时,否则00:00-02:00为子时 25 | StarSign bool // 读取星座 26 | } 27 | 28 | // defaultConfig 新的默认配置 29 | func defaultConfig() CalendarConfig { 30 | return CalendarConfig{ 31 | Grid: GridMonth, 32 | FirstWeek: 0, 33 | TimeZoneName: time.Local.String(), 34 | SolarTerms: true, 35 | Lunar: true, 36 | HeavenlyEarthly: true, 37 | NightZiHour: true, 38 | StarSign: true, 39 | } 40 | } 41 | 42 | // (*CalendarConfig) clone 43 | func (cfg *CalendarConfig) clone() *CalendarConfig { 44 | return &CalendarConfig{ 45 | Grid: cfg.Grid, 46 | FirstWeek: cfg.FirstWeek, 47 | TimeZoneName: cfg.TimeZoneName, 48 | SolarTerms: cfg.SolarTerms, 49 | Lunar: cfg.Lunar, 50 | HeavenlyEarthly: cfg.HeavenlyEarthly, 51 | NightZiHour: cfg.NightZiHour, 52 | StarSign: cfg.StarSign, 53 | } 54 | } 55 | 56 | // (*Calendar) GetConfig 读取配置 57 | // 返回的是一个clone 58 | func (c *Calendar) GetConfig() CalendarConfig { 59 | cfg := c.config.clone() 60 | return *cfg 61 | } 62 | 63 | // NewConfig 用一个map[string]interface{}新建配置 64 | // func NewConfig(cfgMap map[string]interface{}) CalendarConfig { 65 | // cfg := defaultConfig() 66 | // 67 | // if grid, ok := cfgMap["Grid"]; ok { 68 | // if reflect.TypeOf(grid).Kind() == reflect.Int { 69 | // 70 | // cfg.Grid = int(math.Mod(math.Abs(float64(reflect.ValueOf(grid).Int())),3)) 71 | // } 72 | // } 73 | // 74 | // if firstWeek, ok := cfgMap["FirstWeek"]; ok { 75 | // if reflect.TypeOf(firstWeek).Kind() == reflect.Int { 76 | // cfg.FirstWeek = int(math.Mod(math.Abs(float64(reflect.ValueOf(firstWeek).Int())),7)) 77 | // } 78 | // } 79 | // 80 | // if timeZoneName, ok := cfgMap["TimeZoneName"]; ok { 81 | // if reflect.TypeOf(timeZoneName).Kind() == reflect.String { 82 | // tzn := reflect.ValueOf(timeZoneName).String() 83 | // if tzn != "" { 84 | // cfg.TimeZoneName = reflect.ValueOf(timeZoneName).String() 85 | // } 86 | // } 87 | // } 88 | // 89 | // if solarTerms, ok := cfgMap["SolarTerms"]; ok { 90 | // if reflect.TypeOf(solarTerms).Kind() == reflect.Bool { 91 | // cfg.SolarTerms = reflect.ValueOf(solarTerms).Bool() 92 | // } 93 | // } 94 | // 95 | // if lunar, ok := cfgMap["Lunar"]; ok { 96 | // if reflect.TypeOf(lunar).Kind() == reflect.Bool { 97 | // cfg.Lunar = reflect.ValueOf(lunar).Bool() 98 | // } 99 | // } 100 | // 101 | // if heavenlyEarthly, ok := cfgMap["HeavenlyEarthly"]; ok { 102 | // if reflect.TypeOf(heavenlyEarthly).Kind() == reflect.Bool { 103 | // cfg.HeavenlyEarthly = reflect.ValueOf(heavenlyEarthly).Bool() 104 | // } 105 | // } 106 | // 107 | // if nightZiHour, ok := cfgMap["NightZiHour"]; ok { 108 | // if reflect.TypeOf(nightZiHour).Kind() == reflect.Bool { 109 | // cfg.NightZiHour = reflect.ValueOf(nightZiHour).Bool() 110 | // } 111 | // } 112 | // 113 | // if starSign, ok := cfgMap["StarSign"]; ok { 114 | // if reflect.TypeOf(starSign).Kind() == reflect.Bool { 115 | // cfg.StarSign = reflect.ValueOf(starSign).Bool() 116 | // } 117 | // } 118 | // 119 | // return cfg 120 | // } 121 | 122 | 123 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // func TestNewConfig(t *testing.T) { 8 | // m := map[string]interface{}{ 9 | // "Grid":-1, 10 | // "FirstWeek":-771, 11 | // "TimeZoneName":"Asia/Chongqing", 12 | // "SolarTerms":false, 13 | // "Lunar":false, 14 | // "HeavenlyEarthly":false, 15 | // "NightZiHour":false, 16 | // } 17 | // cfg := NewConfig(m) 18 | // t.Log(cfg) 19 | // } 20 | 21 | func TestCalendar_GetConfig(t *testing.T) { 22 | // c := DefaultCalendar() 23 | c := NewCalendar(CalendarConfig{Grid:-2,FirstWeek:789,TimeZoneName:"Asia/Chongqing",Lunar:true}) 24 | cfg := c.GetConfig() 25 | t.Log(cfg) 26 | } 27 | -------------------------------------------------------------------------------- /datetime.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // GregorianMonthDays 公历某月的总天数 8 | func GregorianMonthDays(year, month int) int { 9 | md := [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} 10 | if month == 2 && IsLeapYear(year) { 11 | return 29 12 | } 13 | return md[month - 1] 14 | } 15 | 16 | // IsLeapYear 给定的公历年year是否是闰年 17 | // 18 | // 增加3200年的情况 19 | func IsLeapYear(year int) bool { 20 | return year%4 == 0 && (year%100 != 0 || year%400 == 0) && (year%3200 != 0 || year%172800 == 0) 21 | } 22 | 23 | // BeginningOfMonth 计算出t所在月份的首日 24 | // 25 | // 时hour,分minute,秒second与t对应的值一样 26 | func BeginningOfMonth(t time.Time) time.Time { 27 | day := t.Day() 28 | day-- 29 | return t.AddDate(0,0,-day) 30 | } 31 | 32 | // EndOfMonth 计算出t所在月份的最后一日 33 | // 34 | // 时hour,分minute,秒second与t对应的值一样 35 | func EndOfMonth(t time.Time) time.Time { 36 | nextMonthTime := t.AddDate(0,1,0) 37 | return BeginningOfMonth(nextMonthTime).AddDate(0,0,-1) 38 | } -------------------------------------------------------------------------------- /datetime_test.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestIsLeapYear(t *testing.T) { 9 | if !IsLeapYear(1900) && IsLeapYear(1940) && IsLeapYear(2000) && !IsLeapYear(2100) { 10 | t.Log("passed") 11 | }else{ 12 | t.Error() 13 | } 14 | } 15 | 16 | func TestBeginningOfMonth(t *testing.T) { 17 | ti := time.Date(2021,5,15,0,0,0,10,time.UTC) 18 | tb := time.Date(2021,5,1,0,0,0,10,time.UTC) 19 | if BeginningOfMonth(ti).Equal(tb){ 20 | t.Log("passed") 21 | }else{ 22 | t.Error() 23 | } 24 | } 25 | 26 | func TestEndOfMonth(t *testing.T) { 27 | ti := time.Date(2021,5,15,0,0,0,10,time.UTC) 28 | tb := time.Date(2021,5,31,0,0,0,10,time.UTC) 29 | if EndOfMonth(ti).Equal(tb){ 30 | t.Log("passed") 31 | }else{ 32 | t.Error() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liujiawm/gocalendar 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /julian.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "math" 5 | "time" 6 | ) 7 | 8 | const ( 9 | 10 | // 儒略历历法废弃年 11 | cJulianAbandonmentYear float64 = 1582 12 | 13 | // 儒略历历法废弃月 14 | cJulianAbandonmentMonth float64 = 10 15 | 16 | // 儒略历历法废弃日 17 | cJulianAbandonmentDay float64 = 4 18 | 19 | // 格里历历法实施年 20 | cGregorianAdoptionYear float64 = 1582 21 | 22 | // 格里历历法实施月 23 | cGregorianAdoptionMonth float64 = 10 24 | 25 | // 格里历历法实施日 26 | cGregorianAdoptionDay float64 = 15 27 | 28 | // 格里历历法实施日期TT时间(1582年10月15日)中午12点的儒略日 29 | cJulianGregorianBoundary float64 = 2299161.0 30 | 31 | // J2000.0的儒略日 (TT时间2000年1月1日中午12点 (UTC时间2000年1月1日11:58:55.816)的儒略日) 32 | cJulianDayJ2000 = 2451545.0 33 | 34 | // 儒略历1年有多少天 35 | cDaysOfAYear = 365.25 36 | ) 37 | 38 | // JulianDay 计算日期时间(TT)的儒略日 39 | // 40 | // (特别提醒,我们将一个日期时间转为儒略日时,其实使用的并不是真正的TT时间,而是我们常用的UTC或当地时区时间,故此无需考虑TT与UTC之间的转换) 41 | func JulianDay(year, month, day float64, timeParts ...float64) float64 { 42 | 43 | var hour, minute,second, millisecond float64 = 0, 0, 0, 0 44 | 45 | for timeIndex, timePart := range timeParts { 46 | switch timeIndex { 47 | case 0: 48 | hour = timePart 49 | case 1: 50 | minute = timePart 51 | case 2: 52 | second = timePart 53 | case 3: 54 | millisecond = timePart 55 | } 56 | } 57 | 58 | // 计算公式参见: https://zh.wikipedia.org/wiki/%E5%84%92%E7%95%A5%E6%97%A5 59 | // 或参见: https://blog.csdn.net/weixin_42763614/article/details/82880007 60 | 61 | a := math.Floor((14 - month) / 12) 62 | y := year + 4800 - a 63 | m := month + 12 * a - 3 64 | second += millisecond / 1000.0 65 | d := day + hour / 24.0 + minute / 1440.0 + second / 86400.0 66 | 67 | var jdn float64 68 | 69 | // 依据儒略历废弃日期和格里历实施日期,使用两个不同的公式计算儒略日JDN 70 | if year < cJulianAbandonmentYear || (year == cJulianAbandonmentYear && month < cJulianAbandonmentMonth) || (year == cJulianAbandonmentYear && month == cJulianAbandonmentMonth && day <= cJulianAbandonmentDay) { 71 | // 儒略历日期 72 | jdn = jdnInJulian(y, m, d) 73 | } else if year > cGregorianAdoptionYear || (year == cGregorianAdoptionYear && month > cGregorianAdoptionMonth) || (year == cGregorianAdoptionYear && month == cGregorianAdoptionMonth && day >= cGregorianAdoptionDay) { 74 | // 格里历日期 75 | jdn = jdnInGregorian(y, m, d) 76 | } else { 77 | // 在儒略历废弃与格里历实施这中间有一段日期,这段日期的儒略日计算使用格里历实施的起始日计算 78 | jdn = jdnInGregorian(cGregorianAdoptionYear, cGregorianAdoptionMonth, cGregorianAdoptionDay) 79 | } 80 | 81 | return Round(jdn - 0.5, 10) 82 | } 83 | 84 | // jdnInJulian 儒略历日期(TT)转儒略日JDN 85 | // 86 | // JDN表达式与JD的关系是: JDN = JD + 0.5 87 | func jdnInJulian(year, month, day float64) float64 { 88 | 89 | return day + math.Floor((153 * month + 2) / 5) + 365 * year + math.Floor(year / 4) - 32083 90 | } 91 | 92 | // jdnInGregorian 格里历日期(TT)转儒略日JDN 93 | // 94 | // JDN表达式与JD的关系是: JDN = JD + 0.5 95 | func jdnInGregorian(year, month, day float64) float64 { 96 | 97 | return day + math.Floor((153 * month + 2) / 5) + 365 * year + math.Floor(year / 4) - math.Floor(year / 100) + math.Floor(year / 400) - 32045 98 | } 99 | 100 | // JdToTimeMap 儒略日计算对应的日期时间(TT) 101 | // 102 | // (特别提醒,我们将一个日期时间转为儒略日时,其实使用的并不是真正的TT时间,而是我们常用的UTC或当地时区时间,故此无需考虑TT与UTC之间的转换) 103 | func JdToTimeMap(jd float64) map[string]int { 104 | 105 | jdn := jd + 0.5 106 | 107 | // 计算公式: https://blog.csdn.net/weixin_42763614/article/details/82880007 108 | Z := math.Floor(jdn) // 儒略日的整数部分 109 | F := jdn - Z // 儒略日的小数部分 110 | 111 | var A float64 112 | 113 | // 2299161 是1582年10月15日12时0分0秒 114 | if Z < cJulianGregorianBoundary { 115 | // 儒略历 116 | A = Z 117 | } else { 118 | a := math.Floor((Z - 2305507.25) / 36524.25) 119 | var IntervalDays float64 = 10 // 儒略历被废弃至格里历被启用之间相差多少天 120 | A = Z + IntervalDays + a - math.Floor(a/4) 121 | } 122 | 123 | var dayF float64 = 1 124 | var C float64 = 0 125 | var E float64 = 0 126 | var k float64 = 0 127 | 128 | for { 129 | B := A + 1524 // 以BC4717年3月1日0时为历元 130 | C = math.Floor((B - 122.1) / cDaysOfAYear) // 积年 131 | D := math.Floor(cDaysOfAYear * C) // 积年的日数 132 | E = math.Floor((B - D) / 30.6) // B-D为年内积日,E即月数 133 | dayF = B - D - math.Floor(30.6*E) + F 134 | 135 | // 否则即在上一月,可前置一日重新计算 136 | if dayF >= 1 { 137 | break 138 | } 139 | 140 | A -= 1 141 | k += 1 142 | } 143 | 144 | var year float64 // 年 145 | var month float64 // 月 146 | 147 | if E < 14 { 148 | month = E - 1 149 | } else { 150 | month = E - 13 151 | } 152 | 153 | if month > 2 { 154 | year = C - 4716 155 | } else { 156 | year = C - 4715 157 | } 158 | 159 | dayF += k 160 | if dayF == 0 { 161 | dayF += 1 162 | } 163 | 164 | // 天数分开成天与时分秒 165 | day := math.Floor(dayF) // 天 166 | dayD := dayF - day 167 | 168 | var hh, ii, ss, ms float64 169 | 170 | if dayD > 0 { 171 | hhF := dayD * 24 + 0.000000005 // 0.000000005补一个精度差 172 | hh = math.Floor(hhF) // 时 173 | hhD := hhF - hh 174 | if hhD > 0 { 175 | iiF := hhD * 60 176 | ii = math.Floor(iiF) // 分 177 | iiD := iiF - ii 178 | if iiD > 0 { 179 | ssF := iiD * 60 180 | ss = math.Floor(ssF) // 秒 181 | ssD := ssF - ss 182 | if ssD > 0 { 183 | ms = ssD * 1000 // 毫秒 184 | } 185 | } 186 | } 187 | } 188 | 189 | return map[string]int{ 190 | "year": int(year), 191 | "month": int(month), 192 | "day": int(day), 193 | "hour": int(hh), 194 | "minute": int(ii), 195 | "second": int(ss), 196 | "millisecond": int(ms), 197 | } 198 | } 199 | 200 | // TimeMapToTime 将time map转成loc时区的 *time.Time 201 | // 202 | // 该方法未将TT转为UTC,而是将TT强行等于UTC,如需TT日期,请不要转换, 203 | // (TT与UTC的计算公式可以参考 TT = UTC + 64.184s , 204 | // 但由于我们在将日期时间转换为儒略日时使用的并不是真正的TT时间,而是UTC或当地时区时间,故此处不做TT与UTC转换.) 205 | func TimeMapToTime(tm map[string]int, loc *time.Location) time.Time { 206 | if loc == nil { 207 | loc = time.Local 208 | } 209 | return time.Date(tm["year"],time.Month(tm["month"]),tm["day"],tm["hour"],tm["minute"],tm["second"],0,time.UTC).In(loc) 210 | } 211 | 212 | // JdToTime 将儒略日转成loc时区的 *time.Time 213 | // 214 | // 该方法未将TT转为UTC,而是将TT等于UTC,如需TT日期,请使用 JdToTimeMap 方法 215 | func JdToTime(jd float64, loc *time.Location) time.Time { 216 | tm := JdToTimeMap(jd) 217 | return TimeMapToTime(tm,loc) 218 | } 219 | 220 | // Mjd 计算日期时间(TT)的简化儒略日 221 | // 222 | // 简化儒略日(Modified Julian Day, MJD)是将儒略日(Julian Day, JD)进行简化后得到的新计时法。 223 | // 1957年,简化儒略日由史密松天体物理台(Smithsonian Astrophysical Observatory)引入。 224 | // 1957年史密松天体物理台为便用于记录“伴侣号”人造卫星的轨道,将儒略日进行了简化,并将其命名为简化儒略日,其定义为: MJD=JD-2400000.5 225 | // 儒略日2400000是1858年11月16日,因为JD从中午开始计算,所以简化儒略日的定义中引入偏移量0.5, 226 | // 这意味着MJD 0相当于1858年11月17日的凌晨,并且每一个简化儒略日都在世界时午夜开始和结束. 227 | func Mjd(year, month, day float64, timeParts ...float64) float64 { 228 | 229 | jd := JulianDay(year, month, day, timeParts...) 230 | 231 | return jdToMjd(jd) 232 | } 233 | 234 | // MjdToTimeMap 简化儒略日计算对应的日期时间(TT) 235 | func MjdToTimeMap(mjd float64) map[string]int { 236 | 237 | jd := mjdToJd(mjd) 238 | return JdToTimeMap(jd) 239 | } 240 | 241 | // jdToMjd 儒略日转换为简儒略日 242 | func jdToMjd(jd float64) float64 { 243 | return Round(jd - 2400000.5,10) 244 | } 245 | 246 | // mjdToJd 简儒略日转换为儒略日 247 | func mjdToJd(mjd float64) float64 { 248 | return Round(mjd + 2400000.5,10) 249 | } 250 | 251 | // julianCentury 计算标准历元起的儒略世纪 252 | func julianCentury(jd float64) float64 { 253 | return julianDayFromJ2000(jd) / cDaysOfAYear / 100.0 254 | } 255 | 256 | // julianThousandYear 计算标准历元起的儒略千年数 257 | func julianThousandYear(jd float64) float64 { 258 | return julianDayFromJ2000(jd) / cDaysOfAYear / 1000.0 259 | } 260 | 261 | // julianDayFromJ2000 计算标准历元起的儒略日 262 | func julianDayFromJ2000(jd float64) float64 { 263 | return jd - cJulianDayJ2000 264 | } 265 | -------------------------------------------------------------------------------- /julian_test.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestJulianDay(t *testing.T) { 9 | 10 | jd := JulianDay(1581,1,1,12) 11 | if Round(jd,10) == 2298519 { 12 | t.Log("passed") 13 | }else{ 14 | t.Error(jd) 15 | } 16 | 17 | jd = JulianDay(2021,12,6) 18 | if Round(jd,10) == 2459554.5 { 19 | t.Log("passed") 20 | }else{ 21 | t.Error(jd) 22 | } 23 | 24 | jd = JulianDay(2021,12,6,12) 25 | if Round(jd,10) == 2459555 { 26 | t.Log("passed") 27 | }else{ 28 | t.Error(jd) 29 | } 30 | 31 | jd = JulianDay(2021,12,6,12,10,10) 32 | if Round(jd,10) == 2459555.007060185 { 33 | t.Log("passed") 34 | }else{ 35 | t.Error(jd) 36 | } 37 | 38 | } 39 | 40 | func TestMjd(t *testing.T) { 41 | jd := Mjd(2021,12,6) 42 | if Round(jd,10) == 59554 { 43 | t.Log("passed") 44 | }else{ 45 | t.Error(jd) 46 | } 47 | 48 | jd = Mjd(2021,12,6,12) 49 | if Round(jd,10) == 59554.5 { 50 | t.Log("passed") 51 | }else{ 52 | t.Error(jd) 53 | } 54 | 55 | jd = Mjd(2021,12,6,12,10,10) 56 | if Round(jd,10) == 59554.5070601851 { 57 | t.Log("passed") 58 | }else{ 59 | t.Error(jd) 60 | } 61 | } 62 | 63 | func TestJdToTimeMap(t *testing.T) { 64 | datetime := JdToTimeMap(2298519) 65 | if datetime["year"] == 1581 && datetime["month"] == 1 && datetime["day"] == 1 && datetime["hour"] == 12 { 66 | t.Log("passed") 67 | }else{ 68 | t.Error(datetime) 69 | } 70 | 71 | datetime = JdToTimeMap(2459554.5) 72 | if datetime["year"] == 2021 && datetime["month"] == 12 && datetime["day"] == 6 { 73 | t.Log("passed") 74 | }else{ 75 | t.Error(datetime) 76 | } 77 | 78 | datetime = JdToTimeMap(2459555) 79 | if datetime["year"] == 2021 && datetime["month"] == 12 && datetime["day"] == 6 && datetime["hour"] == 12 { 80 | t.Log("passed") 81 | }else{ 82 | t.Error(datetime) 83 | } 84 | 85 | datetime = JdToTimeMap(2459555.007060185) 86 | if datetime["year"] == 2021 && datetime["month"] == 12 && datetime["day"] == 6 && datetime["hour"] == 12 && datetime["minute"] == 10 && datetime["second"] == 10 { 87 | t.Log("passed") 88 | }else{ 89 | // fmt.Printf("%d年%d月%d日%d时%d分%d秒\n",datetime["year"], datetime["month"],datetime["day"],datetime["hour"],datetime["minute"],datetime["second"]) 90 | t.Error(datetime) 91 | } 92 | 93 | } 94 | 95 | func TestJdToTime(t *testing.T) { 96 | datetime := JdToTime(2459555.007060185,time.Local) 97 | t.Log(datetime.Format(time.RFC3339)) 98 | } 99 | 100 | func TestMjdToTimeMap(t *testing.T) { 101 | datetime := MjdToTimeMap(59554) 102 | if datetime["year"] == 2021 && datetime["month"] == 12 && datetime["day"] == 6 { 103 | t.Log("passed") 104 | }else{ 105 | t.Error(datetime) 106 | } 107 | 108 | datetime = MjdToTimeMap(59554.5) 109 | if datetime["year"] == 2021 && datetime["month"] == 12 && datetime["day"] == 6 && datetime["hour"] == 12 { 110 | t.Log("passed") 111 | }else{ 112 | t.Error(datetime) 113 | } 114 | 115 | datetime = MjdToTimeMap(59554.507060185075) 116 | if datetime["year"] == 2021 && datetime["month"] == 12 && datetime["day"] == 6 && datetime["hour"] == 12 && datetime["minute"] == 10 && datetime["second"] == 10 { 117 | t.Log("passed") 118 | }else{ 119 | t.Error(datetime) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package gocalendar 2 | 3 | import ( 4 | "math" 5 | "strings" 6 | ) 7 | 8 | 9 | 10 | // Pow x的整数n次方 11 | func Pow(x float64, n int) float64 { 12 | if x == 0 { 13 | return 0 14 | } 15 | result := calPow(x, n) 16 | if n < 0 { 17 | result = 1 / result 18 | } 19 | return result 20 | } 21 | 22 | func calPow(x float64, n int) float64 { 23 | if n == 0 { 24 | return 1 25 | } 26 | if n == 1 { 27 | return x 28 | } 29 | 30 | // 向右移动一位 31 | result := calPow(x, n>>1) 32 | result *= result 33 | 34 | // 如果n是奇数 35 | if n&1 == 1 { 36 | result *= x 37 | } 38 | 39 | return result 40 | } 41 | 42 | // Round 四舍五入保留prec位小数 43 | func Round(n float64, prec int) float64 { 44 | e := math.Pow10(prec) 45 | return math.Round(n*e) / e 46 | 47 | // fs := fmt.Sprintf("%."+strconv.Itoa(prec)+"f",n) 48 | // r,err := strconv.ParseFloat(fs,64) 49 | // if err != nil { 50 | // return 0 51 | // } 52 | // return r 53 | } 54 | 55 | // B2i bool转int 56 | // 57 | // true=>1,false=>0 58 | func B2i(b bool) int { 59 | if b { 60 | return 1 61 | } 62 | return 0 63 | } 64 | 65 | // I2b int转bool 66 | // 67 | // 0=>false,other=>true 68 | func I2b(i int) bool { 69 | return i != 0 70 | } 71 | 72 | // StringSplice 字符串拼接 73 | func StringSplice(str ...string) string { 74 | var builder strings.Builder 75 | var totalLength int 76 | 77 | for _, s := range str { 78 | totalLength += len(s) 79 | builder.WriteString(s) 80 | } 81 | 82 | if totalLength != builder.Len() { 83 | return "" 84 | } 85 | 86 | return builder.String() 87 | } --------------------------------------------------------------------------------