- 宜忌弹窗字体 44→32rpx、收紧间距,20+ 条可完整显示 - 历史上的今天内页年份加粗、文字稍小,修正层级 - 节日速查外页3条/内页10条,佛历开启时并入排序 - 统一「更多」箭头为 >>
1509 lines
48 KiB
JavaScript
1509 lines
48 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_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');
|
||
const { Solar } = require('../lunar.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 pengZu = getPengZu(dayGz);
|
||
const clashHarm = getClashHarmCombine(dayGz.branch);
|
||
const nayin = getNayin(dayGz.ganzhi);
|
||
const hourDetails = getHourlyAlmanac(dayGz);
|
||
|
||
// 用 lunar-javascript 获取真实的宜忌/吉神/凶煞(老黄历数据,最多 20+ 项)
|
||
const lunar = Solar.fromYmd(year, month, day).getLunar();
|
||
const recommends = lunar.getDayYi();
|
||
const avoids = lunar.getDayJi();
|
||
const goodGods = lunar.getDayJiShen();
|
||
const badGods = lunar.getDayXiongSha();
|
||
|
||
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 可选,默认今天
|
||
* includeBuddhist 为 true 时,把佛教节日也并入一起按倒计时排序
|
||
*/
|
||
function getUpcomingFestivals(count = 6, baseYear, baseMonth, baseDay, includeBuddhist = false) {
|
||
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,
|
||
type: fest.type,
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 佛历节日(开启后并入,统一按倒计时排序)
|
||
if (includeBuddhist) {
|
||
for (const [key, name] of Object.entries(BUDDHIST_FESTIVALS)) {
|
||
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,
|
||
type: 'buddhist',
|
||
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 getYearFestivals(year) {
|
||
const list = [];
|
||
for (const fest of FESTIVAL_QUICK_SEARCH) {
|
||
const solar = getFestivalSolarDate(year, fest);
|
||
if (solar) {
|
||
list.push({
|
||
name: fest.name,
|
||
type: fest.type,
|
||
solarDate: `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`,
|
||
solarYear: solar.year,
|
||
solarMonth: solar.month,
|
||
solarDay: solar.day
|
||
});
|
||
}
|
||
}
|
||
// 按日期排序
|
||
list.sort((a, b) => {
|
||
if (a.solarMonth !== b.solarMonth) return a.solarMonth - b.solarMonth;
|
||
return a.solarDay - b.solarDay;
|
||
});
|
||
return list;
|
||
}
|
||
|
||
/** 获取下一个佛教节日及倒计时(基准日可选,默认今天) */
|
||
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,
|
||
getYearFestivals,
|
||
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
|
||
};
|