Python 农历公历相互转换
Python 农历公历相互转换

背景
日常用python处理各种数据分析工作,最近需要对历年春节期间的数据做一些对比工作,本来只是用了一个简单的日期数组来进行,但后来发现一些数据在农历日期进行对比的时候,会有一些有趣的规律,进而产生了公历农历进行互转的需求。
本来以为网上有现成的库或者是文章,结果发现要不是请求网络Api,要么就是数据有错误,语言不是Python的等等。由于基于是10万量级的数据,网络请求转换明显是不可能的,所以自己写了一个本地转换的库,研究过程中又发现了一些比较有趣的在平时开发中用的不多的算法和Python基础,就都添加了上去,并成为我第一个发布的pypi包。这篇文章主要介绍基础算法和使用方法,后续会把那些Python基础知识也补充进去。
项目使用说明
先上项目吧,想直接使用的同学,拿来就能用了 ZhDate GitHub主页,对开发过程有兴趣的请继续往下看。
安装方法
通过 pip 直接安装
pip install zhdate
或从git拉取
git clone https://github.com/CutePandaSh/zhdate.git
cd zhdate
python setup.py install
更新
pip install zhdate --upgrade
使用方法
见如下代码案例:
from zhdate import ZhDate
date1 = ZhDate(2010, 1, 1) # 新建农历 2010年正月初一 的日期对象
print(date1) # 直接返回农历日期字符串
dt_date1 = date1.to_datetime() # 农历转换成阳历日期 datetime 类型
dt_date2 = datetime(2010, 2, 6)
date2 = ZhDate.from_datetime(dt_date2) # 从阳历日期转换成农历日期对象
date3 = ZhDate(2020, 4, 30, leap_month=True) # 新建农历 2020年闰4月30日
print(date3.to_datetime())
# 支持比较
if ZhDate(2019, 1, 1) == ZhDate.from_datetime(datetime(2019, 2, 5)):
pass
# 减法支持
new_zhdate = ZhDate(2019, 1, 1) - 30 #减整数,得到差额天数的新农历对象
new_zhdate2 = ZhDate(2019, 1, 1) - ZhDate(2018, 1, 1) #两个zhdate对象相减得到两个农历日期的差额
new_zhdate3 = ZhDate(2019, 1, 1) - datetime(2019, 1, 1) # 减去阳历日期,得到农历日期和阳历日期之间的天数差额
# 加法支持
new_zhdate4 = ZhDate(2019, 1, 1) + 30 # 加整数返回相隔天数以后的新农历对象
# 中文输出
new_zhdate5 = ZhDate(2019, 1, 1)
print(new_zhdate5.chinese())
# 当天的农历日期
ZhDate.today()
核心算法
重要的事情说三遍
农历不是算出来的,是天文台观测出来的
农历不是算出来的,是天文台观测出来的
农历不是算出来的,是天文台观测出来的
所以也想做农历功能的同学就不要费心去学什么农历算法了,浪费了我三天时间也没看懂到底是怎么计算的。
目前通用的也是比较准确的,可下载的农历阳历对照数据是 香港天文台农历对照表(文字版), 可下载txt格式的农历对照数据。写了一个简单的爬虫,将所有txt文件下载下来。注意获得到的txt是Big5的,并且需要跳过头部的三行,头部三行是每个文件的年份基础信息。可以用以下代码来读取,这里还用到了如何跳过文件头部n行,以及打开非utf8编码格式文件的小技巧。
with open('./{年份}.txt', encoding='big5') as file:
for n_line, line in enumerate(file.readline()):
if n_line < 3:
continue
else:
dosomething()
下载到的数据是从 公历 1901年1月1日,农历 1900年11月11日起,至 2100年12月31日,农历 2100年12月1日之间的200年的每天对照数据。经过编码转换后,重新存一个json或者pickle文件就可以直接拿来用了,速度也不慢。但是这个包含了所有日期数据的文件,json格式的话,有6M多,字典pickle格式也有2M多,显然不利于传播和重复使用。参考了网上一篇Java的农历转换源码,虽然使用的基础数据存在错误,但是算法非常精辟,所以就 拿来主义 了。
香港天文台原始数据处理
从原始数据处理转换成可用于统计和进一步处理的完整代码如下:
from datetime import datetime
CHINESENUMBERS = {
'一': 1,
'二': 2,
'三': 3,
'四': 4,
'五': 5,
'六': 6,
'七': 7,
'八': 8,
'九': 9,
'十': 10,
'正': 1
}
def read_single_file(file_name, coding="big5"):
result = list()
with open(file_name, encoding=coding) as file:
for idx, l in enumerate(file.readlines()):
if idx < 3:
continue
else:
result.append(list(filter(lambda x: x != "" and x != "\n", l.split(" "))))
return result
def day_data_process(day_data, c_year, c_month, c_leap=False):
day_info = dict()
date = datetime.strptime(day_data[0], '%Y年%m月%d日')
day_info['year'] = date.year
day_info['month'] = date.month
day_info['day'] = date.day
chinese_day = day_data[1]
if chinese_day == '正月':
day_info['lunar_year'] = c_year + 1
else:
day_info['lunar_year'] = c_year
if chinese_day[-1] == '月':
if chinese_day[0] == '閏':
day_info['lunar_leap'] = True
if len(chinese_day) == 4:
day_info['lunar_month'] = 10 + CHINESENUMBERS[chinese_day[2]]
else:
day_info['lunar_month'] = CHINESENUMBERS[chinese_day[1]]
else:
day_info['lunar_leap'] = False
if len(chinese_day) == 3:
day_info['lunar_month'] = 10 + CHINESENUMBERS[chinese_day[1]]
else:
day_info