当前位置:首页 > 百科 > 正文

利用PyEphem库进行任意日期的公历转换农历

一般使用的万年历,只提供距今前后百年的日历。这是因为其所用的计算方法是一种简便的计算方法,适用范围较小。其次,天文学方法计算量大,不适合日常软件使用。但如果要进行历史研究,范围就超出常用日历,本文即实现计算任意日期的农历。

由于天文星历数据太大,本文的代码是直接利用的库。该库只提供了二分二至时间,没有24节气。但是也提供了任意时间的太阳黄经,可以利用此项计算24节气。

中国阴阳历基础:

1. 以太阳历确定岁首,方法是通过测量影长确定冬至。以冬至所在月为子月,其后每月依次排序,闰月无建。

2. 现行农历用寅正,即冬至后第二个月(冬至月起的第三月),冬至确定在十一月。

3. 将二十四节气中的偶数序的节气称为十二中气。每月对应一个中气,若无中气,则该月作为闰月。

4. 使用节气月排定月份干支。

5. 同时定气和定朔的情况下,可能出现多个无中气月,如此则规定只有连续两个冬至月之间出现十三次合朔年份的的第一个无中气月置闰。

import math, ephem
 
yuefen =["正月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"]
nlrq = ["初一","初二","初三","初四","初五","初六","初七","初八","初九","初十","十一","十二","十三","十四","十五","十六","十七","十八","十九","二十","廿一","廿二","廿三","廿四","廿五","廿六","廿七","廿八","廿九","三十"]
tiangan = ["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
dizhi = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
gz = [''] * 60  # 六十甲子表
for i in range(60):
	gz[i] = tiangan[i % 10] + dizhi[i % 12]
 
def EquinoxSolsticeJD(year, angle):
	if 0 <= angle < 90:
		date = ephem.next_vernal_equinox(year)
	elif 90 <= angle < 180:
		date = ephem.next_summer_solstice(year)
	elif 180 <= angle < 270:
		date = ephem.next_autumn_equinox(year)
	else:
		date = ephem.next_winter_solstice(year)
	JD = ephem.julian_date(date)

	return JD
 
# 计算二十四节气
def SolarLongitube(JD):
	date = ephem.Date(JD - 2415020)
	s = ephem.Sun(date)  # date应为UT时间
	sa = ephem.Equatorial(s.ra, s.dec, epoch=date)
	se = ephem.Ecliptic(sa)
	L = se.lon / ephem.degree / 180 * math.pi
	return L
 
def SolarTerms(year, angle):
	if angle > 270: year -= 1
	if year == 0: year -= 1  # 公元0改为公元前1
	JD = EquinoxSolsticeJD(str(year), angle)  # 初值
	JD1 = JD
	while True:
		JD2 = JD1
		L = SolarLongitube(JD2)
		JD1 += math.sin(angle * math.pi / 180 - L) / math.pi * 180
		if abs(JD1 - JD2) < 0.00001:
			break # 精度小于1 second
	return JD1 # UT
 
def DateCompare(JD1, JD2): # 输入ut,返回ut+8的比较结果
	JD1 += 0.5 + 8/24
	JD2 += 0.5 + 8/24
	if int(JD1) >= int(JD2): return True

	else: return False
 
def dzs_search(year): # 寻找年前冬至月朔日
	if year == 1: year -= 1  # 公元0改为公元前1
	dz = ephem.next_solstice(str(year-1) + '/12') # 年前冬至
	jd = ephem.julian_date(dz)
	# 可能的三种朔日
	date1 = ephem.next_new_moon(ephem.Date(jd - 2415020 - 0))
	jd1 = ephem.julian_date(date1)
	date2 = ephem.next_new_moon(ephem.Date(jd - 2415020 - 29))
	jd2 = ephem.julian_date(date2)
	date3 = ephem.next_new_moon(ephem.Date(jd - 2415020 - 31))
	jd3 = ephem.julian_date(date3)
	if DateCompare(jd, jd1): # 冬至合朔在同一日或下月
		return date1
	elif DateCompare(jd, jd2) and (not DateCompare(jd, jd1)):
		return date2
	elif DateCompare(jd, jd3): # 冬至在上月
		return date3
 
def SolarLunarCalendar(date): # 默认输入ut+8时间
	JD = ephem.julian_date(date) - 8/24 # ut
	year = ephem.Date(JD + 8/24 - 2415020).triple()[0]
	shuo = []
	shuo.append(dzs_search(year))  # 冬至朔
	sJD1 = ephem.julian_date(shuo[0])
	next_dzs = dzs_search(year+1) # 次年冬至朔
	dzsJD = ephem.julian_date(next_dzs)

	if DateCompare(JD, dzsJD):
		shuo[0] = next_dzs
		next_dzs = dzs_search(year+2)
		dzsJD = ephem.julian_date(next_dzs)
	# 查找所在月及判断置闰
	run = ''
	szy = 0
	i = -2  # 中气序
	j = -1  # 计算连续两个冬至月中的合朔次数
	zry = 99 # 无效值
	flag = False
	while not DateCompare(sJD1, dzsJD): # 次年冬至朔前合朔即当年月份
		i += 1
		j += 1
		sJD1 = ephem.julian_date(shuo[j]) # 起冬至朔
		if DateCompare(JD, sJD1):
			szy += 1 # date所在月
			newmoon = int(sJD1 + 8/24 + 0.5)
		shuo.append(ephem.next_new_moon(shuo[j])) # i+1月朔
		if j == 0: continue # 冬至月一定含中气,从次月开始查找
		sJD2 = ephem.julian_date(shuo[j+1])  # 次月朔
		angle = (-90 + 30 * i) % 360  # 起大雪
		if j == 1:
			nian1 = ephem.Date(sJD1 + 8/24 - 2415020).triple()[0]
			qJD1 = SolarTerms(nian1, angle) # 每月中气
		else:
			qJD1 = qJD2 # 使用上次计算结果,节约计算
		nian2 = ephem.Date(sJD2 + 8/24 - 2415020).triple()[0]

		qJD2 = SolarTerms(nian2, (angle+30)%360) # 次月中气
		if not DateCompare(qJD1, sJD1) and DateCompare(qJD2, sJD2) and flag == False:
				zry = j + 1 # 置闰月
				i -= 1
				flag = True # 仅第一个无中气月置闰
	rq = int(JD + 8/24 + 0.5) - newmoon # 日干支
	if j == 12 and zry != 99: # 连续两个冬至月间合朔12次则不闰
		zry = 99
	if szy % 12 == zry % 12 and zry != 99:
		run = '闰'
	if szy >= zry % 12 and zry != 99:
		szy -= 1
	# 判断节气月
	month = ephem.Date(date).triple()[1]
	angle = (-135 + 30 * month) % 360
	jJD2 = SolarTerms(year, (angle+30)%360) # 次月节气
	if angle == 225: # 再次月节气
		jJD3 = SolarTerms(year+1, (angle+60)%360)
	else:
		jJD3 = SolarTerms(year, (angle + 60) % 360)
	if angle == 255: # 每月节气
		jJD1 = SolarTerms(year-1, angle)
	else:
		jJD1 = SolarTerms(year, angle)
	daxue = False
	if DateCompare(JD, jJD3): # JD ≥ JD3
		jq = ephem.Date(jJD3 + 8/24 - 2415020)
		month2 = (angle + 15 - 210) // 30 % 12

		if angle == 225: daxue = True
	elif DateCompare(JD, jJD2): # JD2 ≤ JD < JD3
		jq = ephem.Date(jJD2 + 8/24 - 2415020)
		month2 = (angle + 15 - 240) // 30 % 12
		if angle == 225 or (jq.triple()[1] == 12 and angle == 255): daxue = True  # 大雪或小寒
	else: # JD < JD2
		jq = ephem.Date(jJD1 + 8/24 - 2415020)
		month2 = (angle + 15 - 270) // 30 % 12
		if angle == 255: daxue = True
	nian2 = jq.triple()[0]
	if nian2 < 0: nian2 += 1
	if daxue == True: nian2 += 1
	jqy = gz[(nian2 * 12 + month2 + 12) % 60] # 月干支
	if (szy - 3) % 12 >= 10 and ephem.Date(date).triple()[1] <= 3: year -= 1 # 年干支
	if year < 0: year += 1
	return date + ' 为农历:' + gz[(year - 4) % 60] + '年 ' + jqy + '月 ' + gz[int(JD + 8/24 + 0.5 + 49) % 60] + '日 ' + run + yuefen[(szy - 3) % 12] + nlrq[rq] + '\n'
 
#date = input('请输入日期:')
date = "2019-3-15"
date1 = "2016-11-29"
date2 = "2033-9-1"
date3 = "2033-12-31"
date4 = "2034-3-1"
print(SolarLunarCalendar(date))

天文计算中经常简化历表以简便计算,简化会降低计算精度。特别是时间进位可能会差到一天。计算定气定朔,日期越久远误差越大。如-2/12/26定朔23:59:38,若误差约1分钟,则可能定朔为12/27日。不过这种情况很少见。

————————————————

版权声明:本文为CSDN博主「方中」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:利用库进行任意日期的公历转换农历的博客-CSDN博客