Files
lunar-mini/mini/utils/core/index.js
T

1273 lines
38 KiB
JavaScript

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_FESTIVALS,
LUNAR_FESTIVALS,
BUDDHIST_FESTIVALS,
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;
}
/** 公历转农历 */
function solarToLunar(year, month, day) {
if (year < 1900 || year > 2100) return null;
let offset = 0;
for (let y = 1900; y < year; y++) {
offset += getLunarYearDays(y);
}
let leapMonth = getLeapMonth(year);
let isLeap = false;
let lunarMonth = 1;
let lunarDay = 1;
let daysInMonth = 0;
// 计算从正月初一到目标日期的天数
let targetOffset = offset;
for (let m = 1; m < month; m++) {
targetOffset += getSolarMonthDays(year, m);
}
targetOffset += day - 1;
// 农历年剩余天数
let daysInLunarYear = getLunarYearDays(year);
let remaining = targetOffset - offset;
if (remaining < 0 || remaining >= daysInLunarYear) {
// 需要调整年份
if (remaining < 0) {
year--;
remaining += getLunarYearDays(year);
} else {
remaining -= daysInLunarYear;
year++;
}
leapMonth = getLeapMonth(year);
}
// 逐月计算
for (let m = 1; m <= 12; m++) {
daysInMonth = getLunarMonthDays(year, m);
if (remaining < daysInMonth) {
lunarMonth = m;
lunarDay = remaining + 1;
break;
}
remaining -= daysInMonth;
// 处理闰月
if (m === leapMonth && !isLeap) {
m--;
isLeap = true;
daysInMonth = getLeapMonthDays(year);
if (remaining < daysInMonth) {
lunarMonth = m + 1;
lunarDay = remaining + 1;
isLeap = true;
break;
}
remaining -= daysInMonth;
}
}
return {
year,
month: lunarMonth,
day: lunarDay,
isLeap: isLeap && lunarMonth === leapMonth
};
}
/** 农历转公历 */
function lunarToSolar(year, month, day, isLeap = false) {
if (year < 1900 || year > 2100) return null;
let offset = 0;
for (let y = 1900; y < year; y++) {
offset += getLunarYearDays(y);
}
const leapMonth = getLeapMonth(year);
let days = 0;
for (let m = 1; m < month; m++) {
days += getLunarMonthDays(year, m);
if (m === leapMonth && !isLeap) {
days += getLeapMonthDays(year);
}
}
if (isLeap && month === leapMonth) {
days += getLunarMonthDays(year, month);
}
days += day - 1;
offset += days;
// 从1900年1月31日(农历正月初一)开始计算
let solarYear = 1900;
let solarMonth = 1;
let solarDay = 31;
let remaining = offset;
while (remaining > 0) {
const daysInMonth = getSolarMonthDays(solarYear, solarMonth);
if (remaining < daysInMonth) {
solarDay = remaining + 1;
break;
}
remaining -= daysInMonth;
solarMonth++;
if (solarMonth > 12) {
solarMonth = 1;
solarYear++;
}
}
return { year: solarYear, month: solarMonth, day: solarDay };
}
/** 获取日期的干支 */
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];
}
}
/** 获取节气 */
function getSolarTerm(year, month, day) {
const termDates = SOLAR_TERM_DATES[month - 1];
if (day === termDates[0]) {
return SOLAR_TERMS[(month - 1) * 2];
} else if (day === termDates[1]) {
return SOLAR_TERMS[(month - 1) * 2 + 1];
}
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 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,
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
};