当前位置:   article > 正文

Python万年历(含农历、节气等)_python万年历代码包含农历

python万年历代码包含农历

前已在Python公历转换农历及简易万年历一文中实现农历计算的基本功能(内附农历计算原理),本篇利用这些功能完成UI小程序。

完整代码下载:GitHub - Luni-solar-Calendar: 万年历(含农历、节气等)

5积分支持作者:CSDN - Python万年历源码

以下为显示月历部分的代码示例。

  1. def getSolorTerms(year):
  2. jqb = [[i] for i in range(12)] # [月序,[日序, 节气序] * n]
  3. for i in range(24):
  4. jq = JD2date(SolarTerms(year, i * 15), 8)
  5. jqn, jqy, jqr = jq.triple()
  6. if jqn != year:
  7. jq = JD2date(SolarTerms(year + (year - jqn), i * 15), 8)
  8. jqn, jqy, jqr = jq.triple()
  9. if jqn == year:
  10. jqb[jqy-1].append([int(jqr), (i + 6) % 24]) # 按月存储
  11. for j in range(len(jqb)): jqb[j].pop(0)
  12. return jqb # [[日序, 节气序] * n]
  13. def setNYE(festival, ymb, shuoJD): # 重设节日日期
  14. yx = ymb.index(festival[0])
  15. if DateDiffer(shuoJD[yx + 1], shuoJD[yx]) == 29:
  16. festival[1] = '廿九'
  17. else:
  18. festival[1] = '三十'
  19. return festival
  20. def getYearMonth(ui, wheel=0): # 根据输入重设年月
  21. edit = ui.cblYear.currentText()
  22. try:
  23. year = int(re.search(r'-?\d+', edit).group())
  24. except:
  25. return 0, 0 # 异常输入
  26. if year == 0: return 0, 0
  27. if edit[:2].lower() == 'bc': year = -year
  28. year += wheel
  29. century = year // 100
  30. if start_century * 100 <= year < endCentury * 100:
  31. ui.cblCentury.setCurrentIndex(century - start_century)
  32. if century == 0: ui.cblYear.setCurrentIndex(year % 100 - 1)
  33. else: ui.cblYear.setCurrentIndex(year % 100)
  34. else:
  35. ui.cblCentury.setCurrentIndex(-1)
  36. ui.cblYear.clear()
  37. if wheel == 0: ui.cblYear.setCurrentText(edit)
  38. else: ui.cblYear.setCurrentText(str(year))
  39. if ui.sender() == ui.cblFindFestival: month = -1
  40. else: month = ui.cblMonth.currentIndex()
  41. global setYear
  42. if year != setYear or setYear == '':
  43. setYear = year
  44. updateYear()
  45. return year, month
  46. def updateYear():
  47. global yearYMB, yearSJD, yearST
  48. yearYMB, yearSJD = LunarCalendar(setYear, 0)
  49. yearST = getSolorTerms(setYear)
  50. def displayMonth(ui):
  51. year, month = getYearMonth(ui)
  52. if year == 0: return 0, 0, 0
  53. ymb, shuoJD, jqb = yearYMB, yearSJD, yearST
  54. # ymb, shuoJD = LunarCalendar(year, 0)
  55. # jqb = getSolorTerms(year)
  56. if DateCompare(ephem.julian_date((year, 12, 31)), shuoJD[-2] + 29):
  57. ymb1, shuoJD1 = LunarCalendar(year + 1)
  58. ymb = ymb[:-2] + ymb1[:2]
  59. shuoJD = shuoJD[:-2] + shuoJD1[:3]
  60. currentFes, fesDay = '', 0
  61. if ui.sender() == ui.cblFindFestival:
  62. currentFes = ui.cblFindFestival.currentText()
  63. month = jumpLCF(currentFes, ymb, shuoJD)
  64. ui.cblMonth.setCurrentIndex(month)
  65. days[1] = 29 if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0 else 28
  66. if year < 1582 and year % 4 == 0: days[1] = 29
  67. i = month
  68. ysJD = ephem.julian_date((year, i + 1))
  69. szy = findSZY(ysJD, shuoJD) # 每月1日对应的农历月
  70. ysRQ = DateDiffer(ysJD, shuoJD[szy]) # 每月1日的农历日期
  71. yue0 = DateDiffer(shuoJD[szy + 1], shuoJD[szy])
  72. yue1 = DateDiffer(shuoJD[szy + 2], shuoJD[szy + 1])
  73. blank = int((ysJD + 0.5) % 7)
  74. for j in range(6):
  75. for k in range(7):
  76. # 计算日期
  77. day = j * 7 + k - blank + 1
  78. rqx = ysRQ + (j+1) * 7 - 7 + k - blank
  79. if rqx < 0: # 月首日所在农历月上月
  80. yue = DateDiffer(shuoJD[szy], shuoJD[szy - 1])
  81. rq = nlrq[rqx % yue]
  82. yx = szy - 1
  83. elif 0 <= rqx < yue0:
  84. rq = nlrq[rqx]
  85. yx = szy
  86. elif yue0 <= rqx < yue0 + yue1:
  87. rq = nlrq[rqx - yue0]
  88. yx = szy + 1
  89. elif rqx >= yue0 + yue1:
  90. rq = nlrq[rqx - yue0 - yue1]
  91. yx = szy + 2
  92. if day == 1:
  93. yx1 = yx
  94. rq1 = rq
  95. if day == days[i]:
  96. yx2 = yx
  97. rq2 = rq
  98. dateInfo[j * 7 + k] = [day, ymb[yx], rq, ysJD+day-1, jqb[i]]
  99. if year == 1582 and i == 9: dateInfo[j * 7 + k][-1] = [jqb[i][0], jqb[i-1][1]] # 该月无节气,月干支序从上月获取
  100. if rq == '初一': rq = ymb[yx]
  101. # 显示月历
  102. if j == 0 and k < blank: # 上月
  103. ui.labs[j][k].setText(font(days[i-1]-blank+k+1, 20) + font(rq))
  104. else: # 本月
  105. if day <= days[i]:
  106. if year == 1582 and i == 9 and day > 4:
  107. if day < 15: continue
  108. else: ui.labs[j+(k-10)//7][k-3].setText(font(day, 20, "black", 800) + font(rq))
  109. else:
  110. ui.labs[j][k].setText(font(day, 20, "black", 800) + font(rq)) # "500;font-family:微软雅黑"
  111. else: # 次月
  112. if year == 1582 and i == 9:
  113. ui.labs[j+(k-10)//7][k-3].setText(font(day - days[i], 20) + font(rq))
  114. if day - days[i] == 11:
  115. for m in range(10): ui.labs[j-1+(m+k-2)//7][(m+k-2)%7].setText("")
  116. else: ui.labs[j][k].setText(font(day-days[i], 20) + font(rq))
  117. # 显示节日
  118. qmDay = 0
  119. for jq in jqb[i]: # 节气
  120. jqrx = jq[0] + blank - 1
  121. if year == 1582 and i == 9: jqrx -= 10
  122. ui.labs[jqrx//7][jqrx%7].setText(font(jq[0], 20, "black", 800) + font(jieqi[jq[1]], 12, "red"))
  123. if jq[1] == 7: qmDay = jq[0]
  124. if year >= 1949: # 公历节日起始年
  125. if qmDay: ui.labs[(qmDay+blank-1)//7][(qmDay+blank-1)%7].setText(font(qmDay, 20, "red", 800) + font("清明", 12, "red"))
  126. for fes in scfestivals:
  127. if fes[0] == month + 1:
  128. jqrx = fes[1] + blank - 1
  129. ui.labs[jqrx // 7][jqrx % 7].setText(font(fes[1], 20, "red", 800) + font(fes[2], 12, "red"))
  130. if year > 1911: # 农历节日起始年
  131. rqx1 = nlrq.index(rq1)
  132. for fes in lcfestivals:
  133. if fes[2] == '除夕': fes = setNYE(fes, ymb, shuoJD)
  134. jqrx = nlrq.index(fes[1])
  135. if fes[0] == ymb[yx1] and jqrx >= rqx1: # 该月农历首日
  136. jqrx += -rqx1 + blank
  137. ui.labs[jqrx // 7][jqrx % 7].setText(font(jqrx-blank+1, 20, "red", 800) + font(fes[2], 12, "red"))
  138. elif fes[0] == ymb[yx1+1] and jqrx <= nlrq.index(rq2): # 该月农历末日
  139. jqrx += DateDiffer(shuoJD[yx1 + 1], shuoJD[yx1]) - rqx1 + blank
  140. ui.labs[jqrx // 7][jqrx % 7].setText(font(jqrx - blank + 1, 20, "red", 800) + font(fes[2], 12, "red"))
  141. elif yx2 - yx1 == 2 and fes[0] == ymb[yx2]: # 跨2月
  142. jqrx += DateDiffer(shuoJD[yx1 + 2], shuoJD[yx1]) - rqx1 + blank
  143. if 0 < jqrx <= days[i] + blank: ui.labs[jqrx // 7][jqrx % 7].setText(font(jqrx - blank + 1, 20, "red", 800) + font(fes[2], 12, "red"))
  144. if fes[2] == currentFes: fesDay = jqrx - blank + 1
  145. return year, month, fesDay
  146. def displayDate(ui):
  147. global selected
  148. try:
  149. selected.setStyleSheet("")
  150. except:
  151. pass
  152. if ui.sender() in [None, ui.btnToday]: # 设为今日
  153. year, month, day = time.localtime(time.time())[0:3]
  154. month -= 1
  155. ui.cblCentury.setCurrentIndex(-start_century + year // 100)
  156. ui.cblYear.setCurrentIndex(year % 100)
  157. ui.cblMonth.setCurrentIndex(month % 12)
  158. displayMonth(ui)
  159. borderDate(ui, month, day)
  160. else:
  161. if ui.sender() in [ui.cblCentury, ui.cblYear, ui.cblMonth, ui.btnLastMonth, ui.btnNextMonth, ui.btnLastYear, ui.btnNextYear, ui.cblFindFestival]: # 设为原公历日
  162. year, month, day = displayMonth(ui)
  163. if year == 0: return 0
  164. if ui.sender() != ui.cblFindFestival: day = int(re.findall(r'(\d+)</font>', ui.labInfo.text())[0]) # 公历日期
  165. if day > days[month]: day = days[month] # 跳到上月底
  166. borderDate(ui, month, day)
  167. else: # 点击日期跳转
  168. year, month = getYearMonth(ui)
  169. if year == 0: return 0
  170. selected = ui.sender()
  171. if selected.text() == "": return 0 # 1582年被删除的日期
  172. day = int(re.findall(r'(\d+)</font>', selected.text())[0]) # 公历日期
  173. if not re.search(r"<font style='font-size:20px; text-align:center; color:gray", selected.text()): # 本月内
  174. ui.sender().setStyleSheet("QLabel{border:3px solid; border-radius: 5px; border-color:#1E90FF}") # 1E90FF 9400D3
  175. else: # 跳转前后月
  176. if day > 20: lastMonth(ui)
  177. else: nextMonth(ui)
  178. year, month = displayMonth(ui)[:2] # 更新月历
  179. borderDate(ui, month, day)
  180. # 日期相关显示信息
  181. jdn = math.floor(ephem.julian_date((year, month + 1, day)) + 8/24 + 0.5)
  182. jdn0 = math.floor(ephem.julian_date(time.localtime(time.time())[0:3]) + 8 / 24 + 0.5)
  183. difference = jdn - jdn0
  184. if difference > 0: difference = '距今:' + str(abs(difference)) + '天后'
  185. elif difference == 0: difference = '今天'
  186. else: difference = '距今:' + str(abs(difference)) + '天前'
  187. week = weeks[math.floor(jdn % 7)]
  188. ym, rq, JD, jqrq = dateInfo[day-dateInfo[0][0]][1:]
  189. nian = year
  190. if month < 3 and yuefen.index(ym.split('闰')[-1]) >= 10: nian -= 1
  191. if nian < 0: ngz = gz[(nian - 3) % 60]
  192. else: ngz = gz[(nian - 4) % 60]
  193. nm = gyjn(year)
  194. sxm = zodiac[(nian - 4) % 12]
  195. if year < 0: year += 1
  196. jqr = 99
  197. for i in range(len(jqrq)):
  198. if jqrq[i][1] % 2 == 1: jqr = jqrq[i][0]
  199. if day >= jqr: ygz = gz[(year * 12 + 13 + month) % 60]
  200. else: ygz = gz[(year * 12 + 12 + month) % 60]
  201. rgz = gz[math.floor(JD + 8/24 + 0.5 + 49) % 60]
  202. # JDN、距今、年名、月、星期、日、农历月日、年干支、生肖名、月干支、日干支
  203. ui.labInfo.setText("<br/>JDN {}<br/>{}<br/>{}<br/>{}月 星期{}<br/>{}{}{}年 〖{}〗<br/>{}月 {}日<br/><br/>".format(
  204. jdn, font(difference, 12, "black"), nm, month+1, week, font(day, 50, "black"), font(ym+rq, 17, "black"), ngz, sxm, ygz, rgz))
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

UI(以PyQt5为例)中使用Label控件显示月历,为完成点击后显示该日信息功能,需要对QLabel类添加单击函数。QComboBox的鼠标滚动功能默认滚动到最前或最末项后不再改变,需要重写wheelEvent事件使其在某年1月向上滚动时跳转到上一年12月。

Qt中每次设置样式表会清空原先的样式,故而选择使用html实现字体相关设置,保证设置样式表后字体不会变化,并且可通过获取Label文本判断日期类型。

虽然前已实现任意日期的公历转农历函数,但这里并不使用。如果每次点击标签都重新计算,会极大地增大计算量。本程序在每年内只计算一次,使用全局变量存储,跳转到年内其他月可以直接读取,无需重新计算。

程序中世纪列表提供前13世纪至30世纪,因为PyEphem库在计算BC13世纪至BC30世纪间的平均误差达2小时,导致确定农历月份容易出错,在早于BC13世纪前,误差更大。虽然在30世纪以后的较长时间内,ephem库的误差还很小,但也并非一般所需的功能。同时,对于特殊需求,程序中的年列表控件也开放了输入任意年份的功能。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/389333
推荐阅读
相关标签
  

闽ICP备14008679号