1273 lines
38 KiB
JavaScript
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
|
|
};
|