记一次开发农历日期计算器的过程

缘起:闲得发慌突然想写一个没啥用的东西

最近在看日期看看什么时候开学时突发奇想想开发一个虽然没啥用小工具但是还浪费时间的工具——一个能够同时显示公历和农历的日期计算器。这个想法完全是闲出屁来才写的,不考虑任何实用性

环境配置

我的开发环境:

  • 操作系统:Arch Linux 2025.08.01
  • Python版本:3.11.4
  • 开发工具:VS Code + Jupyter扩展 + vim

依赖安装

农历计算目前没有任何公式可以准确计算所有需要专门的库支持,翻阅大量文献,我选择了zhdate

1
pip install zhdate

这个库专门处理中国农历日期转换,可以解决复杂的闰月计算问题。

思路与实现过程

核心功能分解

  1. 公历日期计算:计算从今天到目标日期的天数差
  2. 农历转换:将公历日期转换为农历日期
  3. 额外信息:显示生肖、天干地支、闰年信息

功能1:公历日期计算

思考过程

  • 需要计算两个日期之间的天数差
  • Python本身就有强大的日期处理模块datetime
  • 关键是要确保两个日期对象类型一致

计算天数差

1
2
3
4
5
6
7
from datetime import date

def calculate_days_diff(target_date):
"""计算当前日期到目标日期的天数差"""
today = date.today()
delta = target_date - today # 日期相减得到timedelta对象
return delta.days # 提取天数差

功能2:农历日期转换

思考过程

  • zhdate库可以将公历日期转换为农历日期
  • 需要将date对象转换为datetime对象(库的要求)
  • 农历日期天干地支、闰月等需要特殊格式化

实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from zhdate import ZhDate

def get_lunar_info(target_date):
# 转换为datetime对象(zhdate需要)
target_dt = datetime(target_date.year, target_date.month, target_date.day)

lunar_date = ZhDate.from_datetime(target_dt)
lunar_str = lunar_date.chinese() # 获取中文农历表示

# 天干地支计算
stems = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
branches = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]

lunar_year = lunar_date.lunar_year
stem_idx = (lunar_year - 4) % 10
branch_idx = (lunar_year - 4) % 12
gan_zhi = stems[stem_idx] + branches[branch_idx]

# 生肖计算
zodiacs = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"]
zodiac = zodiacs[(lunar_year - 4) % 12]

return f"{gan_zhi}年({zodiac}){lunar_str.split('年', 1)[1]}"

功能3:用户界面与额外信息

思考过程

  • 需要友好的用户输入提示
  • 显示清晰的格式化结果
  • 添加闰年和生肖信息增加实用性

关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def main():
print("公历农历日期计算器")
print("=" * 40)

try:
# 获取用户输入
year = int(input("请输入目标年份: "))
month = int(input("请输入目标月份: "))
day = int(input("请输入目标日期: "))

# 创建日期对象
target = date(year, month, day)

# 计算并显示结果
days_diff = calculate_days_diff(target)
lunar_str = get_lunar_info(target)

# 显示结果
print(f"\n距离目标日期还有 {days_diff} 天")
print(f"农历日期: {lunar_str}")

# 闰年判断
is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
print(f"{year}年是{'闰' if is_leap else '平'}年")

except ValueError:
print("日期输入不合法!")

踩坑实录

第一坑:日期类型不一致

写完代码后兴奋地运行:

1
2
3
target = datetime(2025, 8, 8)  # datetime对象
current = date.today() # date对象
days = (target - current).days

结果:

1
TypeError: unsupported operand type(s) for -: 'datetime.datetime' and 'datetime.date'

错啦!!!!为什么?

盯着错误信息看了半天才想起来:Python的datetimedate是两种不同的类型!虽然它们看起来很相似,但不能直接运算。

解决方案也很简单,统一使用date类型就好了

1
2
3
4
# 统一使用date类型
target = date(2025, 8, 8) # 改为date对象
current = date.today()
days = (target - current).days

第二坑:消失的闰月属性

农历功能实现方法:

1
2
3
lunar = ZhDate.from_datetime(target_dt)
if lunar.is_leap_month: # 判断闰月
print("闰月")

结果运行时报错:

1
AttributeError: 'ZhDate' object has no attribute 'is_leap_month'

又双叒叕错啦!!!!为什么?

查了zhdate的GitHub才发现,原来是新版本移除了这个属性!API变更真是开发者的噩梦啊。

只能顺着作者的意思修改了

1
2
3
# 改用字符串判断
lunar_str = lunar.chinese()
is_leap = "闰" in lunar_str # 如果有"闰"字就是闰月

第三坑:混乱的生肖年份

测试2025年1月1日:

1
2
农历日期: 甲辰年(龙)腊月初二
2025年属龙

好像没什么问题,没报错…….
求证麻袋,2025不是蛇年吗?
不对,农历新年通常在公历1月下旬到2月中旬之间,所以公历的2025年1月1日还没过农历年,所以还是甲辰年龙年,生肖应该是龙

解决方案

1
2
3
4
5
6
# 错误:使用公历年份计算生肖
zodiac_index = (year - 4) % 12

# 正确:使用农历年份计算生肖
lunar_year = lunar_date.lunar_year
zodiac_index = (lunar_year - 4) % 12

第四坑:负数天数显示

当目标日期是过去的时间时:

1
距离目标日期还有 -365 天

WTF?负数???我还能回到过去吗???

解决可以用绝对值把这个数变为正数

所以

1
2
3
4
5
6
if days_diff < 0:
print(f"目标日期已经过去 {abs(days_diff)} 天")
elif days_diff == 0:
print("目标日期就是今天!")
else:
print(f"距离目标日期还有 {days_diff} 天")

所以:

完整实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from datetime import datetime, date
from zhdate import ZhDate

def calculate_days_diff(target_date):
"""计算当前日期到目标日期的天数差"""
today = date.today()
delta = target_date - today
return delta.days

def get_lunar_info(target_date):
"""获取目标日期的农历信息"""
# 转换为datetime对象(zhdate需要)
target_dt = datetime(target_date.year, target_date.month, target_date.day)

lunar_date = ZhDate.from_datetime(target_dt)
lunar_str = lunar_date.chinese()

# 提取农历年份
lunar_year = lunar_date.lunar_year

# 天干地支计算
stems = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"]
branches = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"]
stem_idx = (lunar_year - 4) % 10
branch_idx = (lunar_year - 4) % 12
gan_zhi = stems[stem_idx] + branches[branch_idx]

# 生肖计算(基于农历年)
zodiacs = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"]
zodiac = zodiacs[(lunar_year - 4) % 12]

# 构建农历日期字符串
return f"{gan_zhi}年({zodiac}){lunar_str.split('年', 1)[1]}"

def main():
print("公历农历日期计算器")
print("=" * 40)

try:
# 获取用户输入
year = int(input("请输入目标年份: "))
month = int(input("请输入目标月份: "))
day = int(input("请输入目标日期: "))

# 创建目标日期
target = date(year, month, day)
today = date.today()

# 计算日期差
days_diff = calculate_days_diff(target)

# 获取农历日期
lunar_str = get_lunar_info(target)

# 显示结果
print("\n计算结果:")
print("-" * 40)
print(f"当前公历日期: {today.strftime('%Y年%m月%d日')}")
print(f"目标公历日期: {target.strftime('%Y年%m月%d日')}")

if days_diff < 0:
print(f"目标日期已经过去 {abs(days_diff)} 天")
elif days_diff == 0:
print("目标日期就是今天!")
else:
print(f"距离目标日期还有 {days_diff} 天")

print(f"目标农历日期: {lunar_str}")
print("-" * 40)

# 额外信息
is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
print(f"{year}年是{'闰' if is_leap else '平'}年")

# 显示生肖(基于公历年)
zodiacs = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"]
zodiac_idx = (year - 4) % 12
print(f"注:公历{year}年大部分时间属{zodiacs[zodiac_idx]}")

except ValueError as e:
print(f"输入错误: {e} - 请输入有效的日期")
except Exception as e:
print(f"发生意外错误: {e}")

if __name__ == "__main__":
main()

运行测试过程

1:春节计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
公历农历日期计算器
========================================
请输入目标年份: 2024
请输入目标月份: 2
请输入目标日期: 10

计算结果:
----------------------------------------
当前公历日期: 2023年11月03日
目标公历日期: 2024年02月10日
距离目标日期还有 99 天
目标农历日期: 甲辰年(龙)正月初一
----------------------------------------
2024年是闰年
注:公历2024年大部分时间属龙

2:跨年测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
公历农历日期计算器
========================================
请输入目标年份: 2025
请输入目标月份: 1
请输入目标日期: 1

计算结果:
----------------------------------------
当前公历日期: 2023年11月03日
目标公历日期: 2025年01月01日
距离目标日期还有 424 天
目标农历日期: 甲辰年(龙)腊月初二
----------------------------------------
2025年是平年
注:公历2025年大部分时间属蛇

3:过去日期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
公历农历日期计算器
========================================
请输入目标年份: 2020
请输入目标月份: 1
请输入目标日期: 1

计算结果:
----------------------------------------
当前公历日期: 2023年11月03日
目标公历日期: 2020年01月01日
目标日期已经过去 1402 天
目标农历日期: 己亥年(猪)腊月初七
----------------------------------------
2020年是闰年
注:公历2020年大部分时间属鼠

总结与碎碎念

首先先总结几点:

  1. 类型一致性:在Python的datedatetime看似毫不相干其实也毫无关系
  2. 第三方库API会变:要注意关注依赖库的更新日志,不然S13作者突然改了或者删库跑路了都不知道
  3. 写这个代码只是为了好玩: just for fun

最终完成的农历日期计算器虽然代码量不大,但实际上也没啥用。它除了能计算日期差,还能显示详细的农历信息,包括天干地支、生肖和闰月情况就没有任何作用啦!

写在最后

我乱写的,你能看到这我也是服了你