简介:面向八字命理学习与爱好者,一份可直接安装运行的八字排盘程序,用于自动推算年、月、日、时四柱并输出天干地支、五行分布、十神关系、大运流年与格局类型,适合零基础快速上手,也可配合专业书籍深入学习。rar压缩包共6个文件,整体仅2.34MB,主要包含exe与msi两种Windows安装文件、htm使用说明以及相关网址快捷方式,其中exe与msi用于程序安装,htm帮助用户了解功能与操作。解压后可按需选择安装运行,软件支持自定义五行旺衰判断标准等个性化设置,便于对照不同命理观点反复验证;同时可借助说明文件理解排盘软件的界面与逻辑,进而把握四柱推算、十神解读等核心模块。目前已有1589人浏览学习。需注意命理预测缺乏科学依据,结果应理性参考、以兴趣娱乐为主。
1. 八字排盘程序:把千年命理算法变成可复现的代码
八字排盘程序,听起来像是玄学界的黑匣子,实际上拆开看就是一套历法计算引擎:把出生时间换算成天干地支纪年体系下的年柱、月柱、日柱、时柱,再挂上十神、大运、流年这些附属信息。真正让大多数开发者翻车的不是“命理”部分,而是节气、时辰边界、农历闰月这些历法细节的精确处理。我见过不少排盘工具,算法逻辑写得很热闹,但一个立春分界算错,整个年柱就偏差一整年。这篇文章我会把四柱计算的规则、JavaScript 实现、真太阳时校正和几类高频踩坑一次讲清,适合正在写排盘工具、想把传统历算模块化的开发者参考。
2. 四柱计算的底层规则:八字排盘程序的三大物理边界
2.1 六十甲子与四柱的组装逻辑
八字排盘程序兜底的数据结构只有一套:天干十个、地支十二个,按最小公倍数循环出六十个组合,这就是六十甲子。年柱、月柱、日柱、时柱各有自己的干支,四柱拼在一起就是八个字。这个“循环”本质上是一个模运算问题:任意一个时间点,总能被映射到一组唯一的干支坐标上。
天干按甲乙丙丁戊己庚辛壬癸的顺序,地支按子丑寅卯辰巳午未申酉戌亥的顺序。六十甲子从甲子开始,乙丑、丙寅……每过六十个单位回到甲子。编程时我通常用两个数组加索引来完成。这里有个容易被低估的边界:天干周期是10,地支周期是12,两个周期的最小公倍数是60,所以同一个天干要经过六轮地支才能回到原位。这个特性决定了日柱、年柱计算时不能只取公历年份对某个数取模,必须考虑六十甲子整体的游标位置。
四柱的生成顺序也值得注意:年柱和月柱共用一套节气逻辑,日柱和时柱共用一套时辰逻辑。年柱以立春为分界,月柱以十二节为分界,日柱从当日零点开始走六十甲子循环,时柱则按日干通过五鼠遁推算。这些规则排进程序时,顺序应当是先算年、再算月、然后算日、最后算时,因为时柱的天干依赖日柱的天干,月柱的天干依赖年柱的天干,链条不能反。
2.2 年柱、月柱为什么必须以节气为界
很多人第一次做八字排盘程序时,直接取公历年份作为年柱,或者取农历正月初一作为换年界线,这都会出错。传统命理体系里,年柱的分界线是立春,不是元旦,也不是春节。立春所在时刻之前出生,年柱用上一个干支年;立春时刻之后,才进入新年。这个规则出自节令纪年法,和日常用的农历纪年并不完全一致。
月柱同理,以“节”为边界,而不是以农历初一为边界。一年有十二个月建,分别是寅月、卯月、辰月、巳月、午月、未月、申月、酉月、戌月、亥月、子月、丑月。寅月的起点就是立春,卯月从惊蛰开始,辰月从清明开始,以此类推。这里注意,不是“中气”,而是“节”。雨水、春分这类中气只用来辅助确定闰月,不参与月柱换界。十二节才是换月柱的真正开关。
落到程序里,年柱和月柱实际上取决于一个精确到分钟的时间点:立春和十二节各自的精确发生时刻。有些成型的简化方案用“公历2月4日就是立春”这种固定日期来判断,只适合演示,不能用于生产环境,因为节气精确时刻每年都不同,甚至同一天不同经度的时间也不同。后面踩坑章节我会专门展开。
2.3 日柱与十二时辰的循环模型
日柱的算法相对单纯:从某个已知干支的基准日,计算目标日距离基准日过了多少天,然后对六十取模。关键问题是基准日的干支必须是可验证的,不能拍脑袋。我习惯用 1900-01-01 作为校准点,因为这一天对应甲戌日,在任何权威万年历里都能核对。这个基准一旦确定,日柱就不依赖任何外部库,纯数值计算。
时柱的难点在于地支划分和天干推算。十二时辰按两小时一段划分,子时对应 23:00 到次日 01:00,丑时对应 01:00 到 03:00,依此类推。这里马上会冒出一个边界问题:23:00 出生的人,子时从当天晚上就开始,而子时的日柱归属存在流派分歧。我采用的方案是晚子时算次日,也就是当天 23:00 之后出生的日柱按次日计算,这一点后面真太阳时章节会细讲。
时柱天干不独立,要依据日柱天干查五鼠遁口诀:甲己还加甲、乙庚丙作初、丙辛从戊起、丁壬庚子居、戊癸何方发、壬子是真途。这意味着时柱的推算顺序依赖日柱结果,所以在代码里日柱函数和时柱函数必须分开,但组合时有时序约束。
3. 用 JavaScript 从零写一个四柱计算核心
3.1 六十甲子与日柱偏移计算函数
直接贴基础函数。这段代码将作为后续排盘的核心,先定义天干地支数组,再写日柱偏移计算:
const GAN = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; const ZHI = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; // 以 1900-01-01(甲戌日)为基准日,计算任意日期的日柱 function getDayGZ(date) { const base = new Date(1900, 0, 1); const msPerDay = 24 * 60 * 60 * 1000; const days = Math.round((date.getTime() - base.getTime()) / msPerDay); const pos = ((days + 10) % 60 + 60) % 60; return { gan: GAN[pos % 10], zhi: ZHI[pos % 12], pos: pos }; }逻辑说明:基准日 1900-01-01 对应的六十甲子位置是 10(因为甲戌排在第 10 位)。目标日期与基准日相差days天,所以目标日的六十甲子位置就是(days + 10) % 60,再加 60 再取模是为了防止出现负数索引。拿到位置后,天干用位置对 10 取模,地支用位置对 12 取模,因为六十甲子本身就是 10 和 12 的最小公倍数循环。
参数说明:date必须是 JavaScript Date 对象,且内部时间戳会被当作本地时间计算。Math.round是为了消除夏令时或毫秒误差带来的 0 点偏移风险,但如果你在跨时区场景跑,建议先统一为 UTC 时间再算天数差。这个函数只要你确认了基准日干支,后续整个日柱链都是确定的。
3.2 年干月干的五虎遁与时干五鼠遁
年柱算出之后,月柱天干靠五虎遁口诀推算,时柱天干靠五鼠遁口诀推算。这两个口诀在程序里可以收敛成两个极简表达式。先看月干:
// 年干索引,0=甲,1=乙,...,9=癸;monthZhiIdx 对应地支索引,0=子,1=丑,2=寅... function getMonthGan(yearGan, monthZhiIdx) { const yinIdx = 2; // 寅的地支索引 const offset = (monthZhiIdx - yinIdx + 12) % 12; const base = ((yearGan % 5) * 2 + 2) % 10; return GAN[(base + offset) % 10]; }逻辑说明:五虎遁口诀“甲己之年丙作首”,意思是甲年、己年的寅月天干为丙。把年干取模 5 后乘以 2,再加 2 再取模,就能把甲己映射到丙、乙庚映射到戊、丙辛映射到庚、丁壬映射到壬、戊癸映射到甲。之后每推进一个月,天干顺序后移一位。这样不需要在代码里写五组长字符串,直接用数学关系表达。
再写时干:
// dayGan 为日柱天干索引;hour 为 24 小时制数值,支持小数,如 23.5 表示 23:30 function getHourGZ(dayGan, hour) { const ziIdx = Math.floor(((hour + 1) % 24) / 2) % 12; const base = ((dayGan % 5) * 2) % 10; return { gan: GAN[(base + ziIdx) % 10], zhi: ZHI[ziIdx] }; }逻辑说明:五鼠遁口诀“甲己还加甲”,甲日、己日的子时天干为甲。这里((dayGan % 5) * 2) % 10把甲己映射到 0(甲)、乙庚映射到 2(丙)、丙辛映射到 4(戊)、丁壬映射到 6(庚)、戊癸映射到 8(壬)。ziIdx的计算里先对 24 取模是为了把 23 点之后的时间归入新一天的循环,(hour + 1) / 2向下取整能正确把 23:00 归为子时、01:00 归为丑时。
参数说明:hour建议传小数而不是分钟字符串,例如 23.5 代表 23 点 30 分,避免在函数内部做字符串拆分。dayGan必须是日柱天干部的索引,这个值来自上一节getDayGZ返回的gan索引。
3.3 拼合完整盘面并校验
有了前面的基础函数,最后拼装完整四柱。月支不能只靠公历月份近似,我这里的示例先用一个简化节气表来演示主流程,生产环境请换成精确节气时刻:
// 简化版:按公历日期近似推断月支,仅用于演示主流程 // 真实环境应传入精确节气时刻或使用节气表 function getMonthZhi(date) { const m = date.getMonth() + 1; // 1-12 const d = date.getDate(); const jieStart = { 2: 4, 3: 5, 4: 5, 5: 5, 6: 6, 7: 7, 8: 7, 9: 8, 10: 8, 11: 7, 12: 7, 1: 6 }; // 从寅月开始,地支顺序:寅卯辰巳午未申酉戌亥子丑 const jieMonth = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1]; let idx = 0; for (let i = 0; i < 12; i++) { const jm = jieMonth[i]; if (m === jm && d >= jieStart[m]) { idx = i; break; } if ((m === jm) || (i > 0 && ((m > jieMonth[i - 1] && m < jm) || (jm === 1 && m > jieMonth[i - 1])))) { // 落在该节到下一节之间 } } // 简化处理:直接用月份映射到地支,演示逻辑 const zhiIdx = (m + 1) % 12; // 1月对应丑,2月对应寅,3月对应卯... return ZHI[(zhiIdx + 2) % 12]; } function buildFourPillars(date) { const dayGZ = getDayGZ(date); const hour = date.getHours() + date.getMinutes() / 60; const hourGZ = getHourGZ(GAN.indexOf(dayGZ.gan), hour); // 年柱与月柱需依赖更精确的节气,这里仅返回日柱与时柱拼合结果 return { day: dayGZ, hour: hourGZ }; }逻辑说明:这个简化版本的月支映射只能作为学习参考,真实项目里不要直接拿公历月份代替节令。完整的排盘工具一定需要一张节气时间表,或者一个计算节气精确时刻的函数。这里保留这段代码是为了让你看清四柱拼接的完整链条:日柱先算,时柱依赖日柱天干,月柱依赖年柱天干,整体组装顺序不能乱。
参数说明:date传 Date 对象,内部会读取本地时区的小时分钟。如果你已经做了真太阳时校正,应该把校正后的时间再传入这个函数,而不是在校正前就排盘。
4. 真太阳时、经度校正与早晚子时处理
4.1 真太阳时换算:不能直接用北京时间的三个原因
八字排盘程序的输入时间通常是出生证或户口登记上的北京时间,但这个时间本身有两个问题。第一,北京时间是东经 120 度的标准时,不是每个出生地的本地太阳时。出生地经度和 120 度每差 1 度,时间差 4 分钟。乌鲁木齐的经度大约在东经 87 度,和北京时间的差异超过两小时,这会直接导致时辰判断改变。
第二,地球公转轨道是椭圆,真太阳日的长度不恒定,每天的实际太阳时和平太阳时之间有一个全年周期性偏差,最大能到 16 分钟左右。这个偏差就是均时差。16 分钟在某些流派里足以让时柱边界发生变化,所以排盘程序如果声称精确到时辰,就必须处理这一项。
第三,历史时间问题。一些地方曾经使用过地方平时、夏令时或不同标准时区。做现代排盘工具时,至少得做经度校正和均时差校正两步;如果面向历史人物,还要考虑当时的时间制度。这是很多初版排盘程序翻车最厉害的地方,不校时,后续所有柱子都可能错。
4.2 子时换日实现:先校时再排日柱
子时从 23:00 开始,但 23:00 之后的日柱应该算当天还是次日,传统上存在“早晚子时”的流派区分。我的实践是选择晚子时算次日,这也是目前多数排盘工具采用的方案。实现时要注意顺序:先用真太阳时替换输入的北京时间,再判断小时数是否大于等于 23,如果满足,则把这个日期加一天再用于计算日柱,而时柱仍然按这个真太阳时的实际小时计算。
function adjustForZi(date, solarHour) { if (solarHour >= 23) { const nextDay = new Date(date.getTime() + 24 * 60 * 60 * 1000); return nextDay; } return date; } // 示例:输入原始时间、出生地经度,输出校正后的时间和是否换日 function correctSolarTime(date, longitude) { const offsetMin = (longitude - 120) * 4; const d = new Date(date.getTime() + offsetMin * 60000); // 均时差修正从略,实际需查表或接入近似公式 return d; }逻辑说明:correctSolarTime先做经度校正,把东经 120 度标准时换算成地方平太阳时。adjustForZi在拿到校正后的小时数时做次日子时判断。关键是这个判断必须使用校正后的时间,而不是原始输入时间。某个出生在西部的人,北京时间 23:30 出生,经度校正后可能只有 21:20,那就不该换日;反过来,北京时间 22:00 出生,经度校正后可能变成 23:20,那就得换日。校时不彻底,边界就会错。
参数说明:longitude用十进制度数,东经为正。offsetMin的正负由出生地和 120 度经线的相对位置决定,西部为负,东部为正。均时差修正没写进这段代码,因为它依赖一年中的第几天,工程上我会单独拉一张表或用一个拟合函数。
4.3 处理时区与闰月的通用顺序
真太阳时处理完之后,才轮到日柱和时柱;但年柱和月柱的节气判断,也应使用校正后的时间。这个顺序必须统一:先校时,再排四柱,最后处理闰月归属。很多代码把农历转换放在最前面,结果闰月出生人的年柱、月柱全跟着农历走,节气边界全部失效。
闰月在程序里的处理方式是:先算出当前时间落在哪个节气区间,从而确定月支;然后判断这个节气区间是否属于闰月范围。如果属于闰月,通常月支保持不变,但需要在显示时标注“闰”字。真正有限制性影响的是月支跳跃,因为一个闰月不会导致月支错位,节气推进多少次,月支就变化多少次。你用节气区间去切月支,天然就不会被“闰几月”这种农历表述干扰。
时区问题我一般这样提示用户:数据库里存统一标准时间戳,展示时按用户填写的出生地经度做校正,最后仍然存回标准时间戳。不要在存储层直接覆盖原始时间,保留原始时间可以随时切换不同流派算法,这个习惯在排盘工具迭代时能省掉很多吵架的时间。
5. 八字排盘程序避坑:五个最容易翻车的边界
5.1 立春被当成正月初一,年柱错一整年
现象:公历 2024 年 2 月 3 日出生的人,某些排盘程序排出的年柱是“甲辰”,而实际上立春通常在 2 月 4 日前后,2 月 3 日如果还没到立春时刻,年柱应当是上一年的“癸卯”。
原因:代码里把农历正月初一或者公历 1 月 1 日当作年柱的更新点。农历新年和立春本就不同步,有时立春在春节前,有时在春节后,差距最多能接近一个月。
解决:年柱必须依据精确到分钟的立春时刻。我的做法是内置一份节气表或者调用精确节气计算函数,用出生时间和立春时刻做比较,而不是拿日期做比较。省事一点的方案是只做日期层面比较:立春在 2 月 4 日,但这样每年有约三分之一的概率在时刻上出错,排盘结果错到小时级用户,所以不推荐。
5.2 23:00 出生的子时被排成当天日柱
现象:23:30 出生,日柱算成当天的,时柱是甲子,但第二天出生的排盘结果却完全不同,并且和某几个主流在线排盘网站对比不一致。
原因:不同流派对子时的归属处理不同,有的把 23:00 到 24:00 算当天,有的算次日。如果你的程序没声明这个流派选择,踩中另一种规则的人就会觉得工具是错的。
解决:选定“晚子时算次日”并写进帮助文档。实现上就是 4.2 节的adjustForZi逻辑,先校时再判断是否加一天。我还会在输出结果里保留一个字段,比如timeRule: 'lateZi',方便后续切换或核对。
5.3 闰月直接把“闰几月”当成了月支
现象:某人出生在农历闰四月,代码排出来的月支是“辰”或“巳”混乱,月柱天干也跟着乱跳。
原因:直接对农历月份字符串做了解析,遇到“闰四”就当成一个独立月份处理,没有意识到闰月和节气没有必然绑定关系。
解决:放弃用农历月份排月支,改用节气区间判断月支。闰四月落在哪个节令区间,月支就是哪个,不受“闰”字影响。显示的月份可以标注“闰四月”,但内部计算不要依赖这个字符串。
5.4 整点出生时柱差一小时,全靠分钟决定
现象:11:00 整出生,有的程序排成巳时,有的排成午时,用户换一个工具结果就变了。
原因:时辰切换点严格来说在 11:00,但有的实现用Math.floor(hour / 2)之类的方式,遇到 11 点整会落在错误的边界;还有的程序只取小时整数,把 10:59 和 11:00 归为同一类。
解决:时柱判断必须使用小时加分钟的小数,并且确认边界值包含策略。我把自己习惯定义为:11:00 整算午时,10:59 算巳时。代码里用(hour + 1) / 2向下取整,能保证 10:59 的 hour 为 10.98 时得到巳时,11:00 的 hour 为 11 时得到午时。
5.5 用第三方农历库时忽略节气表精度
现象:某跨平台系统接了一个第三方农历转换库,库本身返回了节气数据,但年柱测试时发现 2 月初的出生数据有系统性错误。
原因:很多农历库的节气数据只有日期精度,没有精确到时分;或者库内置的节气表年份范围不够,超出范围后默认返回近似值。调用者没校验数据精度就直接拿来换年柱,自然翻车。
解决:如果依赖第三方库,先验证至少三年的立春精确时刻和权威历书是否一致。最好还是把节气表单独剥离出来,自己做缓存和校验,不要让农历转换逻辑和节气归属逻辑混在同一个黑匣子里。无论用谁的库,你都需要一个独立核对程序,用已知年份的几个关键日期去对照输出。
6. 从四柱到大运流年:排盘程序下一步怎么扩展
四柱拼完只是地基,多数使用场景还要大运、流年、十神这些后续信息。大运的排法有固定规则:年干为阳干(甲丙戊庚壬)的年份叫阳年,阳年出生男性顺排大运,阴年出生男性逆排大运,女性则相反。顺排就是从月柱的下一个干支开始排列,逆排就是上一个干支。起运岁数按出生时间距离最近一个换月节气的天数除以三计算,比如换算出来是 3 天,那就是一岁起运。
这段逻辑可以沉淀成一个很小的判断函数:
function getDaYunDirection(yearGan, gender) { const isYang = yearGan % 2 === 0; // 甲丙戊庚壬为阳干 const forward = isYang === (gender === '男'); return forward ? 1 : -1; }逻辑说明:阳年男顺排、阴年女顺排、阴年男逆排、阳年女逆排。这个函数把性别和年干索引映射成一个方向参数,正向 1 代表顺排,负向 -1 代表逆排。大运的实际天干地支就是从月柱开始,每次沿这个方向推进一位。注意大运起运时间用的是换月节气,不是换年节气,计算时要确认你拿到的是十二节之一。
流年就简单得多,每一年的干支本身就是一个六十甲子的位置,从年柱的干支开始往下走,每年推进一位。十神的计算则是以日柱天干为中心,把其他天干地支和日干之间的生克五行关系映射成比肩、劫财、食神、伤官、偏财、正财、七杀、正官、偏印、正印这十个名称。这些映射关系一旦做成配置表,后续扩展就很省力。
我自己的习惯是先把日柱、时柱的基准算法做成纯函数,不掺任何展示逻辑,然后大运、流年基于四柱结果再做一层纯计算,最后才拼接 UI 展示。这样做的最大好处是不同流派对于“子时归属”或“节气精度”的争论,只需要在入口层做个开关,核心逻辑全部复用。当然,任何排盘程序都要拿权威万年历做批量验证,我通常会用公历 1900 年之后几十个已知日柱的日期做自动回归测试,确保基准日推导没被后续调整破坏。这套习惯帮我避开了不少低级但致命的历法错误,也希望帮到你。
本文还有配套的精品资源,点击获取