eba3ff7eaf5607d5beb57615f710fe0102aecf81
8 LunarCalendar by Adrian I. Lam (2019),
9 a Python 3 port of the JavaScript LunarCalendar by zzyss86,
10 available at <https://github.com/zzyss86/LunarCalendar>.
12 This is only a partial port. I have ported
13 LunarCalendar.solarToLunar(year, month, day) and
14 LunarCalendar.lunarToSolar(year, month, day) only,
15 and have ignored everything related to holidays.
17 Since the original author did not specify any license, I will
18 also not license this script. This script comes with no warranties,
19 expressed or implied. In particular, the warranties of merchantability,
20 fitness for a particular purpose and noninfringement are disclaimed.
23 def __formatDayD4(self
, month
, day
):
25 month
= '0' + str(month
) if month
< 10 else str(month
)
26 day
= '0' + str(day
) if day
< 10 else str(day
)
27 return 'd' + month
+ day
32 __heavenlyStems
= ['甲', '乙', '丙', '丁', '戊',
33 '己', '庚', '辛', '壬', '癸'] # 天干
34 __earthlyBranches
= ['子', '丑', '寅', '卯', '辰', '巳',
35 '午', '未', '申', '酉', '戌', '亥'] # 地支
36 __zodiac
= ['鼠', '牛', '虎', '兔', '龍', '蛇',
37 '馬', '羊', '猴', '雞', '狗', '豬'] # 對應地支十二生肖
38 __solarTerm
= ['小寒', '大寒', '立春', '雨水', '驚蟄', '春分',
39 '清明', '穀雨', '立夏', '小滿', '芒種', '夏至',
40 '小暑', '大暑', '立秋', '處暑', '白露', '秋分',
41 '寒露', '霜降', '立冬', '小雪', '大雪', '冬至'] # 二十四節氣
42 __monthCn
= ['正', '二', '三', '四', '五', '六',
43 '七', '八', '九', '十', '十一', '十二']
44 __dateCn
= ['初一', '初二', '初三', '初四', '初五', '初六',
45 '初七', '初八', '初九', '初十', '十一', '十二',
46 '十三', '十四', '十五', '十六', '十七', '十八',
47 '十九', '二十', '廿一', '廿二', '廿三', '廿四',
48 '廿五', '廿六', '廿七', '廿八', '廿九', '三十', '卅一']
55 # *農曆每月的天數的數組(需轉換為二進制,得到每月大小,
56 # 0=小月(29日),1=大月(30日));]
58 __dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
59 with
open(os
.path
.join(__dir
, 'lunarInfo.csv')) as __lunarInfoCSV
:
60 for __row
in csv
.reader(__lunarInfoCSV
):
61 __lunarInfo
.append([int(x
) for x
in __row
])
63 # 二十四節氣數據,節氣點時間(單位是分鐘)
65 __termInfo
= [0, 21208, 42467, 63836, 85337, 107014, 128867, 150921,
66 173149, 195551, 218072, 240693, 263343, 285989, 308563,
67 331033, 353350, 375494, 397447, 419210, 440795, 462224,
73 def __getLunarLeapYear(self
, year
):
76 @param {Number} year 農曆年
79 yearData
= self
.__lunarInfo
[year
- self
.__minYear
]
82 def __getLunarYearDays(self
, year
):
85 @param {Number} year 農曆年
87 yearData
= self
.__lunarInfo
[year
- self
.__minYear
]
88 leapMonth
= yearData
[0]
89 monthData
= yearData
[3]
92 for i
in range(15, -1, -1):
93 monthDataArr
.append((monthData
& (1 << i
)) >> i
)
95 numMonthsInYear
= 13 if leapMonth
else 12
98 for i
in range(numMonthsInYear
):
99 if monthDataArr
[i
] == 0:
107 'yearDays': yearDays
,
108 'monthDays': monthDays
111 def __getLunarDateByBetween(self
, year
, between
):
114 @param {Number} year,between 農曆年,間隔天數
116 lunarYearDays
= self
.__getLunarYearDays(year
)
117 end
= between
if between
> 0 else lunarYearDays
['yearDays'] + between
118 monthDays
= lunarYearDays
['monthDays']
121 for i
in range(len(monthDays
)):
122 tempDays
+= monthDays
[i
]
125 tempDays
= tempDays
- monthDays
[i
]
128 return [year
, month
, end
- tempDays
+ 1]
130 def __getLunarByBetween(self
, year
, month
, day
):
133 @param {Number} year 公曆年,月,日
135 yearData
= self
.__lunarInfo
[year
- self
.__minYear
]
136 zenMonth
= yearData
[1]
138 between
= self
.__getDaysBetweenSolar(year
, zenMonth
- 1, zenDay
,
143 lunarYear
= year
if between
> 0 else year
- 1
144 return self
.__getLunarDateByBetween(lunarYear
, between
)
146 def __getDaysBetweenSolar(self
, year
, month
, day
, year1
, month1
, day1
):
150 # https://stackoverflow.com/a/151211
151 d0
= datetime
.date(year
, month
+ 1, day
)
152 d1
= datetime
.date(year1
, month1
+ 1, day1
)
156 def __getDaysBetweenZheng(self
, year
, month
, day
):
159 @param {Number} year,month,day 農年,月(0-12,有閏月),日
161 lunarYearDays
= self
.__getLunarYearDays(year
)
162 monthDays
= lunarYearDays
['monthDays']
164 for i
in range(len(monthDays
)):
169 return days
+ day
- 1
171 def __getTerm(self
, y
, n
):
174 31556925974.7為地球公轉週期,是毫秒
175 1890年的正小寒點:01-05 16:02:31,1890年為基準點
176 @param {Number} y 公曆年
177 @param {Number} n 第幾個節氣,從0小寒起算
178 由於農曆24節氣交節時刻採用近似算法,可能存在少量誤差(30分鐘內)
180 offsetms
= 31556925974.7 * (y
- 1890) + self
.__termInfo
[n
] * 60000
181 offDate
= datetime
.datetime(
182 1890, 1, 5, 16, 2, 31,
183 tzinfo
=datetime
.timezone
.utc
184 ) + datetime
.timedelta(milliseconds
=offsetms
)
187 def __getYearTerm(self
, year
):
195 day
= self
.__getTerm(year
, i
)
198 res
[self
.__formatDayD4(month
- 1, day
)] = self
.__solarTerm
[i
]
201 def __getYearZodiac(self
, year
):
204 @param {Number} year 干支所在年(默認以立春前的公曆年作為基數)
206 num
= year
- 1890 + 25 # 參考干支紀年的計算,生肖對應地支
207 return self
.__zodiac
[num %
12]
209 def __cyclical(self
, num
):
212 @param {Number} num 60進制中的位置(把60個天干地支,當成一個60進制的數)
215 self
.__heavenlyStems
[num %
10] +
216 self
.__earthlyBranches
[num %
12]
219 def __getLunarYearName(self
, year
, offset
=0):
222 @param {Number} year 干支所在年
223 @param {Number} offset 偏移量,默認為0,便於查詢一個年跨兩個干支紀年(以立春為分界線)
225 # 1890年1月小寒(小寒一般是1月5或6日)以前為己丑年,在60進制中排25
226 return self
.__cyclical(year
- 1890 + 25 + offset
)
228 def __getLunarMonthName(self
, year
, month
, offset
=0):
231 @param {Number} year,month 公曆年,干支所在月
232 @param {Number} offset 偏移量,默認為0,便於查詢一個月跨兩個干支紀月(有立春的2月)
234 # 1890年1月小寒以前為丙子月,在60進制中排12
235 return self
.__cyclical((year
- 1890) * 12 + month
+ 12 + offset
)
237 def __getLunarDayName(self
, year
, month
, day
):
240 @param {Number} year,month,day 公曆年,月,日
243 # 1890/1/1與1970/1/1 相差29219日, 1890/1/1 日柱為壬午日(60進制18)
245 datetime
.date(year
, month
+ 1, day
) +
246 datetime
.timedelta(days
=29219+18) -
247 datetime
.date(1970, 1, 1)
250 return self
.__cyclical(dayCyclical
.days
)
252 def __getSolarMonthDays(self
, year
, month
):
255 @param {Number} year 公曆年
256 @param {Number} month 公曆月
258 monthDays
= [31, 29 if self
.__isLeapYear(year
) else 28,
259 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
260 return monthDays
[month
]
262 def __isLeapYear(self
, year
):
265 @param {Number} year 公曆年
267 return (year %
4 == 0 and year %
100 != 0) or year %
400 == 0
269 def __formatDate(self
, year
=None, month
=None, day
=None, _minYear
=None):
271 統一日期輸入參數(輸入月份從1開始,內部月份統一從0開始)
273 now
= datetime
.datetime
.now()
274 year
= int(year
) if year
is not None else now
.year
275 month
= int(month
) - 1 if month
is not None else now
.month
- 1
276 day
= int(day
) if day
is not None else now
.day
277 if year
< (_minYear
if _minYear
is not None
278 else self
.__minYear
+ 1) or year
> self
.__maxYear
:
281 'msg': 'Year out of range'
289 def lunarToSolar(self
, _year
, _month
, _day
):
292 @param {Number} year,month,day 農曆年,月(1-13,有閏月),日
295 inputDate
= self
.__formatDate(_year
, _month
, _day
)
302 year
= inputDate
['year']
303 month
= inputDate
['month']
304 day
= inputDate
['day']
306 between
= self
.__getDaysBetweenZheng(year
, month
, day
) # 離正月初一的天數
307 yearData
= self
.__lunarInfo
[year
- self
.__minYear
]
308 zenMonth
= yearData
[1]
312 datetime
.date(year
, zenMonth
, zenDay
) +
313 datetime
.timedelta(days
=between
)
316 'year': offDate
.year
,
317 'month': offDate
.month
,
321 def solarToLunar(self
, _year
, _month
, _day
):
323 Converts Gregorian date to lunar calendar.
324 _year: integer between 1891 and 2100 inclusive.
325 _month, _day: integer, 1-indexed.
327 inputDate
= self
.__formatDate(_year
, _month
, _day
, self
.__minYear
)
335 year
= inputDate
['year']
336 month
= inputDate
['month']
337 day
= inputDate
['day']
340 termList
= self
.__cache
[year
] # 二十四節氣
342 self
.__cache
[year
] = self
.__getYearTerm(year
)
343 termList
= self
.__cache
[year
]
345 term2
= [k
for (k
, v
) in termList
.items() if v
== '立春'][0]
346 term2
= int(term2
[-1])
348 firstTerm
= self
.__getTerm(year
, month
* 2)
350 year
+ 1 if month
> 1 or month
== 1 and day
>= term2
353 GanZhiMonth
= month
+ 1 if day
>= firstTerm
else month
355 lunarDate
= self
.__getLunarByBetween(year
, month
, day
)
356 lunarLeapMonth
= self
.__getLunarLeapYear(lunarDate
[0])
358 if lunarLeapMonth
> 0 and lunarLeapMonth
== lunarDate
[1]:
359 lunarMonthName
= '閏' + self
.__monthCn
[lunarDate
[1] - 1] + '月'
360 elif lunarLeapMonth
> 0 and lunarDate
[1] > lunarLeapMonth
:
361 lunarMonthName
= self
.__monthCn
[lunarDate
[1] - 1] + '月'
363 lunarMonthName
= self
.__monthCn
[lunarDate
[1]] + '月'
366 term
= termList
[self
.__formatDayD4(month
, day
)]
370 'zodiac': self
.__getYearZodiac(GanZhiYear
),
371 'GanZhiYear': self
.__getLunarYearName(GanZhiYear
),
372 'GanZhiMonth': self
.__getLunarMonthName(year
, GanZhiMonth
),
373 'GanZhiDay': self
.__getLunarDayName(year
, month
, day
),
375 'lunarYear': lunarDate
[0],
376 'lunarMonth': lunarDate
[1] + 1,
377 'lunarDay': lunarDate
[2],
378 'lunarMonthName': lunarMonthName
,
379 'lunarDayName': self
.__dateCn
[lunarDate
[2] - 1],
380 'lunarLeapMonth': lunarLeapMonth