尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python从零实现中国农历与节日工具库:压缩表、节气与闰月处理
简介这份资源是面向Python开发者、日历应用与传统文化工具开发者的中国农历与节日实现工具库适合需要处理公历农历转换、传统节日识别、二十四节气计算等场景的中级学习者。压缩包共123个文件约918KB以72个py源码文件为核心辅以30个md文档、6张png示意图以及yml、yaml、csv、toml、cfg等配置与数据文件结构完整便于直接阅读源码与查阅说明。资源围绕农历转换、农历信息查询、节日计算、节气计算、API设计、兼容性测试与社区维护等模块展开csv数据文件可用于节日与节气数据校验md文档则记录了开发与变更说明。目前已有162人学习下载读者可借此掌握农历库的调用方式与实现思路快速集成到日历、提醒、文化教育等应用中同时理解开源库的目录组织与测试维护方法。1. 农历转换这件事为什么自己写一遍比调库更值每年一到春节、中秋、端午前后后台总会冒出同一类需求把公历日期转成农历、算出当天对应的节日、判断今年是不是闰月、给活动页挂一个「距离除夕还有 X 天」的倒计时。很多开发者第一反应是找个现成库 pip 装上就完事结果真到线上才发现——节气算错一天、闰月判断翻车、节日表里混进了洋节、跨年边界直接抛异常。这类问题不是库不行而是农历本身是一套「天文历法 查表」的混合系统你不理解它的数据结构就永远只能当黑匣子用。这篇笔记讲的就是用 Python 从零搭一个中国农历与节日实现工具库核心是农历与公历的双向转换、二十四节气、传统节日映射以及闰月处理。适合两类人——一类是要在业务里做日期展示、活动倒计时、日历组件的后端或前端工程师另一类是刚接触历法计算、想搞明白「为什么农历不能纯公式算」的新手。我会把数据表怎么组织、转换算法怎么落地、参数怎么调、坑在哪一步步拆开代码可以直接抄去改。2. 农历数据到底怎么存一张压缩表撑起两百年农历转换的难点从来不是算法而是数据。公历是纯数学的闰年规则一句话说清农历是「朔望月 回归年」的阴阳合历每个月长度 29 或 30 天闰月插在哪一年、插在哪个月全靠天文观测结果没有简洁公式能推。所以工程上的通行做法是把一段年份的农历信息预先编码成整数表运行时查表 位运算解码。2.1 为什么是「压缩整数表」而不是 JSON常见做法是给每一年分配一个十六进制整数用二进制位段存这一年的关键信息。以 1900 到 2100 这个常用区间为例每年一个整数200 个整数就能覆盖两百年体积小到可以直接内嵌进源码不依赖外部文件。相比每年存一个 JSON 对象闰月月份、每月大小、节气时刻整数表读取快、无 IO、便于版本管理。一个典型的编码方案是这样分配的低位 4 位存闰月月份0 表示无闰月接着 12 或 13 位存每个月的天数1 表示 30 天0 表示 29 天高位存该年正月初一对应的公历日期偏移或节气基准。不同实现位段划分略有差异但思路一致——把「查表」变成「位运算」。# 农历年份压缩表示意仅展示结构与解码逻辑非完整两百年数据 # 每个整数编码一年的农历信息 # 低 4 位 : 闰月月份0 表示该年无闰月 # 中间 13 位: 从正月起每个月的天数130天029天 # 高位 : 该年正月初一距基准日的天数偏移 LUNAR_INFO [ 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, # 仅示例若干年 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, ] def leap_month(year_index): 返回该年闰月月份0 表示无闰月 return LUNAR_INFO[year_index] 0xf def leap_days(year_index): 闰月天数闰月为 30 天返回 30否则 29 if leap_month(year_index) 0: return 0 return 30 if (LUNAR_INFO[year_index] 0x10000) else 29 def month_days(year_index, month): 返回某农历月的天数month 从 1 开始 return 30 if (LUNAR_INFO[year_index] (0x10000 month)) else 29这段代码的关键在三个位运算 0xf取出低 4 位拿闰月月份 0x10000判断闰月本身是 29 还是 30 天0x10000 month逐位右移判断第 month 个月是 30 天还是 29 天。参数上year_index是相对基准年比如 1900的偏移不是公历年份本身这一点新手最容易搞混——传 2024 进去会直接越界。提示压缩表的年份区间一旦确定就不能随意扩展因为高位偏移是相对基准日累加的。要扩到 2100 年以后得重新生成整张表并校准基准日。2.2 基准日与偏移量所有转换的锚点有了每年的月长信息还需要一个绝对锚点才能和公历对上。通常选一个已知的农历正月初一对应的公历日期作为基准比如 1900 年 1 月 31 日是农历 1900 年正月初一。之后所有计算都围绕「距基准日多少天」展开公历转农历先算目标公历距基准日的天数再逐年减去农历年的总天数定位到具体年份再逐月减定位到月和日。from datetime import date BASE_DATE date(1900, 1, 31) # 农历1900年正月初一 def lunar_year_days(year_index): 计算某农历年的总天数 total 0 for m in range(1, 13): total month_days(year_index, m) total leap_days(year_index) # 有闰月则加上闰月天数 return total def solar_to_lunar(solar: date): 公历转农历返回 (农历年, 月, 日, 是否闰月) offset (solar - BASE_DATE).days year_index 0 # 逐年扣减定位农历年 while year_index len(LUNAR_INFO): days lunar_year_days(year_index) if offset days: break offset - days year_index 1 # 逐月扣减定位农历月 leap leap_month(year_index) is_leap False month 1 while month 12: # 闰月插在 leap 月之后需要单独判断 if leap and month leap 1 and not is_leap: days leap_days(year_index) is_leap True else: days month_days(year_index, month) if offset days: break offset - days if is_leap: is_leap False month 1 else: month 1 day offset 1 return 1900 year_index, month, day, is_leap逻辑说明外层 while 逐年扣减offset落到哪一年就停内层 while 逐月扣减遇到闰月时先处理闰月再回到正常月份序列。参数offset是距基准日的整数天数is_leap标记当前定位到的是不是闰月。这里有个容易翻车的点——闰月的插入位置是「闰 X 月」表示 X 月之后的那个重复月所以判断条件是month leap 1而不是month leap。写错这一处闰年所有日期整体偏移一个月。2.3 反向转换农历转公历的累加法反向转换思路对称从基准日开始先加上目标农历年之前所有年的天数再加上目标月之前所有月的天数最后加上日偏移得到距基准日总天数再换算成公历日期。from datetime import timedelta def lunar_to_solar(lunar_year, lunar_month, lunar_day, is_leapFalse): 农历转公历 year_index lunar_year - 1900 total 0 # 累加之前所有农历年的天数 for i in range(year_index): total lunar_year_days(i) # 累加目标年之前所有月的天数 leap leap_month(year_index) for m in range(1, lunar_month): total month_days(year_index, m) if leap and m leap: total leap_days(year_index) # 闰月在 m 月之后补上 # 如果目标就是闰月本身还要加上正常月的天数 if is_leap and leap lunar_month: total month_days(year_index, lunar_month) total lunar_day - 1 return BASE_DATE timedelta(daystotal)参数is_leap决定目标日期落在闰月还是正常月。注意累加逻辑里闰月天数是在遍历到m leap时补进去的因为闰月排在正常 leap 月之后。如果目标本身就是闰月还要额外加上正常 leap 月的天数。这两处顺序错了转换结果会差 29 或 30 天属于典型的「差一天」血泪经验。3. 二十四节气与节日映射别用固定日期硬编码农历转换跑通之后下一个需求几乎必然是节气和节日。很多人图省事直接把「清明 4 月 5 日」「冬至 12 月 22 日」写死结果遇到闰年或特殊年份就翻车。节气和节日是两套不同的机制得分开处理。3.1 节气用天文近似公式而非查表二十四节气本质是太阳在黄道上的视位置每走 15 度一个节点属于天文计算。工程上常用一个近似公式以某个基准年的节气时刻为起点加上回归年长度的累积偏移再对 24 取模。常见做法是用一个「节气分钟偏移表」配合年份修正。# 二十四节气名称从立春开始 SOLAR_TERMS [ 立春, 雨水, 惊蛰, 春分, 清明, 谷雨, 立夏, 小满, 芒种, 夏至, 小暑, 大暑, 立秋, 处暑, 白露, 秋分, 寒露, 霜降, 立冬, 小雪, 大雪, 冬至, 小寒, 大寒, ] # 以 1900 年为基准的节气分钟偏移示意实际需完整 24 项 TERM_MINUTES [0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758] def solar_term_date(year, term_index): 计算某年某节气的公历日期term_index 从 0 开始 # 基准1900年1月6日2时5分 对应小寒附近此处用简化偏移 base date(1900, 1, 6) minutes TERM_MINUTES[term_index] 525948.76 * (year - 1900) return base timedelta(minutesminutes)逻辑说明TERM_MINUTES存的是每个节气相对基准时刻的分钟偏移525948.76是回归年长度分钟。参数term_index对应节气序号year是公历年份。这个近似公式在 1900 到 2100 区间误差通常在一天以内够业务用如果要精确到分钟得引入更复杂的太阳黄经计算。注意base的选取要和偏移表配套换一套表就得换基准否则整体偏移。3.2 传统节日分「农历固定」和「公历固定」两类节日映射的坑在于不能一刀切。春节、端午、中秋、重阳、除夕是农历固定日期元旦、劳动节、国庆是公历固定日期清明既是节气又是节日得走节气计算母亲节、父亲节是「某月第几个星期几」属于规则型。用一个统一的节日表按类型分派计算逻辑才不会漏。# 节日定义类型 参数 FESTIVALS [ {name: 春节, type: lunar, month: 1, day: 1}, {name: 元宵节, type: lunar, month: 1, day: 15}, {name: 端午节, type: lunar, month: 5, day: 5}, {name: 中秋节, type: lunar, month: 8, day: 15}, {name: 重阳节, type: lunar, month: 9, day: 9}, {name: 元旦, type: solar, month: 1, day: 1}, {name: 劳动节, type: solar, month: 5, day: 1}, {name: 国庆节, type: solar, month: 10, day: 1}, {name: 清明节, type: term, term_index: 4}, ] def get_festival(solar: date): 返回某公历日期对应的传统节日名无则返回 None ly, lm, ld, is_leap solar_to_lunar(solar) for f in FESTIVALS: if f[type] lunar: # 闰月不算节日避免闰五月初五被误判为端午 if not is_leap and lm f[month] and ld f[day]: return f[name] elif f[type] solar: if solar.month f[month] and solar.day f[day]: return f[name] elif f[type] term: if solar solar_term_date(solar.year, f[term_index]): return f[name] return None参数说明type决定走哪条分支lunar用农历月日匹配solar用公历月日term用节气序号。这里特意加了not is_leap判断——闰五月初五不是端午节这是很多人忽略的边界。除夕更特殊它是「农历腊月最后一天」可能是二十九也可能是三十得单独算不能写死腊月三十。3.3 除夕与倒计时的正确算法除夕的日期取决于腊月是大月还是小月。正确做法是先定位到农历腊月取该月天数最后一天就是除夕。def chuxi_date(solar_year): 计算某公历年的除夕对应公历日期 # 除夕落在该公历年的农历腊月需先找到对应农历年 ly, _, _, _ solar_to_lunar(date(solar_year, 12, 31)) year_index ly - 1900 # 腊月天数 days month_days(year_index, 12) # 腊月最后一天即除夕 return lunar_to_solar(ly, 12, days)逻辑说明先通过年末日期反推农历年再取该年腊月天数最后一天就是除夕。参数solar_year是公历年份。倒计时则用(chuxi_date(year) - date.today()).days注意跨年时目标年份要取下一个春节所在的公历年否则会出现负数。4. 避坑与排查农历实现里最容易翻车的五处这一章全是踩过的坑按「现象 → 原因 → 解决」写照着排查能省不少时间。4.1 转换结果整体差一天现象公历转农历所有日期都比预期少一天或多一天。原因基准日选错或者offset计算时用了(solar - BASE_DATE).days但基准日本身没算进去。解决确认基准日对应的农历日期用几个已知日期如 2024 年春节是 2 月 10 日反测校准BASE_DATE和offset的加减。4.2 闰月年份日期错乱现象闰年时闰月之后的月份全部偏移。原因闰月插入位置判断写成month leap而非month leap 1或者累加时漏加闰月天数。解决用有闰月的年份如 2023 年闰二月测试打印每月天数序列确认闰月出现在正确位置。4.3 节气日期偏差超过一天现象清明、冬至等节气算出来和日历差一两天。原因近似公式的回归年系数不准或基准时刻选取有误。解决换用更精确的太阳黄经算法或直接采用权威节气表做查表修正业务场景下查表更稳。4.4 节日表混入非传统节日现象情人节、圣诞节被算进「中国节日」。原因节日表定义时没区分文化来源。解决把节日表按「传统节日」「公历纪念日」「规则型节日」分类传统节日只保留农历和节气来源的避免语义混淆。4.5 年份越界直接抛异常现象传入 1899 或 2101 的日期程序崩溃或返回错误结果。原因压缩表区间固定越界后索引失效。解决在入口处做年份校验越界时明确抛ValueError并提示支持区间而不是静默返回错误值。注意农历库的测试用例一定要覆盖闰年、闰月、跨年、节气边界这四类普通日期测不出问题。5. 进阶技巧把工具库做成可验证、可扩展的形态前面几章把核心转换和节日映射跑通了但一个能长期用的工具库还得解决「怎么验证结果对不对」和「怎么扩展到更多年份」这两个问题。这一章讲两个具体技巧。第一个技巧是用已知锚点做回归测试。农历虽然复杂但有很多公开的权威对照日期比如每年春节的公历日期、几个固定节气的日期。把这些锚点整理成测试用例每次改代码跑一遍比人工核对靠谱得多。# 回归测试锚点公历日期 - 期望农历 ANCHORS [ (date(2024, 2, 10), (2024, 1, 1, False)), # 2024春节 (date(2023, 1, 22), (2023, 1, 1, False)), # 2023春节 (date(2023, 3, 22), (2023, 2, 1, True)), # 2023闰二月初一 (date(2025, 1, 29), (2025, 1, 1, False)), # 2025春节 ] def run_regression(): for solar, expected in ANCHORS: result solar_to_lunar(solar) assert result expected, f{solar} 期望 {expected}实际 {result} print(全部锚点通过)逻辑说明ANCHORS存的是「公历日期 → 期望农历元组」run_regression逐个断言。参数上期望元组是(年, 月, 日, 是否闰月)。这套测试能快速暴露基准日、闰月、边界三类问题。我一般会在每次调整压缩表或转换逻辑后先跑它比翻日历快。第二个技巧是给工具库加一层缓存。农历转换里lunar_year_days会被反复调用逐年累加在批量转换时开销明显。用functools.lru_cache把年天数、月天数缓存起来批量处理几千个日期时能快好几倍。from functools import lru_cache lru_cache(maxsizeNone) def lunar_year_days_cached(year_index): total 0 for m in range(1, 13): total month_days(year_index, m) total leap_days(year_index) return total参数maxsizeNone表示不限缓存条数因为年份区间有限两百年也就 200 条不会撑爆内存。注意缓存函数必须是纯函数——只依赖year_index不依赖外部可变状态否则缓存会返回脏数据。扩展年份区间时别急着改代码先确认新数据表的基准日是否和现有逻辑一致。如果新表换了基准所有偏移计算都要跟着调这时候回归测试就是你的后悔药。我自己的习惯是任何涉及日期表的改动先跑锚点测试再跑一遍全年 365 天的往返转换公历转农历再转回公历看是否等于原日期两个都过才提交。往返一致性测试能抓出绝大多数边界 bug比单测几个日期管用。最后说个选型上的判断如果你的业务只需要展示农历日期和几个大节日用现成库完全够没必要自己造但如果涉及节气精确计算、闰月业务规则、跨两百年以上的日期处理自己掌握这套数据结构和算法改起来才不被动。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Python端口扫描与终端监控实战:原理、代码与避坑

Python端口扫描与终端监控实战:原理、代码与避坑

简介:面向网络安全课程设计场景的一份端口扫描实现代码,适合计算机网络或安全方向的学生完成终端监控类项目时参考借鉴。压缩包内共十七个文件,主要以C源码为主,包括头文件、源程序文件、对话框资源文件及工程配置文件等&#xff…

📅 2026/10/16 16:46:00
PB12票据打印错位修复:DataWindow套打与三表参数校准实战

PB12票据打印错位修复:DataWindow套打与三表参数校准实战

简介:面向PowerBuilder程序员的PB12票据打印解决方案,针对12版数据窗口与报表功能,解决企业应用中的票据版式设计、数据填充、多页连续打印等实际需求,也适合需要开发自定义纸张票据的桌面系统。压缩包共18个文件,其中…

📅 2026/10/16 16:46:00
古典密码C实现:棋盘密码与凯撒密码轻量级嵌入式方案

古典密码C实现:棋盘密码与凯撒密码轻量级嵌入式方案

简介:本资源是一份面向密码学初学者与C语言编程入门者的古典加密算法实践代码包,聚焦棋盘密码(Playfair)与凯撒密码两大基础替换/移位加密机制的完整C实现,助力理解加密原理、掌握字符处理逻辑及算法编程思维。压缩包为…

📅 2026/10/16 16:46:00
MORE NEWS

更多资讯

📰

新媒体全链路运营工具选型常见问题解答

搭建新媒体账号矩阵时,很多团队在运营工具选型上存在困惑。从一键分发、账号矩阵管控到私信统一管理,不少运营人员不清楚工具该如何评估、落地。蚁小二作为覆盖全链路的新媒体运营工具,集成了分发、矩阵管控、私信管理能力。下面围绕新媒体工…

📰

MCreator 方块基础类型(Block Base)完全指南:参数禁用机制与默认值约定

低代码代码生成桌面应用游戏开发 【免费下载链接】MCreator MCreator is an open-source software used to make Minecraft Java Edition mods, Minecraft Bedrock Edition Add-Ons, resource packs, and data packs using an intuitive easy-to-learn interface or with an in…

📰

具身智能中的协同机理研究(115):TVA-World与其他架构的十大本质区别

前沿技术探索:TVA智能体(简称TVA) TVA智能体(亦称“AI智能体视觉”)是依托Transformer架构与“因式智能体”理论构建的新型工业视觉系统,也是当前最具代表性的具身视觉技术之一。它有机融合深度强化学习&a…

📰

Python、数据分析、编程都是啥?——写给业务同学的知识百科

为什么业务也要懂这些 你大概率不是程序员,但你每天都在和“数据”打交道:对账、做表、拉数、写周报、找异常。本文想用大白话把三件事讲清楚——编程是什么、Python 是什么、数据分析又是什么,以及它们怎么帮你把“重复劳动”变成“自动完成…

📰

2026年阿里企业邮箱授权电话获取指南,本地服务商对接联系号码

企业采购阿里云企业邮箱时,通过正规授权渠道与本地服务商对接,是保障部署、迁移与售后质量的关键环节。本文梳理授权联络渠道的获取方式、本地服务商的对接流程与常见误区,帮助企业在不依赖单一号码的前提下,掌握合规、高效的联系…

📰

AI 训练图片素材供应商推荐|可商用版权图像数据集服务商清单

在人工智能模型迭代加速的当下,寻找可靠的AI训练图片素材供应商已成为企业研发的关键环节。可商用版权图像数据集服务商清单不仅是资源索引,更是合规安全的保障。面对海量数据需求,选择具备正版授权、多模态供给及专业清洗能力的供应商至关重…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬