背景

日常用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 ZhDatedate1 = 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:continueelse:dosomething()

下载到的数据是从 公历 1901年1月1日,农历 1900年11月11日起,至 2100年12月31日,农历 2100年12月1日之间的200年的每天对照数据。经过编码转换后,重新存一个json或者pickle文件就可以直接拿来用了,速度也不慢。但是这个包含了所有日期数据的文件,json格式的话,有6M多,字典pickle格式也有2M多,显然不利于传播和重复使用。参考了网上一篇Java的农历转换源码,虽然使用的基础数据存在错误,但是算法非常精辟,所以就 拿来主义 了。

香港天文台原始数据处理

从原始数据处理转换成可用于统计和进一步处理的完整代码如下:

from datetime import datetimeCHINESENUMBERS = {'一': 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:continueelse:result.append(list(filter(lambda x: x != "" and x != "n", l.split(" "))))return resultdef 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.yearday_info['month'] = date.monthday_info['day'] = date.daychinese_day = day_data[1]if chinese_day == '正月':day_info['lunar_year'] = c_year + 1else:day_info['lunar_year'] = c_yearif chinese_day[-1] == '月':if chinese_day[0] == '閏':day_info['lunar_leap'] = Trueif 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'] = Falseif len(chinese_day) == 3:day_info['lunar_month'] = 10 + CHINESENUMBERS[chinese_day[1]]else:day_info['lunar_month'] = CHINESENUMBERS[chinese_day[0]]day_info['lunar_day'] = 1else:day_info['lunar_month'] = c_monthday_info['lunar_leap'] = c_leapif chinese_day[0] == '初':day_info['lunar_day'] = CHINESENUMBERS[chinese_day[1]]elif chinese_day[0] == '十':day_info['lunar_day'] = 10 + CHINESENUMBERS[chinese_day[1]]elif chinese_day[0] == '廿':day_info['lunar_day'] = 20 + CHINESENUMBERS[chinese_day[1]]elif chinese_day == '二十':day_info['lunar_day'] = 20elif chinese_day == '三十':day_info['lunar_day'] = 30return day_infodef lunar_data():data_list = list()for i in range(1901, 2101):data_list = data_list + read_single_file(f"./rawdata/{i}.txt")lunar_calendar_data = list()for day in data_list:try:datetime.strptime(day[0], '%Y年%m月%d日')except:continueif len(lunar_calendar_data) != 0:lunar_calendar_data.append(day_data_process(day, lunar_calendar_data[-1]['lunar_year'], lunar_calendar_data[-1]['lunar_month'], lunar_calendar_data[-1]['lunar_leap']))else:lunar_calendar_data.append(day_data_process(day, 1900, 11))return lunar_calendar_data

上述代码可返回一个每天日期信息字典的List,可再使用pandas对这些数据进行编码。编码过程略。

年度数据编码

每一整年的数据可用 20位的二进制数表示

0001 1000 1000 1000 1000

  • 第一部分,最左边的前4位,只有0或1,0表示当年闰月为小月(即29天),1表示当年闰月为大月(即30天),这个需要和最右侧的最后4位结合使用。
  • 第二部分,中间的12位,表示当年农历年每月的大小月,0表示小月,1表示大月,忽略闰月,从左起第一位表示1月。
  • 第三部分,最右侧的最后4位,转换成10进制表示当年的闰月月份,如果闰月不存在那就为 0。

举例说明

2019年的年度编码 43312

转换成二进制为

0000 1010 1001 0011 0000

位数不足左侧补0, 解析如下:

  • 先考虑中间12位表示月份,形成月份天数数组 [30, 29, 30, 29, 30, 29, 29, 30, 29, 29, 30, 30],此为农历1-12月的月份天数。
  • 再看最后4位,等于0,表示当年无闰月
  • 解析完成

2020年的年度编码 31060

转换成二进制为

0000 0111 1001 0101 0100

位数不足左侧补0, 解析如下:

  • 先考虑中间12位表示月份,形成月份天数数组 [29, 30, 30, 30, 30, 29, 29, 30, 29, 30, 29, 30],此为农历1-12月的月份天数。
  • 再看最后4位,转换10进制,等于4,表示当年存在 闰4月
  • 查看最左侧,前4位,等于0,表示当年闰4月为小月,只有29天
  • 在初始月份数组的 4月后插入 29,形成新的月份天数List [29, 30, 30, 30, 29, 30, 29, 29, 30, 29, 30, 29, 30],这里包含13个月,含闰月的天数。
  • 解析完成

坑爹的网上农历说明

有些网站上提到每年的闰月应该和实际月天数相同,比如上述的例子,按照说明那么 2020年的农历4月和农历闰4月的天数是相同的,实际上是不同的,所以按照天文台的数据进行处理吧。

年度编码解析代码

def decode(year_code):"""解析年度农历代码函数Arguments:year_code {int} -- 从年度代码数组中获取的代码整数Returns:[int] -- 当前年度代码解析以后形成的每月天数数组,已将闰月嵌入对应位置,即有闰月的年份返回长度为13,否则为12"""month_days = list()for i in range(5, 17):if (year_code >> (i - 1)) & 1:month_days.insert(0, 30)else:month_days.insert(0, 29)if year_code & 0xf:if year_code >> 16:month_days.insert((year_code & 0xf), 30)else:month_days.insert((year_code & 0xf), 29)return month_days

香港天文台能下载到的只有1901年-2100年的数据,作为一个强迫症患者,看到这个1901总是不爽,在百度上查了一下,正好它支持1900年2050年的数据,所以手动添加了1900的部分,形成了这个项目中的1900 - 2100年的完整农历数据。

为了加快运算除了年度代码,还存储了每年的农历正月初一的公历日期,这样就用了20K就保存了200年的农历数据。

天干地支算法

天干地支是中国特有的一种历法,看起来很复杂,实际上用简单的代码就用打印出来

tian = '甲乙丙丁戊己庚辛壬癸'
di = '子丑寅卯辰巳午未申酉戌亥'
for i in range(0, 60):print(f"{i:} {tian[i % 10]}{di[i % 12]}")----------------
0 甲子
1 乙丑
2 丙寅
3 丁卯
4 戊辰
5 己巳
6 庚午
...(略)
51 乙卯
52 丙辰
53 丁巳
54 戊午
55 己未
56 庚申
57 辛酉
58 壬戌
59 癸亥

对的,就是这么简单,天干是10进制,地支是12进制,所以每一个序数对10取余数,得到天干,每个序数对12取余数得到地支,相互组合就是该序数对应的天干地支数。所以不用查表,用的时候直接打印一份就行了。

年度的天干地支最容易算,需要注意的是必须使用农历年份,不能用公历年份。查下百度得知 1900年为 庚子年,序号 36,所以用以下代码可获得当前农历年的天干地支

def year_tiandi(year):td_num = year - 1900 + 36tian = '甲乙丙丁戊己庚辛壬癸'di = '子丑寅卯辰巳午未申酉戌亥'return f"{tian[td_num % 10]}{di[td_num % 12]}年"

总结

以上就是整个项目中最核心的部分,本质上来说,这个项目并不涉及复杂算法,最核心的是使用二进制来压缩存储年度数据,相关的在Python中如何二进制的基本用法,以及应用案例我会另开文章来写。至于涉及到的其他,我觉得需要整理的基础知识点也会陆续补充上来,作为分享以及自己的学习笔记。

js 万年历农历转阳历 方法_Python 农历公历算法转换相关推荐

  1. js 万年历农历转阳历 方法_JavaScript实现公历转农历功能示例

    本文实例讲述了JavaScript实现公历转农历功能.分享给大家供大家参考,具体如下: 完整代码(该源码使用在线工具http://tools.jb51.net/code/js进行了格式化处理,以便于读 ...

  2. .NET 调用JS:WebBrowser.Document.InvokeScript 方法抛出“指定的转换无效”异常的原因

    .NET 调用JS:WebBrowser.Document.InvokeScript 方法抛出"指定的转换无效"异常的原因 参考文章: (1).NET 调用JS:WebBrowse ...

  3. js 万年历农历转阳历 方法_JS实现带阴历的日历功能详解

    本文实例讲述了JS实现带阴历的日历功能.分享给大家供大家参考,具体如下: 工作中要写一个带阴历的日历,自己调研了怎么做日历. 一.表格行数问题 既然要显示日期表格的话,首先得知道这个表格有多少行多少列 ...

  4. PB中公历与农历(阳历与阴历)的互相转换——主要是农历转公历(阴历转阳历)

    PB中关于公历转农历的算法,网上有很多,思路也大致一样,在这里我就不再进行说明了. 本文主要是想跟所有PB爱好者,分享农历转公历的方法. 转换思路为:根据传入的农历日期,找到第一个小于传入日期的基准日 ...

  5. js 万年历农历转阳历 方法_利用JS制作万年历的方法

    本篇文章主要介绍了利用JS制作万年历的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 我们知道,万年历在人们的生活中是在平常不过的一种东西了,那么怎样用JS来实现在网 ...

  6. js 万年历农历转阳历 方法_非常酷的javascript实现万年历功能

    body{ margin:0px; padding:0px; font-size:12px; line-height:22px; font-family:tahoma,"宋体",S ...

  7. python万年历差农历程序_Python实现公历(阳历)转农历(阴历)的方法示例

    本文实例讲述了Python实现公历(阳历)转农历(阴历)的方法.分享给大家供大家参考,具体如下: 两个要点: 1.公历转农历用了查表法(第126行) 2.节气用了天文法?(第176行) 运行图(背景是 ...

  8. python实现日历功能_Python方法完成农历日历功能代码

    Python方法完成农历日历功能代码 #coding=utf-8 #****************************************************************** ...

  9. js 根据公历日期 算出农历_给孩子过农历生日,还是阳历生日?家长不要盲从,看看这些再决定...

    生日的实质是一个纪念日,是庆祝每个孩子来到世界上的一天,也是母亲受苦的一天.过生日也可使孩子更懂得感恩母亲,所以,无论是对于孩子或父母来说,都有非常重要的意义. 对于生活在七八十年代的父母来说,通常是 ...

最新文章

  1. python答辩结束语_Beta答辩总结
  2. Android 实时文件夹
  3. Tracetcp/Tcptrace的使用
  4. 系统编程(ISP)与应用编程(IAP)的区别!
  5. centos 下载oracle_Linux镜像下载大全
  6. 给div拼接html 拼接字符串
  7. mybatis处理集合和循环数组问题
  8. 各种控制列表--前缀列表
  9. android预览界面显示不全,Android SurfaceView Camera 预览显示不全(画面拉伸)
  10. vue各种组件(不断增加中...)
  11. linux内核相关的两个问题
  12. Linux 内核官网下载
  13. java基本语法大全(全)_Java基本语法大全(全)
  14. 【Matlab应用】:相控阵天线方向图合成及波束扫描算法实现
  15. pytorch BCEWithLogitsLoss pos_weight参数解疑
  16. 数据库批量插入和存在的问题
  17. 在线展示pdf和word并且不能显示下载和打印按钮
  18. windows命令行中 启动应用程序
  19. 计算机考试贷款日到期日,怎么样用金融计算器算利率和期限
  20. ML-Agents学习之RollerBall项目

热门文章

  1. 图像合成与风格转换实战
  2. 编译器设计-代码优化
  3. Caffe框架GPU与MLU计算结果不一致请问如何调试?
  4. Fragment之间传递数据的方式
  5. thinkphp5.1 中间件是什么有什么用
  6. DCN-S4600 telent、http远程登录配置
  7. Java 对象的理解
  8. 微信小程序获取手机系统信息
  9. 自己理解接口回调入门
  10. haystack全文检索框架