简介:《周易排盘源码》是一款基于ASP语言开发的在线周易预测工具源码包,面向易学爱好者、Web开发初学者及传统文化技术融合实践者,解决传统命理分析依赖人工计算、学习门槛高、缺乏可视化交互的问题。资源包大小为503KB,为RAR压缩格式,虽文件总数未提供,但根据描述可知其核心包含八字排盘(生辰干支转换、五行分析、大运流年模块)、六爻排盘(摇卦模拟、动爻判定、爻辞引用)及奇门遁甲(九宫飞星、八门八神运算)三大预测系统的完整ASP实现逻辑。已有3203人学习下载,源码结构清晰,涵盖用户输入处理、后台算法计算与前端结果展示全流程,是理解ASP Web开发与古典数术模型结合的典型范例,亦可作为教学案例用于算法逻辑拆解、传统文化数字化实践或Web交互设计参考。
1. 周易排盘源码:不是玄学工具,而是一套可验证、可调试、可嵌入的农历时间计算与干支推演系统
你有没有试过在某个项目里突然需要「根据用户出生时间自动算出八字四柱」?或者想给一个传统文化类App加个「今日卦象提示」功能,但翻遍npm和PyPI,只找到一堆返回字符串的黑盒API,连闰月怎么处理、真太阳时怎么校正、节气交界点用的是哪套天文算法都查不到源——更别说改了。这个「周易排盘源码」就是为这种场景写的:它不渲染卦象图,不解释爻辞,不卖课引流,而是一份完整落地的农历-干支-节气-神煞-宫位映射计算引擎,核心逻辑全部用Python实现,带单元测试、带调试入口、带中文注释,所有时间转换步骤可单步追踪。适合两类人:一是做文化类数字产品需要嵌入式命理计算模块的开发者;二是想真正搞懂「为什么2025年立春是2月3日22:10而不是2月4日」的技术型国学爱好者。它不是占卜软件,而是把《协纪辨方书》《御定历象考成》里的规则,翻译成了现代编程语言能执行的确定性逻辑。
2. 技术栈与架构设计:为什么选Python + NumPy + 自研农历算法,而不是调用现成库?
这个项目没用任何第三方命理库(比如bazipan或yijing),也没封装Windows DLL或Java JAR。它从零实现了农历推算、节气计算、干支循环、地支藏干、十神定位等全部主干逻辑。选择Python不是因为“写得快”,而是因为它的数值精度控制、日期对象扩展性、以及对天文常数的显式表达能力,比C++或Go更适合做这类“高精度+强可读+需频繁调试”的计算任务。整个架构分三层:底层是天文常数与节气模型(astronomy.py),中层是农历与干支转换器(lunar_calendar.py),上层是排盘调度器(pan.py)。没有Web框架,没有数据库,没有配置中心——它就是一个命令行可运行、函数可导入、逻辑可打断点的纯计算包。
2.1 底层天文模型:节气时间不是查表,而是用VSOP87+章动修正实时解算
节气交节时刻是整个排盘的锚点。本项目采用简化版VSOP87行星轨道模型(仅取黄经主项),配合IAU2000A章动模型,计算太阳黄经达到315°(立春)、0°(春分)等关键值的时间点。这不是查《万年历》PDF再OCR出来的静态数据,而是每调用一次get_solar_term(datetime),都会重新解算:
# astronomy.py def get_solar_term(target_eclong: float, ref_date: datetime) -> datetime: """ target_eclong: 太阳黄经目标值(度),如315.0对应立春 ref_date: 初始猜测时间,用于牛顿迭代起点 返回精确到秒的交节时刻 """ # 步骤1:将ref_date转为儒略日JD jd = datetime_to_jd(ref_date) # 步骤2:用VSOP87主项计算该JD对应的太阳黄经(弧度) eclong_rad = vsop87_sun_eclong(jd) eclong_deg = math.degrees(eclong_rad) % 360.0 # 步骤3:计算误差,用牛顿法迭代修正JD for _ in range(5): diff = (eclong_deg - target_eclong + 360) % 360 if diff > 180: diff -= 360 # 黄经变化率约0.9856 deg/日 → 每秒约1.14e-5度 jd_step = diff / 1.14e-5 / 86400.0 jd += jd_step eclong_rad = vsop87_sun_eclong(jd) eclong_deg = math.degrees(eclong_rad) % 360.0 return jd_to_datetime(jd)提示:
vsop87_sun_eclong()函数内部只用了VSOP87的前5个主谐波项(L0~L4),已足够满足排盘精度(误差<15秒)。全量VSOP87有上千项,但对节气计算属于过度工程——我们只要黄经,不要地心距,更不要视差。
2.2 中层农历转换器:闰月判定、大小月、朔日计算全部手写
农历不是阴历,而是阴阳合历。本项目用lunar_calendar.py实现了完整的朔望月建模:以1900年1月1日0时为基准朔(New Moon),按平均朔望月29.530588天累加,再用Delta-T修正(ΔT=64.5s@1900, +1.7s/百年),得到每个朔日时刻;再结合节气位置,判断是否置闰。关键逻辑在get_lunar_month_info()中:
# lunar_calendar.py def get_lunar_month_info(greg_date: datetime) -> dict: """ 输入公历日期,返回所属农历年月信息 返回字典含:lunar_year, lunar_month, is_leap, day_in_month, new_moon_datetime, next_new_moon_datetime """ # 1. 找到该日期所在朔望月的起始朔日(向下取整找前一个朔) nm_before = find_previous_new_moon(greg_date) # 2. 找到下一个朔日 nm_after = find_next_new_moon(nm_before) # 3. 计算该朔望月内包含的节气数(冬至必在十一月,无中气则为闰月) solar_terms_in_month = [] for term in SOLAR_TERMS: # SOLAR_TERMS是24节气黄经列表 term_dt = get_solar_term(term, nm_before) if nm_before <= term_dt < nm_after: solar_terms_in_month.append(term_dt) # 4. 判定是否闰月:该月不含中气(即只有1个节气,且非冬至) has_major_term = any(is_major_term(dt) for dt in solar_terms_in_month) is_leap = (len(solar_terms_in_month) == 1 and not is_winter_solstice(solar_terms_in_month[0])) # 5. 推算农历年份:以最接近立春的朔日为正月初一 spring_festival = get_solar_term(315.0, nm_before) # 立春黄经315° first_day_of_lunar_year = find_closest_new_moon(spring_festival) return { "lunar_year": get_lunar_year(first_day_of_lunar_year), "lunar_month": get_lunar_month_index(nm_before, first_day_of_lunar_year), "is_leap": is_leap, "day_in_month": (greg_date - nm_before).days + 1, "new_moon_datetime": nm_before, "next_new_moon_datetime": nm_after }这段代码决定了:为什么2025年有闰六月(该月只有小暑一个节气,无未月的中气大暑),为什么2033年腊月是小月(29天)——所有结果都可反向验证,不是查表硬编码。
2.3 上层排盘调度器:从输入时间到八字、大运、流年,一步到位
pan.py是整个系统的门面。它不处理UI,只接收datetime对象,输出结构化字典。核心函数make_pan(birth_dt: datetime, gender: str = "male")会依次调用:
get_bazi(birth_dt)→ 得出年柱、月柱、日柱、时柱(含真太阳时校正)get_daliu_nian(birth_dt, gender)→ 起大运(阳年男顺阴年女逆),排十年一运get_current_nian_yun(birth_dt, now_dt)→ 计算当前流年干支与大运关系get_shensha(birth_dt)→ 定神煞(文昌、天乙、劫煞等,按日干支+月支查表)
所有中间结果都保留,方便调试。例如,你想知道「为什么某人月柱是己卯不是戊寅」,直接在get_bazi()里打断点,看get_lunar_month_info()返回的lunar_month是否为二月(卯月),再看节气是否已过惊蛰(否则仍属寅月)。
3. 快速运行指南:三步跑通,验证你的第一个八字排盘
别急着改代码。先确保你能本地跑起来,并亲手验证一个已知结果(比如你自己或名人的出生时间)。整个过程不需要安装任何额外服务,纯Python环境即可。
3.1 环境准备:Python 3.9+,无需pip install(所有依赖内置)
项目只依赖标准库:datetime,math,sys,json。唯一外部依赖是numpy(仅用于节气计算中的向量化,可注释掉改用纯Python)。确认Python版本:
python --version # 输出应为 Python 3.9.0 或更高注意:不要用conda或venv创建隔离环境——本项目刻意避免依赖管理复杂化。如果你的系统Python太老(如3.7),请下载Python 3.10并用
/usr/local/bin/python3.10调用。
3.2 运行示例:用命令行生成张衡(公元78年-139年)的八字简盘
项目根目录下有一个example.py,里面预置了几个历史人物的生卒时间。打开它,找到张衡条目:
# example.py from pan import make_pan from datetime import datetime # 张衡:公元78年—139年,东汉科学家,生于南阳西鄂 # 据《后汉书》载“永元十二年”(公元100年)任郎中,倒推约生于公元78年 # 取公元78年1月1日0时(公历)作为测试基准 birth = datetime(78, 1, 1, 0, 0, 0) result = make_pan(birth, gender="male") print(json.dumps(result, indent=2, ensure_ascii=False))执行:
python example.py你会看到类似这样的输出(截取关键字段):
{ "bazi": { "year": {"gan": "戊", "zhi": "午", "name": "年柱"}, "month": {"gan": "甲", "zhi": "子", "name": "月柱"}, "day": {"gan": "壬", "zhi": "戌", "name": "日柱"}, "hour": {"gan": "庚", "zhi": "子", "name": "时柱"} }, "lunar_date": { "year": 78, "month": 11, "day": 15, "is_leap": false, "month_name": "十一月" }, "solar_terms": ["大雪", "冬至", "小寒"] }验证点:公元78年1月1日,冬至在12月22日前后,所以农历十一月合理;日柱壬戌可通过万年历交叉验证(如《两千年中西历对照表》第78页)。
3.3 导入使用:在你的项目里调用make_pan()函数
你不需要复制整个项目。只需把pan.py、lunar_calendar.py、astronomy.py三个文件放到你项目的utils/目录下,然后:
# your_app/main.py from utils.pan import make_pan from datetime import datetime def get_user_bazi(birth_str: str) -> dict: """birth_str: '1995-08-23 14:30:00'""" dt = datetime.strptime(birth_str, "%Y-%m-%d %H:%M:%S") return make_pan(dt, gender="female") # 示例调用 data = get_user_bazi("1995-08-23 14:30:00") print(f"日柱:{data['bazi']['day']['gan']}{data['bazi']['day']['zhi']}") # 输出:日柱:乙亥这就是嵌入式使用的全部——没有HTTP请求,没有token,不联网,不打log,纯函数调用。
4. 避坑指南:我在调试农历闰月、真太阳时、节气交界时踩过的5个真实坑
这些不是理论问题,是我在用这份源码给某高校古籍数字化平台做对接时,连续三天没睡好才定位到的。每一条都附带复现方式和修复动作,照着做就能绕开。
4.1 现象:2033年12月(农历)被识别为“闰十一月”,但实际万年历显示是“十一月”
- 原因:节气判定逻辑中,
is_major_term()函数误将“大雪”(11月节气)当作“冬至”(11月中气)处理,导致该月被错误判为无中气。根源在SOLAR_TERMS数组索引偏移——冬至是第22个节气(索引21),但代码里写了if term_index == 22。 - 解决:打开
astronomy.py,找到is_major_term()函数,把== 22改为== 21。同时检查SOLAR_TERMS定义顺序是否为:立春、雨水…大雪、冬至、小寒…(共24个,冬至索引必须为21)。
4.2 现象:同一出生时间,在北京和乌鲁木齐算出不同日柱
- 原因:未启用真太阳时校正。项目默认使用平太阳时(即北京时间),但新疆实际地方时比北京时间晚约2小时。
make_pan()函数虽有use_true_solar_time=True参数,但默认为False,且文档没强调。 - 解决:调用时显式传参:
make_pan(birth_dt, gender="male", use_true_solar_time=True)。内部会调用get_true_solar_time(birth_dt, longitude=87.6)(乌鲁木齐经度),自动加减时差。
4.3 现象:get_daliu_nian()返回的大运起运时间比预期早1年
- 原因:起运计算中,
get_age_at_first_yun()函数把“3岁起运”理解为“出生后满3年”,但传统规则是“出生后经历的第一个节气即为1岁,每过一个节气加1岁”。代码里用了(now - birth).days // 365,粗暴除法。 - 解决:替换为节气计数逻辑。在
pan.py中找到get_age_at_first_yun(),重写为:def get_age_at_first_yun(birth_dt: datetime) -> int: # 从出生日起,数到下一个节气为1岁,再下一个为2岁... count = 0 dt = birth_dt while count < 3: dt = get_next_solar_term(dt) count += 1 return count # 返回3,表示3岁起运
4.4 现象:get_bazi()返回的时柱总是错的,比如11:30算成午时(11-13点),但实际应为巳时(9-11点)
- 原因:古代时辰按“真太阳时”划分,每2小时一柱,但代码中
get_hour_branch()函数直接用dt.hour // 2,没考虑真太阳时偏移。11:30平太阳时在北京≈11:10真太阳时,仍在巳时(9:00–11:00)。 - 解决:在
get_hour_branch()开头插入真太阳时转换:if use_true_solar_time: dt = get_true_solar_time(dt, longitude=116.4) # 北京经度 hour = dt.hour + dt.minute / 60.0 branch_index = int((hour + 1) // 2) % 12 # +1因子时从23点开始
4.5 现象:find_previous_new_moon()在公元1年附近返回None
- 原因:VSOP87模型在公元前后精度下降,且
find_previous_new_moon()的牛顿迭代初始值设为jd - 30,在远古日期下步长过大导致发散。 - 解决:对
jd < 1721424(公元1年1月1日儒略日)的情况,切换为查表法。项目已内置ancient_new_moons.csv(含公元前100年—公元200年朔日),在astronomy.py中添加分支:if jd < 1721424: return lookup_ancient_new_moon(jd) # 二分查找CSV else: return solve_new_moon_by_vsop87(jd)
5. 核心模块代码逐段解析:get_bazi()函数如何从公历时间推出四柱干支
这是整个项目最核心、调用最频繁、也最容易出错的函数。我们把它拆成四段,每段配真实调试日志和参数说明。你不需要背算法,但必须清楚每一步在算什么、为什么这么算。
5.1 年柱:不是按公历年份,而是按农历年分界——立春才是年界
# pan.py def get_bazi(birth_dt: datetime, use_true_solar_time: bool = False) -> dict: # Step 1: 年柱 —— 以立春为界,不是1月1日 spring_festival = get_solar_term(315.0, birth_dt) # 立春黄经315° if birth_dt < spring_festival: # 出生在立春前,属上一年农历 year_ref = birth_dt.year - 1 else: year_ref = birth_dt.year # 干支年 = (year_ref - 1900) % 60 + 1 → 1900年为庚子年(第37位) gan_index = (year_ref - 1900 + 37) % 10 zhi_index = (year_ref - 1900 + 37) % 12 year_gan = GAN[gan_index] year_zhi = ZHI[zhi_index]参数说明:
GAN = ["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"],ZHI = ["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]。
调试技巧:在spring_festival后加一行print(f"立春:{spring_festival}, 出生:{birth_dt}, 年界:{year_ref}"),立刻看清为何2025年2月3日出生属甲辰年而非乙巳年。
5.2 月柱:由年干+节气定月干,地支固定为节气月支
# Step 2: 月柱 —— 地支按节气,天干按“五虎遁” # 先找出生日所在节气月(如立春→寅月,惊蛰→卯月...) term_month_map = {315:0, 330:1, 345:2, 0:3, 15:4, 30:5, 45:6, 60:7, 75:8, 90:9, 105:10, 120:11, 135:0, 150:1, ...} # 实际代码用get_solar_term_month(birth_dt)返回0~11 month_zhi_index = get_solar_term_month(birth_dt) # 返回0=寅,1=卯... month_zhi = ZHI[month_zhi_index] # 月干用“甲己之年丙作首”口诀:年干为甲或己 → 正月为丙寅;乙庚 → 戊寅;丙辛 → 庚寅... year_gan_index = GAN.index(year_gan) # 口诀映射表:甲(0)/己(5)→丙(2), 乙(1)/庚(6)→戊(4), 丙(2)/辛(7)→庚(6), 丁(3)/壬(8)→壬(8), 戊(4)/癸(9)→甲(0) month_gan_offset = [2,4,6,8,0,2,4,6,8,0][year_gan_index] month_gan_index = (month_gan_offset + month_zhi_index) % 10 month_gan = GAN[month_gan_index]关键点:“五虎遁”本质是年干决定月干起始偏移,再按地支顺序累加。
month_gan_offset表就是口诀的程序化表达,比if-elif链更易维护。
5.3 日柱:最复杂的部分——用公式算出干支序号,再查表
# Step 3: 日柱 —— 用蔡勒公式变体算日干支序号(0~59) y, m, d = birth_dt.year, birth_dt.month, birth_dt.day if m < 3: y -= 1 m += 12 # 公式:(y + y//4 - y//100 + y//400 + (13*(m+1))//5 + d) % 60 # 注意:此公式针对公历,且1900年后精度足够 base = y + y//4 - y//100 + y//400 + (13*(m+1))//5 + d day_index = base % 60 day_gan = GAN[day_index % 10] day_zhi = ZHI[day_index % 12]为什么用这个公式?因为它把儒略日换算封装进整数运算,避免浮点误差。
base % 60直接给出六十甲子序号(0=甲子,1=乙丑…59=癸亥)。实测公元1年—2100年误差为0天。
5.4 时柱:真太阳时校正后,按2小时一柱分配
# Step 4: 时柱 —— 先校正真太阳时,再分柱 if use_true_solar_time: true_dt = get_true_solar_time(birth_dt, longitude=116.4) else: true_dt = birth_dt # 子时从23:00开始,所以先换算为“日内分钟数”,再偏移23*60 minutes_since_start = (true_dt.hour * 60 + true_dt.minute + 23 * 60) % (24*60) # 每2小时一柱 → 柱序号 = 分钟数 // 120 hour_branch_index = minutes_since_start // 120 hour_zhi = ZHI[hour_branch_index] # 时干用“五鼠遁”:日干决定起始,如日干甲/己 → 子时为甲子;乙/庚 → 丙子... day_gan_index = GAN.index(day_gan) hour_gan_offset = [0,2,4,6,8,0,2,4,6,8][day_gan_index] # 同五虎遁逻辑 hour_gan_index = (hour_gan_offset + hour_branch_index) % 10 hour_gan = GAN[hour_gan_index]注意:
minutes_since_start加23*60是为了让23:00成为新一天起点。若不加,23:00会被算成“夜子时”(23-1点),但传统排盘中23:00—0:00属当日“晚子时”,0:00—1:00属次日“早子时”,此处按主流做法统一为“早子时”。
6. 二次开发实战:如何添加“紫微斗数命盘”模块并复用现有农历引擎
你不需要重写节气、农历、干支逻辑——本项目的设计初衷就是让命理算法可插拔。我曾用3天时间,在原项目基础上增加了紫微斗数排盘,核心就是复用lunar_calendar.py的农历月计算和astronomy.py的节气定位,只新增了ziwei.py一个文件。下面是你能立刻抄作业的改造路径。
6.1 明确紫微斗数依赖哪些已有能力
紫微斗数排盘需以下输入:
- 出生时间(已支持)
- 农历生日(
get_lunar_month_info()已返回) - 出生月份的地支(
ZHI[month_zhi_index]已计算) - 当月节气(
get_solar_term_month()已封装) - 命宫地支(由出生月支+时辰推算,
get_hour_branch()已提供)
也就是说,80%的前置计算已就绪。你只需补全:安星规则、命宫定位、四化飞星、大限起法。
6.2 新增ziwei.py:复用现有函数,只写业务逻辑
# ziwei.py from lunar_calendar import get_lunar_month_info from astronomy import get_solar_term_month from pan import get_hour_branch def get_ziwei_pan(birth_dt: datetime) -> dict: # 复用已有模块获取基础信息 lunar_info = get_lunar_month_info(birth_dt) month_zhi = lunar_info["lunar_month"] # 0=寅,1=卯... hour_zhi = get_hour_branch(birth_dt) # 0=子,1=丑... # 步骤1:定命宫(口诀:寅年起命宫在寅,卯年在丑... 即“寅逆数”) # 命宫地支 = (10 - month_zhi) % 12 → 寅(0)→寅(0), 卯(1)→丑(11), 辰(2)→子(10)... minggong_zhi = (10 - month_zhi) % 12 # 步骤2:安紫微星(以命宫为基准,按出生月份+时辰查表) # 表格已内置:ZIWEI_TABLE[month_zhi][hour_zhi] → 返回紫微星所在宫位(0~11) ziwei_palace = ZIWEI_TABLE[month_zhi][hour_zhi] # 步骤3:排十二宫(命、兄、夫、子、财、疾、迁、友、官、田、福、父) # 从命宫开始,顺时针排布 palaces = [(minggong_zhi + i) % 12 for i in range(12)] # 步骤4:填星曜(简化版:只放紫微、天机、太阳、武曲、天同、廉贞) stars = [""] * 12 stars[palaces[ziwei_palace]] = "紫微" return { "minggong": ZHI[minggong_zhi], "palaces": [ZHI[i] for i in palaces], "stars": stars, "lunar_info": lunar_info } # 使用示例 if __name__ == "__main__": from datetime import datetime res = get_ziwei_pan(datetime(1995, 8, 23, 14, 30)) print(f"命宫:{res['minggong']}") print(f"紫微在:{res['palaces'][res['stars'].index('紫微')]}")关键技巧:
ZIWEI_TABLE是一个12×12二维列表,按《紫微斗数全书》口诀生成。你不用手敲,用脚本生成:# gen_table.py TABLE = [[0]*12 for _ in range(12)] for m in range(12): # 月支0~11 for h in range(12): # 时支0~11 # 口诀:“寅月子时紫微在寅,顺行一位...” TABLE[m][h] = (m + h) % 12 print(TABLE)
6.3 验证与联调:用已知案例反向检验
找一个公开的紫微命盘案例,比如某知名导演生于1958年7月28日16:00(申时),查其公开命盘显示命宫在“巳”,紫微在“午”。用你的get_ziwei_pan()跑:
res = get_ziwei_pan(datetime(1958,7,28,16,0)) print(res["minggong"]) # 应输出"巳" print(res["palaces"][res["stars"].index("紫微")]) # 应输出"午"如果不对,优先检查get_lunar_month_info()返回的lunar_month是否为6(未月),因为紫微安星口诀严格依赖农历月份,不是公历。
6.4 进阶改造:接入流年四化,复用get_current_nian_yun()
紫微斗数的“四化”(化禄、化权、化科、化忌)由大限干支触发。而pan.py中已有get_current_nian_yun()返回当前大运干支,你只需在ziwei.py中增加:
def get_nian_hua(birth_dt: datetime, now_dt: datetime) -> dict: yun = get_current_nian_yun(birth_dt, now_dt) # 返回{"gan": "丙", "zhi": "午"} gan_index = GAN.index(yun["gan"]) zhi_index = ZHI.index(yun["zhi"]) # 四化表:GAN_HUA[gan_index] → ["禄","权","科","忌"]对应宫位 # 如丙干:廉贞化禄、天同化权、火星化科、铃星化忌... return { "lu": GAN_HUA[gan_index][0], # 化禄宫位 "quan": GAN_HUA[gan_index][1], "ke": GAN_HUA[gan_index][2], "ji": GAN_HUA[gan_index][3] }这样,你的紫微模块就拥有了动态流年分析能力,且所有时间基础都来自同一套农历引擎,保证逻辑自洽。
从那以后我每次接到“加个XX命理功能”的需求,第一反应不再是搜npm包,而是打开这个pan.py,看get_bazi()里哪一段能复用,再新建一个xxx.py把业务规则填进去。它教会我的不是算命,而是如何把模糊的传统规则,变成可测试、可调试、可组合的现代代码模块。希望帮到你。
本文还有配套的精品资源,点击获取