Files
lunar-mini/mini/utils/core/index.js
T
gouki 2bd84eea88 修复节气计算/佛历倒计时/顶部固定高度+老黄历方框
- 节气改用精确查表(2000-2060天文算法生成),修复立秋等大偏差
- 佛历倒计时/节日速查以当前选中日期为基准,切换日期实时更新
- 大日历顶部节日区/佛历区固定高度,空着也占位不再跳动
- 大日期加老黄历式方框(双线边框,节假日红/工作日绿)
2026-08-07 22:57:26 +00:00

1452 lines
46 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {
LUNAR_INFO,
HEAVEN_STEMS,
EARTH_BRANCHES,
ZODIACS,
LUNAR_MONTH_NAMES,
LUNAR_DAY_NAMES,
WEEK_NAMES,
CONSTELLATIONS,
CONSTELLATION_DATES,
SOLAR_TERMS,
SOLAR_TERM_DATES,
SOLAR_TERM_TABLE,
SOLAR_FESTIVALS,
LUNAR_FESTIVALS,
BUDDHIST_FESTIVALS,
LEGAL_HOLIDAYS_SOLAR,
LEGAL_HOLIDAYS_LUNAR,
SOLAR_TERM_TIMES,
FESTIVAL_QUICK_SEARCH,
NAYIN_TABLE,
HOUR_NAMES,
HOUR_RANGES,
DUTY_NAMES,
TWELVE_STARS,
TWENTY_EIGHT_STARS,
NINE_STARS,
SIX_STARS,
PENG_ZU,
STEM_ELEMENTS,
BRANCH_ELEMENTS,
STEM_YINYANG,
BRANCH_YINYANG,
HIDE_STEMS,
TERRAINS,
BRANCH_COMBINE,
BRANCH_OPPOSITE,
BRANCH_HARM,
BRANCH_PUNISH,
THREE_COMBINES,
STEM_COMBINE,
STEM_OPPOSITE,
GENERATES,
KILLS,
TRIGRAMS,
HEXAGRAMS,
YEAR_WEIGHTS,
MONTH_WEIGHTS,
DAY_WEIGHTS,
HOUR_WEIGHTS,
BONE_INTERPRETATIONS
} = require('./data.js');
/** 判断是否为闰年 */
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
/** 获取公历某月天数 */
function getSolarMonthDays(year, month) {
const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (month === 2 && isLeapYear(year)) return 29;
return days[month - 1];
}
/** 获取农历年天数 */
function getLunarYearDays(year) {
let sum = 348; // 12个月,每月29天
const info = LUNAR_INFO[year - 1900];
for (let i = 0x8000; i > 0x8; i >>= 1) {
if (info & i) sum += 1;
}
return sum + getLeapMonthDays(year);
}
/** 获取闰月月份(0表示无闰月) */
function getLeapMonth(year) {
return LUNAR_INFO[year - 1900] & 0xf;
}
/** 获取闰月天数 */
function getLeapMonthDays(year) {
if (getLeapMonth(year) === 0) return 0;
return (LUNAR_INFO[year - 1900] & 0x10000) ? 30 : 29;
}
/** 获取农历某月天数 */
function getLunarMonthDays(year, month) {
return (LUNAR_INFO[year - 1900] & (0x10000 >> month)) ? 30 : 29;
}
/** 公历转农历(标准算法,基准 1900-01-31 = 农历1900年正月初一) */
function solarToLunar(year, month, day) {
if (year < 1900 || year > 2100) return null;
// 与基准日 1900-01-31(农历1900年正月初一)的天数差(用 UTC 避免时区误差)
const baseDate = Date.UTC(1900, 0, 31);
const objDate = Date.UTC(year, month - 1, day);
let offset = Math.floor((objDate - baseDate) / 86400000);
let lunarYear = 1900;
let daysOfYear = 0;
// 确定农历年
for (; lunarYear < 2101 && offset > 0; lunarYear++) {
daysOfYear = getLunarYearDays(lunarYear);
if (offset < daysOfYear) break;
offset -= daysOfYear;
}
const leapMonth = getLeapMonth(lunarYear);
let isLeap = false;
let lunarMonth = 1;
let daysOfMonth = 0;
// 确定农历月日
for (; lunarMonth < 13 && offset > 0; lunarMonth++) {
// 闰月
if (leapMonth > 0 && lunarMonth === leapMonth + 1 && !isLeap) {
lunarMonth--;
isLeap = true;
daysOfMonth = getLeapMonthDays(lunarYear);
} else {
daysOfMonth = getLunarMonthDays(lunarYear, lunarMonth);
}
if (offset < daysOfMonth) break;
offset -= daysOfMonth;
// 解除闰月标记
if (isLeap && lunarMonth === leapMonth + 1) isLeap = false;
}
// offset 为 0 且恰好是闰月边界
if (offset === 0 && leapMonth > 0 && lunarMonth === leapMonth + 1) {
if (isLeap) {
isLeap = false;
} else {
isLeap = true;
lunarMonth--;
}
}
if (offset < 0) {
offset += daysOfMonth;
lunarMonth--;
}
return {
year: lunarYear,
month: lunarMonth,
day: offset + 1,
isLeap
};
}
/** 农历转公历(标准算法,基准 1900-01-31 = 农历1900年正月初一) */
function lunarToSolar(year, month, day, isLeap = false) {
if (year < 1900 || year > 2100) return null;
// 累计从 1900 年正月初一到目标农历日期的天数
let offset = 0;
for (let y = 1900; y < year; y++) {
offset += getLunarYearDays(y);
}
const leapMonth = getLeapMonth(year);
// 累计当年 1 月到目标月前一月的 days
for (let m = 1; m < month; m++) {
offset += getLunarMonthDays(year, m);
// 经过闰月(在 m 月之后)
if (m === leapMonth) {
offset += getLeapMonthDays(year);
}
}
// 目标月本身是闰月:需先加上该月正常月天数
if (isLeap && month === leapMonth) {
offset += getLunarMonthDays(year, month);
}
offset += day - 1;
// 基准日 1900-01-31 + offset
const baseMs = Date.UTC(1900, 0, 31);
const target = new Date(baseMs + offset * 86400000);
return {
year: target.getUTCFullYear(),
month: target.getUTCMonth() + 1,
day: target.getUTCDate()
};
}
/** 获取日期的干支 */
function getGanzhi(year, month, day) {
// 以1900年1月1日为甲子日(实际上1900-01-01是甲戌日,这里用近似算法)
// 更准确的算法:以某个已知日期为基准
const baseDate = new Date(1900, 0, 31); // 1900-01-31 是甲子日
const targetDate = new Date(year, month - 1, day);
const diffDays = Math.floor((targetDate - baseDate) / (24 * 60 * 60 * 1000));
const stemIndex = (diffDays % 10 + 10) % 10;
const branchIndex = (diffDays % 12 + 12) % 12;
return {
stem: HEAVEN_STEMS[stemIndex],
branch: EARTH_BRANCHES[branchIndex],
ganzhi: HEAVEN_STEMS[stemIndex] + EARTH_BRANCHES[branchIndex]
};
}
/** 获取年的干支 */
function getYearGanzhi(year) {
const stemIndex = (year - 4) % 10;
const branchIndex = (year - 4) % 12;
return {
stem: HEAVEN_STEMS[stemIndex],
branch: EARTH_BRANCHES[branchIndex],
ganzhi: HEAVEN_STEMS[stemIndex] + EARTH_BRANCHES[branchIndex]
};
}
/** 获取月的干支(以节气划分) */
function getMonthGanzhi(year, month, day) {
// 简化算法:以立春为正月起点
const yearGz = getYearGanzhi(year);
const yearStemIndex = HEAVEN_STEMS.indexOf(yearGz.stem);
// 月干 = (年干 * 2 + 月数) % 10
const monthStemIndex = (yearStemIndex * 2 + month) % 10;
// 月支固定:正月寅,二月卯...
const monthBranchIndex = (month + 1) % 12;
return {
stem: HEAVEN_STEMS[monthStemIndex],
branch: EARTH_BRANCHES[monthBranchIndex],
ganzhi: HEAVEN_STEMS[monthStemIndex] + EARTH_BRANCHES[monthBranchIndex]
};
}
/** 获取时辰的干支 */
function getHourGanzhi(dayGanzhi, hour) {
const dayStemIndex = HEAVEN_STEMS.indexOf(dayGanzhi.stem);
const hourBranchIndex = Math.floor(((hour + 1) % 24) / 2);
// 时干 = (日干 * 2 + 时支) % 10
const hourStemIndex = (dayStemIndex * 2 + hourBranchIndex) % 10;
return {
stem: HEAVEN_STEMS[hourStemIndex],
branch: EARTH_BRANCHES[hourBranchIndex],
ganzhi: HEAVEN_STEMS[hourStemIndex] + EARTH_BRANCHES[hourBranchIndex]
};
}
/** 获取生肖 */
function getZodiac(year) {
return ZODIACS[(year - 4) % 12];
}
/** 获取星座 */
function getConstellation(month, day) {
const dates = CONSTELLATION_DATES;
const index = month - 1;
if (day < dates[index]) {
return CONSTELLATIONS[index];
} else {
return CONSTELLATIONS[index + 1];
}
}
/** 计算某年某节气的公历日期(21世纪 C 值法,误差±1天)
* termIndex 按天文顺序:小寒=0, 大寒=1, 立春=2, 雨水=3, ..., 冬至=23
*/
function getSolarTermDate(year, termIndex) {
// 21世纪 C 值表(小寒=0, 大寒=1, 立春=2 ... 冬至=23)已校准
const C21 = [
5.11, 19.84, 3.6295, 18.4599, 5.3826, 20.4155, // 小寒 大寒 立春 雨水 惊蛰 春分
4.59, 19.888, 5.318, 20.86, 5.5, 21.20, // 清明 谷雨 立夏 小满 芒种 夏至
6.928, 22.65, 7.35, 22.95, 7.44, 22.822, // 小暑 大暑 立秋 处暑 白露 秋分
8.098, 23.218, 7.218, 22.08, 6.9, 21.60 // 寒露 霜降 立冬 小雪 大雪 冬至
];
// 20世纪 C 值表
const C20 = [
6.9, 21.37, 5.4055, 20.12, 7.1082, 22.83,
6.38, 21.646, 7.108, 22.36, 7.5, 23.13,
8.318, 24.64, 9.35, 24.77, 9.74, 24.643,
9.878, 24.918, 9.11, 23.65, 8.8, 23.54
];
const Y = year % 100;
const C = year >= 2000 ? C21[termIndex] : C20[termIndex];
// 闰年修正:小寒、大寒、立春、雨水(1-2月)
let leapAdjust = 0;
if (termIndex <= 3 && isLeapYear(year)) leapAdjust = 1;
// 特殊修正
if (year === 2026 && termIndex === 3) leapAdjust = -1; // 2026雨水
if (year === 2084 && termIndex === 3) leapAdjust = 1;
if (year === 1911 && termIndex === 6) leapAdjust = 1; // 清明
if (year === 1984 && termIndex === 7) leapAdjust = -1; // 谷雨
if (year === 1911 && termIndex === 8) leapAdjust = 1; // 立夏
if (year === 1981 && termIndex === 10) leapAdjust = -1; // 芒种
if (year === 1902 && termIndex === 11) leapAdjust = 1; // 夏至
if (year === 1928 && termIndex === 12) leapAdjust = 1; // 小暑
if (year === 1925 && termIndex === 13) leapAdjust = -1; // 大暑
if (year === 2002 && termIndex === 14) leapAdjust = 1; // 立秋
if (year === 1927 && termIndex === 16) leapAdjust = 1; // 白露
if (year === 1942 && termIndex === 17) leapAdjust = 1; // 秋分
if (year === 2089 && termIndex === 18) leapAdjust = 1; // 寒露
if (year === 2089 && termIndex === 19) leapAdjust = 1; // 霜降
if (year === 1978 && termIndex === 20) leapAdjust = 1; // 立冬
if (year === 1954 && termIndex === 21) leapAdjust = -1; // 小雪
if (year === 1918 && termIndex === 22) leapAdjust = 1; // 大雪
if (year === 2021 && termIndex === 22) leapAdjust = -1;
if (year === 1902 && termIndex === 23) leapAdjust = 1; // 冬至
return Math.floor(Y * 0.2422 + C) - Math.floor(Y / 4) + leapAdjust;
}
/** 获取节气(优先精确查表 2000-2060,范围外用 C 值法兜底)
* SOLAR_TERMS 数组从冬至开始:冬至=0, 小寒=1, 大寒=2, 立春=3, ..., 大雪=23
*/
function getSolarTerm(year, month, day) {
// month 月的两个节气在 SOLAR_TERMS 中的索引:
// 1月→小寒(1)、大寒(2)2月→立春(3)、雨水(4)...12月→大雪(23)、冬至(0)
const idx1 = month === 12 ? 23 : (month * 2 - 1); // 每月第一个节气
const idx2 = month === 12 ? 0 : (month * 2); // 每月第二个节气
// 转为天文顺序(小寒=0):SOLAR_TERMS 索引 - 1(冬至特殊为23
const astroIdx1 = idx1 === 0 ? 23 : idx1 - 1;
const astroIdx2 = idx2 === 0 ? 23 : idx2 - 1;
// 优先用精确表
const table = SOLAR_TERM_TABLE[year];
if (table) {
if (day === table[astroIdx1]) return SOLAR_TERMS[idx1];
if (day === table[astroIdx2]) return SOLAR_TERMS[idx2];
return null;
}
// 兜底:C 值法
const d1 = getSolarTermDate(year, astroIdx1);
const d2 = getSolarTermDate(year, astroIdx2);
if (day === d1) return SOLAR_TERMS[idx1];
if (day === d2) return SOLAR_TERMS[idx2];
return null;
}
/** 获取纳音 */
function getNayin(ganzhi) {
const stemIndex = HEAVEN_STEMS.indexOf(ganzhi[0]);
const branchIndex = EARTH_BRANCHES.indexOf(ganzhi[1]);
const index = Math.floor(stemIndex / 2) * 6 + Math.floor(branchIndex / 2);
return NAYIN_TABLE[index % 30];
}
/** 获取建除十二神 */
function getDuty(monthGanzhi, dayGanzhi) {
const monthBranchIndex = EARTH_BRANCHES.indexOf(monthGanzhi.branch);
const dayBranchIndex = EARTH_BRANCHES.indexOf(dayGanzhi.branch);
const dutyIndex = (dayBranchIndex - monthBranchIndex + 12) % 12;
return DUTY_NAMES[dutyIndex];
}
/** 获取黄道黑道 */
function getTwelveStar(dayGanzhi) {
const branchIndex = EARTH_BRANCHES.indexOf(dayGanzhi.branch);
const starIndex = branchIndex % 12;
const star = TWELVE_STARS[starIndex];
const ecliptic = ['青龙', '明堂', '金匮', '天德', '玉堂', '司命'].includes(star) ? '黄道' : '黑道';
return { name: star, ecliptic, luck: ecliptic === '黄道' ? 'good' : 'bad' };
}
/** 获取二十八宿 */
function getTwentyEightStar(year, month, day) {
// 简化算法:以某已知日期为基准
const baseDate = new Date(1900, 0, 1);
const targetDate = new Date(year, month - 1, day);
const diffDays = Math.floor((targetDate - baseDate) / (24 * 60 * 60 * 1000));
const index = diffDays % 28;
const star = TWENTY_EIGHT_STARS[(index + 28) % 28];
return {
name: star,
luck: Math.random() > 0.5 ? 'good' : 'bad', // 简化
animal: star.slice(-1)
};
}
/** 获取九星 */
function getNineStar(year, month, day) {
const baseDate = new Date(1900, 0, 1);
const targetDate = new Date(year, month - 1, day);
const diffDays = Math.floor((targetDate - baseDate) / (24 * 60 * 60 * 1000));
const index = diffDays % 9;
return {
name: NINE_STARS[(index + 9) % 9],
color: '',
element: ''
};
}
/** 获取六曜 */
function getSixStar(lunarMonth, lunarDay) {
const index = (lunarMonth + lunarDay) % 6;
return SIX_STARS[index];
}
/** 获取小六壬 */
function getMinorRen(lunarMonth, lunarDay, hour) {
const index = (lunarMonth + lunarDay + Math.floor(hour / 2)) % 6;
const names = ['大安', '留连', '速喜', '赤口', '小吉', '空亡'];
const lucks = ['good', 'bad', 'good', 'bad', 'good', 'bad'];
const elements = ['木', '土', '火', '金', '水', '土'];
return {
name: names[index],
luck: lucks[index],
element: elements[index]
};
}
/** 获取月相 */
function getMoonPhase(lunarDay) {
if (lunarDay === 1) return '朔月';
if (lunarDay <= 7) return '上弦月';
if (lunarDay === 15) return '满月';
if (lunarDay <= 22) return '下弦月';
return '残月';
}
/** 获取胎神方位 */
function getFetus(dayGanzhi) {
const directions = ['东', '南', '西', '北', '中'];
const sides = ['房内', '房外'];
const index = HEAVEN_STEMS.indexOf(dayGanzhi.stem) % 5;
return {
direction: directions[index],
side: sides[index % 2],
position: `${directions[index]}${sides[index % 2]}`
};
}
/** 获取宜忌 */
function getRecommendsAvoids(duty) {
const recommendsMap = {
'建': ['出行', '上任', '会友', '上书', '见工'],
'除': ['除服', '疗病', '出行', '拆卸', '入宅'],
'满': ['嫁娶', '祈福', '移徙', '开市', '交易'],
'平': ['嫁娶', '修造', '动土', '竖柱', '上梁'],
'定': ['嫁娶', '祈福', '求嗣', '开光', '出行'],
'执': ['祭祀', '祈福', '求嗣', '开光', '嫁娶'],
'破': ['破屋', '坏垣', '求医', '治病'],
'危': ['祭祀', '祈福', '求嗣', '斋醮', '嫁娶'],
'成': ['嫁娶', '开市', '交易', '立券', '纳财'],
'收': ['纳财', '捕捉', '取渔', '纳畜'],
'开': ['嫁娶', '祈福', '求嗣', '开光', '出行'],
'闭': ['祭祀', '祈福', '求嗣', '斋醮', '安葬']
};
const avoidsMap = {
'建': ['动土', '开仓', '掘井', '开渠'],
'除': ['嫁娶', '开市', '交易', '立券'],
'满': ['安葬', '行丧', '伐木', '作梁'],
'平': ['开市', '交易', '立券', '纳财'],
'定': ['诉讼', '出行', '交涉'],
'执': ['开市', '交易', '立券', '纳财'],
'破': ['嫁娶', '开市', '交易', '立券', '纳财'],
'危': ['登高', '行船', '出行'],
'成': ['诉讼', '争讼'],
'收': ['嫁娶', '开市', '交易', '立券'],
'开': ['安葬', '行丧'],
'闭': ['嫁娶', '开市', '交易', '立券', '纳财']
};
return {
recommends: recommendsMap[duty] || [],
avoids: avoidsMap[duty] || []
};
}
/** 获取吉神凶神 */
function getGods(dayGanzhi) {
// 简化实现
const goodGods = ['天德', '月德', '天恩', '母仓', '时阳', '生气', '益后', '青龙'];
const badGods = ['劫煞', '灾煞', '月煞', '月刑', '月害', '月厌', '大时', '大败'];
return { goodGods, badGods };
}
/** 获取冲煞 */
function getClashHarmCombine(dayBranch) {
const clash = BRANCH_OPPOSITE[dayBranch];
const harm = BRANCH_HARM[dayBranch];
const combine = BRANCH_COMBINE[dayBranch];
const zodiacMap = {};
EARTH_BRANCHES.forEach((b, i) => { zodiacMap[b] = ZODIACS[i]; });
return {
clash: clash ? `${zodiacMap[clash]}(${clash})` : '',
harm: harm ? `${zodiacMap[harm]}(${harm})` : '',
combine: combine ? `${zodiacMap[combine]}(${combine})` : '',
evilDirection: ['北', '南', '东', '西'][EARTH_BRANCHES.indexOf(dayBranch) % 4]
};
}
/** 获取彭祖百忌 */
function getPengZu(dayGanzhi) {
const stemIndex = HEAVEN_STEMS.indexOf(dayGanzhi.stem);
const branchIndex = EARTH_BRANCHES.indexOf(dayGanzhi.branch);
return {
text: PENG_ZU.stems[stemIndex] + ' ' + PENG_ZU.branches[branchIndex],
stem: PENG_ZU.stems[stemIndex],
branch: PENG_ZU.branches[branchIndex]
};
}
/** 获取时辰黄历 */
function getHourlyAlmanac(dayGanzhi) {
const result = [];
for (let i = 0; i < 12; i++) {
const hourGz = getHourGanzhi(dayGanzhi, i * 2);
result.push({
branch: EARTH_BRANCHES[i],
name: HOUR_NAMES[i],
range: HOUR_RANGES[i],
ganzhi: hourGz.ganzhi,
recommends: [],
avoids: [],
twelveStar: TWELVE_STARS[i % 12],
nineStar: NINE_STARS[i % 9]
});
}
return result;
}
/** 获取日信息 */
function getDayInfo(year, month, day) {
const date = new Date(year, month - 1, day);
const weekDay = date.getDay();
const lunar = solarToLunar(year, month, day);
const dayGz = getGanzhi(year, month, day);
const yearGz = getYearGanzhi(year);
const monthGz = getMonthGanzhi(year, month, day);
const solarTerm = getSolarTerm(year, month, day);
const now = new Date();
const isToday = year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate();
return {
solarDate: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
solarDay: day,
solarMonth: month,
solarYear: year,
weekDay: WEEK_NAMES[weekDay],
weekDayIndex: weekDay,
constellation: getConstellation(month, day),
solarTerm,
solarTermTime: null,
isTermDay: !!solarTerm,
currentSolarTerm: null,
season: ['冬季', '冬季', '春季', '春季', '春季', '夏季', '夏季', '夏季', '秋季', '秋季', '秋季', '冬季'][month - 1],
termDayIndex: null,
nextSolarTerm: null,
daysToNextTerm: null,
julianDay: null,
buddhistYear: year + 543,
hijriDate: null,
buddhistFestival: lunar ? BUDDHIST_FESTIVALS[`${lunar.month}-${lunar.day}`] || null : null,
phenology: null,
dogDay: null,
nineDay: null,
lunarYear: lunar ? lunar.year : 0,
lunarMonth: lunar ? lunar.month : 0,
lunarMonthName: lunar ? LUNAR_MONTH_NAMES[lunar.month - 1] : '',
lunarDay: lunar ? lunar.day : 0,
lunarDayName: lunar ? LUNAR_DAY_NAMES[lunar.day - 1] : '',
isLeapMonth: lunar ? lunar.isLeap : false,
lunarYearGanzhi: yearGz.ganzhi,
lunarMonthGanzhi: monthGz.ganzhi,
lunarDayGanzhi: dayGz.ganzhi,
zodiac: getZodiac(year),
lunarFestival: lunar ? LUNAR_FESTIVALS[`${lunar.month}-${lunar.day}`] || null : null,
solarFestival: SOLAR_FESTIVALS[`${month}-${day}`] || null,
legalHoliday: null,
moonPhase: lunar ? getMoonPhase(lunar.day) : null,
isToday,
isWeekend: weekDay === 0 || weekDay === 6,
dayOfWeek: weekDay
};
}
/** 获取今日信息 */
function getTodayInfo() {
const now = new Date();
return getDayInfo(now.getFullYear(), now.getMonth() + 1, now.getDate());
}
/** 获取月历 */
function getMonthCalendar(year, month, weekStart = 0) {
const firstDay = new Date(year, month - 1, 1);
const lastDay = new Date(year, month, 0);
const daysInMonth = lastDay.getDate();
const firstWeekDay = firstDay.getDay();
const weeks = [];
let currentWeek = [];
// 填充上月天数
const prevMonthDays = weekStart === 0 ? firstWeekDay : (firstWeekDay + 6) % 7;
if (prevMonthDays > 0) {
const prevMonth = month === 1 ? 12 : month - 1;
const prevYear = month === 1 ? year - 1 : year;
const prevMonthLastDay = getSolarMonthDays(prevYear, prevMonth);
for (let i = prevMonthDays - 1; i >= 0; i--) {
const d = prevMonthLastDay - i;
currentWeek.push(getDayInfo(prevYear, prevMonth, d));
}
}
// 填充当月天数
for (let d = 1; d <= daysInMonth; d++) {
currentWeek.push(getDayInfo(year, month, d));
if (currentWeek.length === 7) {
weeks.push(currentWeek);
currentWeek = [];
}
}
// 填充下月天数
if (currentWeek.length > 0) {
const nextMonth = month === 12 ? 1 : month + 1;
const nextYear = month === 12 ? year + 1 : year;
let d = 1;
while (currentWeek.length < 7) {
currentWeek.push(getDayInfo(nextYear, nextMonth, d));
d++;
}
weeks.push(currentWeek);
}
return weeks;
}
/** 获取黄历信息 */
function getAlmanacInfo(year, month, day) {
const dayInfo = getDayInfo(year, month, day);
const dayGz = { stem: dayInfo.lunarDayGanzhi[0], branch: dayInfo.lunarDayGanzhi[1], ganzhi: dayInfo.lunarDayGanzhi };
const monthGz = { stem: dayInfo.lunarMonthGanzhi[0], branch: dayInfo.lunarMonthGanzhi[1], ganzhi: dayInfo.lunarMonthGanzhi };
const duty = getDuty(monthGz, dayGz);
const twelveStar = getTwelveStar(dayGz);
const twentyEightStar = getTwentyEightStar(year, month, day);
const nineStar = getNineStar(year, month, day);
const sixStar = getSixStar(dayInfo.lunarMonth, dayInfo.lunarDay);
const minorRen = getMinorRen(dayInfo.lunarMonth, dayInfo.lunarDay, 12);
const phase = dayInfo.moonPhase || '';
const fetus = getFetus(dayGz);
const { recommends, avoids } = getRecommendsAvoids(duty);
const { goodGods, badGods } = getGods(dayGz);
const pengZu = getPengZu(dayGz);
const clashHarm = getClashHarmCombine(dayGz.branch);
const nayin = getNayin(dayGz.ganzhi);
const hourDetails = getHourlyAlmanac(dayGz);
const luckyDuties = ['除', '执', '危', '成', '开'];
const unluckyDuties = ['建', '满', '平', '破', '收', '闭'];
let dutyLuck = 'neutral';
if (luckyDuties.includes(duty)) dutyLuck = 'good';
else if (unluckyDuties.includes(duty)) dutyLuck = 'bad';
return {
duty,
dutyLuck,
twelveStar,
twentyEightStar,
nineStar,
sixStar,
minorRen,
phase,
fetus,
recommends,
avoids,
goodGods,
badGods,
dayStem: dayGz.stem,
dayBranch: dayGz.branch,
dayGanzhi: dayGz.ganzhi,
pengZu: pengZu.text,
pengZuStem: pengZu.stem,
pengZuBranch: pengZu.branch,
clash: clashHarm.clash,
harm: clashHarm.harm,
combine: clashHarm.combine,
evilDirection: clashHarm.evilDirection,
nayin,
hourDetails
};
}
/** 八字排盘 */
function birthInfoToBazi(params) {
const { year, month, day, hour, minute, gender, ziSect = 'lateZiNextDay' } = params;
// 处理晚子时
let y = year, m = month, d = day;
if (ziSect === 'lateZiNextDay' && hour >= 23) {
const nextDay = new Date(year, month - 1, day + 1);
y = nextDay.getFullYear();
m = nextDay.getMonth() + 1;
d = nextDay.getDate();
}
const dayInfo = getDayInfo(y, m, d);
const dayGz = { stem: dayInfo.lunarDayGanzhi[0], branch: dayInfo.lunarDayGanzhi[1], ganzhi: dayInfo.lunarDayGanzhi };
const yearGz = { stem: dayInfo.lunarYearGanzhi[0], branch: dayInfo.lunarYearGanzhi[1], ganzhi: dayInfo.lunarYearGanzhi };
const monthGz = { stem: dayInfo.lunarMonthGanzhi[0], branch: dayInfo.lunarMonthGanzhi[1], ganzhi: dayInfo.lunarMonthGanzhi };
const hourGz = getHourGanzhi(dayGz, hour);
function extractPillarInfo(gz) {
const stem = gz.stem;
const branch = gz.branch;
const hideStems = (HIDE_STEMS[branch] || []).map(h => ({
stem: h.stem,
type: h.type,
tenStar: null
}));
const terrainIndex = (HEAVEN_STEMS.indexOf(stem) * 12 + EARTH_BRANCHES.indexOf(branch)) % 12;
const terrainName = TERRAINS[terrainIndex];
const goodTerrain = ['长生', '冠带', '临官', '帝旺', '胎', '养'];
const badTerrain = ['死', '墓', '绝'];
let terrainFortune = 'neutral';
if (goodTerrain.includes(terrainName)) terrainFortune = 'good';
else if (badTerrain.includes(terrainName)) terrainFortune = 'bad';
return {
ganzhi: gz.ganzhi,
heavenStem: stem,
earthBranch: branch,
elementStem: STEM_ELEMENTS[stem],
elementBranch: BRANCH_ELEMENTS[branch],
yinYangStem: STEM_YINYANG[stem],
yinYangBranch: BRANCH_YINYANG[branch],
hideStems,
nayin: getNayin(gz.ganzhi),
terrain: { name: terrainName, fortune: terrainFortune },
tenStar: null
};
}
const yearPillar = extractPillarInfo(yearGz);
const monthPillar = extractPillarInfo(monthGz);
const dayPillar = extractPillarInfo(dayGz);
const hourPillar = extractPillarInfo(hourGz);
// 计算十神(相对于日主)
const dayStemIndex = HEAVEN_STEMS.indexOf(dayGz.stem);
function getTenStar(stem) {
const stemIndex = HEAVEN_STEMS.indexOf(stem);
const diff = (stemIndex - dayStemIndex + 10) % 10;
const stars = ['比肩', '劫财', '食神', '伤官', '偏财', '正财', '七杀', '正官', '偏印', '正印'];
return stars[diff];
}
yearPillar.tenStar = getTenStar(yearGz.stem);
monthPillar.tenStar = getTenStar(monthGz.stem);
hourPillar.tenStar = getTenStar(hourGz.stem);
// 藏干十神
[yearPillar, monthPillar, dayPillar, hourPillar].forEach(p => {
p.hideStems.forEach(h => {
h.tenStar = getTenStar(h.stem);
});
});
// 胎元、胎息、命宫、身宫(简化)
const fetalOrigin = monthGz.ganzhi;
const fetalBreath = dayGz.ganzhi;
const ownSign = '命宫';
const bodySign = '身宫';
// 空亡
const emptyBranches = [];
const dayBranchIndex = EARTH_BRANCHES.indexOf(dayGz.branch);
const emptyIndex1 = (dayBranchIndex + 10) % 12;
const emptyIndex2 = (dayBranchIndex + 11) % 12;
emptyBranches.push(EARTH_BRANCHES[emptyIndex1], EARTH_BRANCHES[emptyIndex2]);
// 起运(简化)
const childLimit = {
startTime: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')} ${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:00`,
endTime: '',
yearCount: 8,
monthCount: 0,
dayCount: 0,
hourCount: 0,
minuteCount: 0,
forward: gender === 'male',
startAge: 8,
endAge: 88
};
// 大运(简化,10条)
const decadeFortunes = [];
for (let i = 0; i < 10; i++) {
const stemIndex = (HEAVEN_STEMS.indexOf(monthGz.stem) + i + 1) % 10;
const branchIndex = (EARTH_BRANCHES.indexOf(monthGz.branch) + i + 1) % 12;
const ganzhi = HEAVEN_STEMS[stemIndex] + EARTH_BRANCHES[branchIndex];
decadeFortunes.push({
index: i,
ganzhi,
startAge: 8 + i * 10,
endAge: 17 + i * 10,
startYear: year + 8 + i * 10,
endYear: year + 17 + i * 10,
heavenStem: HEAVEN_STEMS[stemIndex],
earthBranch: EARTH_BRANCHES[branchIndex],
nayin: getNayin(ganzhi)
});
}
// 流年(简化,10条)
const annualFortunes = [];
for (let i = 0; i < 10; i++) {
const y = year + i;
const yGz = getYearGanzhi(y);
annualFortunes.push({
age: i + 1,
year: y,
ganzhi: yGz.ganzhi,
nayin: getNayin(yGz.ganzhi)
});
}
return {
eightChar: {
birthDate: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
birthTime: `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`,
gender,
yearPillar,
monthPillar,
dayPillar,
hourPillar,
dayMaster: dayGz.stem + STEM_ELEMENTS[dayGz.stem],
dayMasterStem: dayGz.stem,
dayMasterElement: STEM_ELEMENTS[dayGz.stem],
fetalOrigin,
fetalBreath,
ownSign,
bodySign,
emptyBranches,
emptyTen: ''
},
childLimit,
decadeFortunes,
annualFortunes
};
}
/** 获取地支关系 */
function getBranchRelationship(branchA, branchB) {
const combine = BRANCH_COMBINE[branchA] === branchB;
const opposite = BRANCH_OPPOSITE[branchA] === branchB;
const harm = BRANCH_HARM[branchA] === branchB;
let threeCombine = false;
let formation = null;
for (const [name, branches] of Object.entries(THREE_COMBINES)) {
if (branches.includes(branchA) && branches.includes(branchB)) {
threeCombine = true;
formation = name;
break;
}
}
let punish = false;
for (const [a, b] of BRANCH_PUNISH) {
if ((a === branchA && b === branchB) || (a === branchB && b === branchA)) {
punish = true;
break;
}
}
return { combine, threeCombine, opposite, harm, punish, formation };
}
/** 获取十神关系 */
function getTenStarRelationship(subjectStem, objectStem) {
const sIndex = HEAVEN_STEMS.indexOf(subjectStem);
const oIndex = HEAVEN_STEMS.indexOf(objectStem);
const diff = (oIndex - sIndex + 10) % 10;
const stars = ['比肩', '劫财', '食神', '伤官', '偏财', '正财', '七杀', '正官', '偏印', '正印'];
return stars[diff];
}
/** 检查天干合 */
function checkStemCombine(stemA, stemB) {
return STEM_COMBINE[stemA] === stemB;
}
/** 检查天干冲 */
function checkStemOpposite(stemA, stemB) {
return STEM_OPPOSITE[stemA] === stemB;
}
/** 计算每日运势 */
function calculateDailyFortune(userBazi, date) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const dayInfo = getDayInfo(year, month, day);
const dayGz = { stem: dayInfo.lunarDayGanzhi[0], branch: dayInfo.lunarDayGanzhi[1], ganzhi: dayInfo.lunarDayGanzhi };
const pillars = ['year', 'month', 'day', 'hour'];
const pillarLabels = { year: '年柱', month: '月柱', day: '日柱', hour: '时柱' };
const relationships = [];
for (const key of pillars) {
const userPillar = userBazi[`${key}Pillar`];
const dayP = key === 'year' ? { stem: dayInfo.lunarYearGanzhi[0], branch: dayInfo.lunarYearGanzhi[1] } :
key === 'month' ? { stem: dayInfo.lunarMonthGanzhi[0], branch: dayInfo.lunarMonthGanzhi[1] } :
key === 'day' ? dayGz : { stem: dayGz.stem, branch: dayGz.branch };
const stemTenStar = getTenStarRelationship(userPillar.heavenStem, dayP.stem);
const stemCombine = checkStemCombine(userPillar.heavenStem, dayP.stem);
const stemOpposite = checkStemOpposite(userPillar.heavenStem, dayP.stem);
const branchRel = getBranchRelationship(userPillar.earthBranch, dayP.branch);
let score = 0;
if (stemTenStar) {
const good = ['正印', '偏印', '食神', '正财', '偏财', '正官'];
const bad = ['七杀', '劫财', '伤官'];
if (good.includes(stemTenStar)) score += 4;
else if (bad.includes(stemTenStar)) score -= 3;
else score += 1;
}
if (stemCombine) score += 5;
if (stemOpposite) score -= 6;
if (branchRel.combine) score += 4;
if (branchRel.threeCombine) score += 3;
if (branchRel.opposite) score -= 5;
if (branchRel.harm) score -= 4;
if (branchRel.punish) score -= 3;
score = Math.max(-10, Math.min(10, score));
relationships.push({
pillar: key,
pillarLabel: pillarLabels[key],
userGanzhi: userPillar.ganzhi,
dayGanzhi: dayP.stem + dayP.branch,
stemTenStar,
stemCombine,
stemOpposite,
branchCombine: branchRel.combine,
branchThreeCombine: branchRel.threeCombine,
branchOpposite: branchRel.opposite,
branchHarm: branchRel.harm,
branchPunish: branchRel.punish,
branchFormation: branchRel.formation,
score
});
}
const weightedScore = relationships[0].score * 0.20 + relationships[1].score * 0.25 +
relationships[2].score * 0.40 + relationships[3].score * 0.15;
const overallScore = Math.max(-100, Math.min(100, Math.round(weightedScore * 10)));
const scoreLevel = overallScore >= 50 ? 'great' : overallScore >= 20 ? 'good' :
overallScore >= -20 ? 'fair' : overallScore >= -50 ? 'poor' : 'bad';
const luckyAspects = [];
const unluckyAspects = [];
const suggestions = [];
const affectedAreas = ['综合运势'];
// 简化生成
if (overallScore >= 20) {
luckyAspects.push('今天运势不错,适合推进重要事项');
suggestions.push('✅ 适合推进重要事项,果断决策会有好结果');
} else if (overallScore <= -20) {
unluckyAspects.push('今天运势低迷,宜保守行事');
suggestions.push('🛡️ 宜静不宜动重要决策能缓则缓');
} else {
luckyAspects.push('今天整体运势平稳');
suggestions.push('💡 运势平稳该做什么就做什么');
}
const de = userBazi.dayMasterElement;
const colorMap = { '木': ['绿色', '青色'], '火': ['红色', '紫色'], '土': ['黄色', '棕色'], '金': ['白色', '银色'], '水': ['蓝色', '黑色'] };
const dirMap = { '木': '东方', '火': '南方', '土': '中央', '金': '西方', '水': '北方' };
const colors = colorMap[de] || ['红色', '白色'];
const direction = dirMap[de] || '东方';
return {
date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
lunarDate: `${dayInfo.lunarYear}${dayInfo.lunarMonthName}${dayInfo.lunarDayName}`,
dayGanzhi: dayGz.ganzhi,
overallScore,
scoreLevel,
pillarRelationships: relationships,
luckyAspects,
unluckyAspects,
suggestions,
affectedAreas,
luckyMeta: {
colors,
numbers: [3, 7],
direction,
element: de,
activity: '阅读'
},
categoryScores: {
love: overallScore,
career: overallScore,
wealth: overallScore,
health: overallScore
}
};
}
/** 五行分析 */
function analyzeElementBalance(bazi) {
const pillars = [bazi.yearPillar, bazi.monthPillar, bazi.dayPillar, bazi.hourPillar];
const counts = { '木': 0, '火': 0, '土': 0, '金': 0, '水': 0 };
for (const pillar of pillars) {
if (pillar.elementStem) counts[pillar.elementStem] += 1;
if (pillar.elementBranch) counts[pillar.elementBranch] += 1;
for (const hs of pillar.hideStems) {
const el = STEM_ELEMENTS[hs.stem];
if (el) counts[el] += 0.5;
}
}
const total = Object.values(counts).reduce((a, b) => a + b, 0);
let dominant = '木', weakest = '木';
let maxCount = 0, minCount = Infinity;
for (const [el, count] of Object.entries(counts)) {
if (count > maxCount) { maxCount = count; dominant = el; }
if (count < minCount) { minCount = count; weakest = el; }
}
const ideal = total / 5;
let isBalanced = true;
for (const count of Object.values(counts)) {
if (Math.abs(count - ideal) > ideal * 0.4) {
isBalanced = false;
break;
}
}
return {
wood: counts['木'],
fire: counts['火'],
earth: counts['土'],
metal: counts['金'],
water: counts['水'],
total,
dominant,
weakest,
isBalanced
};
}
/** 梅花易数 */
function calculatePlumBlossom(year, month, day, hour = 12) {
const yearNum = year;
const monthNum = month;
const dayNum = day;
const hourIndex = Math.floor(((hour + 1) % 24) / 2);
const upperIdx = ((yearNum + monthNum + dayNum) % 8) + 1;
const lowerIdx = ((monthNum + dayNum + hourIndex + 1) % 8) + 1;
const changingLine = ((yearNum + monthNum + dayNum + hourIndex + 1) % 6) + 1;
const upperTrigram = TRIGRAMS[upperIdx];
const lowerTrigram = TRIGRAMS[lowerIdx];
const originalKey = `${upperIdx},${lowerIdx}`;
const originalHexagram = HEXAGRAMS[originalKey] || {
number: (upperIdx - 1) * 8 + lowerIdx,
name: upperTrigram.name + lowerTrigram.name,
upperTrigram,
lowerTrigram,
changingLine: 0,
interpretation: '此卦象需结合具体事理参详',
judgment: '',
image: '',
lines: ['', '', '', '', '', '']
};
// 变卦
let transUpperIdx = upperIdx;
let transLowerIdx = lowerIdx;
if (changingLine >= 4) {
transUpperIdx = flipTrigramLine(upperIdx, changingLine - 3);
} else {
transLowerIdx = flipTrigramLine(lowerIdx, changingLine);
}
const transKey = `${transUpperIdx},${transLowerIdx}`;
const transformedHexagram = HEXAGRAMS[transKey] || null;
// 互卦(简化)
const mutualKey = `${lowerIdx},${upperIdx}`;
const mutualHexagram = HEXAGRAMS[mutualKey] || null;
const constitution = lowerTrigram.element;
const function_ = upperTrigram.element;
const relationship = getElementRelationship(constitution, function_);
return {
originalHexagram: { ...originalHexagram, changingLine },
transformedHexagram,
mutualHexagram,
upperTrigram,
lowerTrigram,
changingLine,
constitution,
function: function_,
relationship
};
}
function flipTrigramLine(trigramIdx, line) {
const encoding = [0, 7, 6, 5, 4, 3, 2, 1, 0];
let bits = encoding[trigramIdx] || 0;
bits ^= (1 << (line - 1));
const decoding = [8, 7, 6, 2, 5, 3, 4, 1];
return decoding[bits] || trigramIdx;
}
function getElementRelationship(body, func) {
if (body === func) return '比和(体用相同,诸事顺利)';
if (GENERATES[body] === func) return '体生用(泄气,宜守不宜攻)';
if (GENERATES[func] === body) return '用生体(得力,有贵人相助)';
if (KILLS[func] === body) return '用克体(受制,诸事不顺)';
if (KILLS[body] === func) return '体克用(主动,需付出努力)';
return '体用相生';
}
/** 称骨算命 */
function calculateBoneWeight(lunarYearGanzhiIndex, lunarMonth, lunarDay, earthBranchHourIndex) {
const yearWt = YEAR_WEIGHTS[lunarYearGanzhiIndex] || 9;
const monthWt = MONTH_WEIGHTS[lunarMonth] || 9;
const dayWt = DAY_WEIGHTS[lunarDay] || 9;
const hourWt = HOUR_WEIGHTS[earthBranchHourIndex] || 9;
const totalWeight = yearWt + monthWt + dayWt + hourWt;
const totalLiang = Math.floor(totalWeight / 10);
const totalQian = totalWeight % 10;
const interpretation = BONE_INTERPRETATIONS[totalWeight] || { text: '命格推来,自有天定', fortune: '中' };
return {
yearWeight: yearWt,
monthWeight: monthWt,
dayWeight: dayWt,
hourWeight: hourWt,
totalWeight,
totalLiang,
totalQian,
interpretation: interpretation.text,
fortune: interpretation.fortune
};
}
/** 获取佛教节日 */
function getBuddhistFestival(lunarMonth, lunarDay) {
return BUDDHIST_FESTIVALS[`${lunarMonth}-${lunarDay}`] || null;
}
/** 判断是否为法定节假日 */
function getLegalHoliday(year, month, day) {
const solarKey = `${month}-${day}`;
if (LEGAL_HOLIDAYS_SOLAR[solarKey]) return LEGAL_HOLIDAYS_SOLAR[solarKey];
const lunar = solarToLunar(year, month, day);
if (lunar) {
const lunarKey = `${lunar.month}-${lunar.day}`;
if (LEGAL_HOLIDAYS_LUNAR[lunarKey]) return LEGAL_HOLIDAYS_LUNAR[lunarKey];
}
return null;
}
/** 计算某天是星期几(0=周日) */
function getWeekDay(year, month, day) {
return new Date(year, month - 1, day).getDay();
}
/** 判断是否为工作日(周一到周五且非法定节假日) */
function isWorkday(year, month, day) {
if (getLegalHoliday(year, month, day)) return false;
const wd = getWeekDay(year, month, day);
return wd >= 1 && wd <= 5;
}
/** 农历转公历(某年) */
function lunarFestivalToSolar(year, lunarMonth, lunarDay) {
// 除夕特殊:腊月最后一天
if (lunarMonth === 12 && lunarDay === 30) {
const daysInMonth = getLunarMonthDays(year, 12);
const leap = getLeapMonth(year);
if (leap === 12) {
return lunarToSolar(year, 12, daysInMonth, true);
}
return lunarToSolar(year, 12, daysInMonth, false);
}
return lunarToSolar(year, lunarMonth, lunarDay, false);
}
/** 计算某农历节日在公历 year 年的日期 */
function getFestivalSolarDate(year, fest) {
if (fest.type === 'solar') {
const [m, d] = fest.date.split('-').map(Number);
return { year, month: m, day: d };
}
const [lm, ld] = fest.date.split('-').map(Number);
// 农历节日可能落在上一公历年(如春节在1-2月)
// 先按当前公历年算
let solar = lunarFestivalToSolar(year, lm, ld);
if (!solar) return null;
return solar;
}
/** 计算两个日期相差天数(target - from */
function daysBetween(fromY, fromM, fromD, toY, toM, toD) {
const a = new Date(fromY, fromM - 1, fromD);
const b = new Date(toY, toM - 1, toD);
return Math.round((b - a) / (24 * 60 * 60 * 1000));
}
/** 获取即将到来的 N 个节日(含基准日之后的)
* baseYear/baseMonth/baseDay 可选,默认今天
*/
function getUpcomingFestivals(count = 6, baseYear, baseMonth, baseDay) {
const now = new Date();
const y = baseYear || now.getFullYear();
const m = baseMonth || (now.getMonth() + 1);
const d = baseDay || now.getDate();
const list = [];
for (const fest of FESTIVAL_QUICK_SEARCH) {
// 尝试今年和明年
for (const tryYear of [y, y + 1]) {
const solar = getFestivalSolarDate(tryYear, fest);
if (!solar) continue;
const diff = daysBetween(y, m, d, solar.year, solar.month, solar.day);
if (diff >= 0) {
list.push({
name: fest.name,
solarDate: `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`,
solarYear: solar.year,
solarMonth: solar.month,
solarDay: solar.day,
daysLeft: diff
});
break;
}
}
}
list.sort((a, b) => a.daysLeft - b.daysLeft);
return list.slice(0, count);
}
/** 获取下一个佛教节日及倒计时(基准日可选,默认今天) */
function getNextBuddhistFestival(baseYear, baseMonth, baseDay) {
const now = new Date();
const y = baseYear || now.getFullYear();
const m = baseMonth || (now.getMonth() + 1);
const d = baseDay || now.getDate();
const entries = Object.entries(BUDDHIST_FESTIVALS);
const list = [];
for (const [key, name] of entries) {
const [lm, ld] = key.split('-').map(Number);
for (const tryYear of [y, y + 1]) {
const solar = lunarFestivalToSolar(tryYear, lm, ld);
if (!solar) continue;
const diff = daysBetween(y, m, d, solar.year, solar.month, solar.day);
if (diff >= 0) {
list.push({ name, solarDate: `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`, daysLeft: diff });
break;
}
}
}
list.sort((a, b) => a.daysLeft - b.daysLeft);
return list[0] || null;
}
/** 神煞分析 */
function analyzeShensha(bazi) {
const result = [];
const pillars = [bazi.yearPillar, bazi.monthPillar, bazi.dayPillar, bazi.hourPillar];
const dayStem = bazi.dayMasterStem;
const dayBranch = bazi.dayPillar.earthBranch;
// 天乙贵人
const tianYiMap = { '甲': ['丑', '未'], '戊': ['丑', '未'], '乙': ['子', '申'], '己': ['子', '申'], '丙': ['亥', '酉'], '丁': ['亥', '酉'], '壬': ['卯', '巳'], '癸': ['卯', '巳'], '辛': ['寅', '午'] };
const tianYiTargets = tianYiMap[dayStem] || [];
const tianYiFound = pillars.filter(p => tianYiTargets.includes(p.earthBranch));
if (tianYiFound.length > 0) {
result.push({
name: '天乙贵人',
type: '吉',
category: '贵人',
anchors: ['日干'],
foundIn: tianYiFound.map(p => p.earthBranch),
description: '最吉之神煞,主逢凶化吉、贵人相助'
});
}
// 桃花
const taoHuaMap = { '申': '酉', '子': '酉', '辰': '酉', '寅': '卯', '午': '卯', '戌': '卯', '巳': '午', '酉': '午', '丑': '午', '亥': '子', '卯': '子', '未': '子' };
const taoHuaTarget = taoHuaMap[dayBranch];
if (taoHuaTarget) {
const taoHuaFound = pillars.filter(p => p.earthBranch === taoHuaTarget);
if (taoHuaFound.length > 0) {
result.push({
name: '桃花(咸池)',
type: '中性',
category: '感情',
anchors: ['日支'],
foundIn: taoHuaFound.map(p => p.earthBranch),
description: '主异性缘、魅力与风流,利艺术才华'
});
}
}
return result;
}
/** 大运/流年分析 */
function analyzeFortuneGanzhi(ganzhi, dayStem, dayBranch) {
const stem = ganzhi[0];
const branch = ganzhi[1];
const tenStar = getTenStarRelationship(dayStem, stem);
const el = STEM_ELEMENTS[stem];
const de = STEM_ELEMENTS[dayStem];
let elementRelation;
if (!el || !de || el === de) elementRelation = '比和';
else if (GENERATES[el] === de) elementRelation = '生我';
else if (GENERATES[de] === el) elementRelation = '我生';
else if (KILLS[el] === de) elementRelation = '克我';
else elementRelation = '我克';
const stemCombine = checkStemCombine(stem, dayStem);
const stemOpposite = checkStemOpposite(stem, dayStem);
const br = getBranchRelationship(branch, dayBranch);
let score = 0;
const goodTenStars = ['正印', '偏印', '食神', '正财', '偏财', '正官'];
const badTenStars = ['七杀', '劫财', '伤官'];
if (tenStar) {
if (goodTenStars.includes(tenStar)) score += 4;
else if (badTenStars.includes(tenStar)) score -= 3;
else score += 1;
}
if (stemCombine) score += 5;
if (stemOpposite) score -= 6;
if (br.combine) score += 4;
if (br.threeCombine) score += 3;
if (br.opposite) score -= 5;
if (br.harm) score -= 4;
if (br.punish) score -= 3;
score = Math.max(-10, Math.min(10, score));
const level = score >= 3 ? '吉' : score <= -3 ? '凶' : '平';
return {
ganzhi,
tenStar,
elementRelation,
stemCombine,
stemOpposite,
branchCombine: br.combine,
branchThreeCombine: br.threeCombine,
branchOpposite: br.opposite,
branchHarm: br.harm,
branchPunish: br.punish,
score,
level
};
}
/** 获取流月 */
function getYearMonths(year) {
const months = [];
const jieIndices = [3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 1];
const monthNames = ['正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '冬月', '腊月'];
for (let k = 0; k < 12; k++) {
const termYear = k === 11 ? year + 1 : year;
const termIndex = jieIndices[k];
const month = Math.floor(termIndex / 2) + 1;
const day = SOLAR_TERM_DATES[month - 1][termIndex % 2];
const dayInfo = getDayInfo(termYear, month, day);
months.push({
index: k,
name: monthNames[k],
solarMonth: month,
startDate: `${termYear}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
endDate: '',
ganzhi: dayInfo.lunarMonthGanzhi
});
}
return months;
}
module.exports = {
getDayInfo,
getTodayInfo,
getMonthCalendar,
getAlmanacInfo,
birthInfoToBazi,
getBranchRelationship,
getTenStarRelationship,
checkStemCombine,
checkStemOpposite,
calculateDailyFortune,
analyzeElementBalance,
calculatePlumBlossom,
calculateBoneWeight,
getBuddhistFestival,
getLegalHoliday,
isWorkday,
getWeekDay,
getFestivalSolarDate,
getUpcomingFestivals,
getNextBuddhistFestival,
daysBetween,
analyzeShensha,
analyzeFortuneGanzhi,
getYearMonths,
solarToLunar,
lunarToSolar,
getGanzhi,
getYearGanzhi,
getMonthGanzhi,
getHourGanzhi,
getZodiac,
getConstellation,
getSolarTerm,
getNayin,
HEAVEN_STEMS,
EARTH_BRANCHES,
ZODIACS,
LUNAR_MONTH_NAMES,
LUNAR_DAY_NAMES,
WEEK_NAMES,
SOLAR_TERMS,
HOUR_NAMES,
HOUR_RANGES,
TRIGRAMS,
HEXAGRAMS
};