/**
* 日期工具类
* 处理公历、农历转换,节气计算等功能
*/
const Lunar = require('lunar-javascript');
const moment = require('moment-timezone');
class DateUtils {
constructor() {
this.timezone = 'Asia/Shanghai';
}
/**
* 将公历日期转换为农历
* @param {Date|string} date - 公历日期
* @returns {Object} 农历信息
*/
solarToLunar(date) {
try {
const solarDate = moment(date).tz(this.timezone);
const lunar = Lunar.Solar.fromDate(solarDate.toDate()).getLunar();
return {
year: lunar.getYear(),
month: lunar.getMonth(),
day: lunar.getDay(),
yearInChinese: lunar.getYearInChinese(),
monthInChinese: lunar.getMonthInChinese(),
dayInChinese: lunar.getDayInChinese(),
yearInGanZhi: lunar.getYearInGanZhi(),
monthInGanZhi: lunar.getMonthInGanZhi(),
dayInGanZhi: lunar.getDayInGanZhi(),
timeInGanZhi: this.getTimeInGanZhi(solarDate.hour()),
isLeapMonth: lunar.isLeap(),
festivals: lunar.getFestivals(),
jieQi: lunar.getJieQi(),
constellation: lunar.getXingZuo(),
animal: lunar.getYearShengXiao()
};
} catch (error) {
throw new Error(`日期转换失败: ${error.message}`);
}
}
/**
* 根据时辰获取时柱干支
* @param {number} hour - 小时(0-23)
* @returns {string} 时柱干支
*/
getTimeInGanZhi(hour) {
const timeMap = {
23: '子', 0: '子', 1: '丑', 2: '丑',
3: '寅', 4: '寅', 5: '卯', 6: '卯',
7: '辰', 8: '辰', 9: '巳', 10: '巳',
11: '午', 12: '午', 13: '未', 14: '未',
15: '申', 16: '申', 17: '酉', 18: '酉',
19: '戌', 20: '戌', 21: '亥', 22: '亥'
};
const timeBranch = timeMap[hour];
if (!timeBranch) return '未知';
// 根据日干推算时干
// 这里需要结合具体的日干来计算时干
// 简化处理,返回地支
return timeBranch;
}
/**
* 计算完整的四柱八字
* @param {Date|string} birthDate - 出生日期时间
* @param {string} timezone - 时区,默认为北京时间
* @returns {Object} 四柱八字信息
*/
calculateBaZi(birthDate, timezone = 'Asia/Shanghai') {
try {
const birthMoment = moment(birthDate).tz(timezone);
const solar = Lunar.Solar.fromDate(birthMoment.toDate());
const lunar = solar.getLunar();
// 获取年柱
const yearGanZhi = lunar.getYearInGanZhi();
const yearGan = yearGanZhi.charAt(0);
const yearZhi = yearGanZhi.charAt(1);
// 获取月柱
const monthGanZhi = lunar.getMonthInGanZhi();
const monthGan = monthGanZhi.charAt(0);
const monthZhi = monthGanZhi.charAt(1);
// 获取日柱
const dayGanZhi = lunar.getDayInGanZhi();
const dayGan = dayGanZhi.charAt(0);
const dayZhi = dayGanZhi.charAt(1);
// 计算时柱
const hour = birthMoment.hour();
const timeZhi = this.getTimeBranch(hour);
const timeGan = this.calculateTimeStem(dayGan, timeZhi);
return {
year: { stem: yearGan, branch: yearZhi, ganZhi: yearGanZhi },
month: { stem: monthGan, branch: monthZhi, ganZhi: monthGanZhi },
day: { stem: dayGan, branch: dayZhi, ganZhi: dayGanZhi },
time: { stem: timeGan, branch: timeZhi, ganZhi: timeGan + timeZhi },
birthInfo: {
solar: {
year: birthMoment.year(),
month: birthMoment.month() + 1,
day: birthMoment.date(),
hour: birthMoment.hour(),
minute: birthMoment.minute()
},
lunar: {
year: lunar.getYear(),
month: lunar.getMonth(),
day: lunar.getDay(),
yearInChinese: lunar.getYearInChinese(),
monthInChinese: lunar.getMonthInChinese(),
dayInChinese: lunar.getDayInChinese(),
isLeapMonth: lunar.isLeap()
},
timezone: timezone,
jieQi: lunar.getJieQi(),
constellation: lunar.getXingZuo(),
animal: lunar.getYearShengXiao()
}
};
} catch (error) {
throw new Error(`八字计算失败: ${error.message}`);
}
}
/**
* 根据小时获取时支
* @param {number} hour - 小时(0-23)
* @returns {string} 时支
*/
getTimeBranch(hour) {
const branches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
if (hour === 23 || hour === 0) return '子';
if (hour >= 1 && hour <= 2) return '丑';
if (hour >= 3 && hour <= 4) return '寅';
if (hour >= 5 && hour <= 6) return '卯';
if (hour >= 7 && hour <= 8) return '辰';
if (hour >= 9 && hour <= 10) return '巳';
if (hour >= 11 && hour <= 12) return '午';
if (hour >= 13 && hour <= 14) return '未';
if (hour >= 15 && hour <= 16) return '申';
if (hour >= 17 && hour <= 18) return '酉';
if (hour >= 19 && hour <= 20) return '戌';
if (hour >= 21 && hour <= 22) return '亥';
return '子'; // 默认返回子时
}
/**
* 根据日干和时支计算时干
* @param {string} dayStem - 日干
* @param {string} timeBranch - 时支
* @returns {string} 时干
*/
calculateTimeStem(dayStem, timeBranch) {
const stems = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
const branches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
const dayIndex = stems.indexOf(dayStem);
const timeIndex = branches.indexOf(timeBranch);
if (dayIndex === -1 || timeIndex === -1) return '甲'; // 默认返回甲
// 时干计算公式:(日干序号 * 2 + 时支序号) % 10
const timeStemIndex = (dayIndex * 2 + timeIndex) % 10;
return stems[timeStemIndex];
}
/**
* 计算大运
* @param {Object} bazi - 八字信息
* @param {string} gender - 性别 ('male' | 'female')
* @returns {Array} 大运信息
*/
calculateMajorLuck(bazi, gender) {
try {
const birthYear = bazi.birthInfo.solar.year;
const monthStem = bazi.month.stem;
const monthBranch = bazi.month.branch;
const stems = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
const branches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
const monthStemIndex = stems.indexOf(monthStem);
const monthBranchIndex = branches.indexOf(monthBranch);
// 判断顺逆
const yearStem = bazi.year.stem;
const isYangYear = ['甲', '丙', '戊', '庚', '壬'].includes(yearStem);
const isForward = (gender === 'male' && isYangYear) || (gender === 'female' && !isYangYear);
const majorLuck = [];
const direction = isForward ? 1 : -1;
for (let i = 0; i < 8; i++) {
const stemIndex = (monthStemIndex + direction * (i + 1) + 10) % 10;
const branchIndex = (monthBranchIndex + direction * (i + 1) + 12) % 12;
const startAge = (i + 1) * 10;
const endAge = startAge + 9;
const startYear = birthYear + startAge;
const endYear = birthYear + endAge;
majorLuck.push({
period: i + 1,
stem: stems[stemIndex],
branch: branches[branchIndex],
ganZhi: stems[stemIndex] + branches[branchIndex],
startAge,
endAge,
startYear,
endYear,
description: `第${i + 1}步大运:${stems[stemIndex]}${branches[branchIndex]}(${startAge}-${endAge}岁)`
});
}
return majorLuck;
} catch (error) {
throw new Error(`大运计算失败: ${error.message}`);
}
}
/**
* 计算流年
* @param {number} year - 年份
* @returns {Object} 流年信息
*/
calculateAnnualLuck(year) {
try {
const solar = Lunar.Solar.fromYmd(year, 1, 1);
const lunar = solar.getLunar();
const yearGanZhi = lunar.getYearInGanZhi();
return {
year,
stem: yearGanZhi.charAt(0),
branch: yearGanZhi.charAt(1),
ganZhi: yearGanZhi,
animal: lunar.getYearShengXiao(),
description: `${year}年 ${yearGanZhi}年 ${lunar.getYearShengXiao()}年`
};
} catch (error) {
throw new Error(`流年计算失败: ${error.message}`);
}
}
/**
* 计算流月
* @param {number} year - 年份
* @param {number} month - 月份
* @returns {Object} 流月信息
*/
calculateMonthlyLuck(year, month) {
try {
const solar = Lunar.Solar.fromYmd(year, month, 1);
const lunar = solar.getLunar();
const monthGanZhi = lunar.getMonthInGanZhi();
return {
year,
month,
stem: monthGanZhi.charAt(0),
branch: monthGanZhi.charAt(1),
ganZhi: monthGanZhi,
lunarMonth: lunar.getMonth(),
lunarMonthInChinese: lunar.getMonthInChinese(),
jieQi: lunar.getJieQi(),
description: `${year}年${month}月 ${monthGanZhi}月`
};
} catch (error) {
throw new Error(`流月计算失败: ${error.message}`);
}
}
/**
* 验证日期格式
* @param {string|Date} date - 日期
* @returns {boolean} 是否有效
*/
isValidDate(date) {
const momentDate = moment(date);
return momentDate.isValid();
}
/**
* 格式化日期
* @param {string|Date} date - 日期
* @param {string} format - 格式
* @returns {string} 格式化后的日期
*/
formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
return moment(date).tz(this.timezone).format(format);
}
/**
* 获取当前时间的八字
* @returns {Object} 当前时间八字
*/
getCurrentBaZi() {
return this.calculateBaZi(new Date());
}
/**
* 计算年龄
* @param {string|Date} birthDate - 出生日期
* @param {string|Date} currentDate - 当前日期,默认为今天
* @returns {Object} 年龄信息
*/
calculateAge(birthDate, currentDate = new Date()) {
const birth = moment(birthDate);
const current = moment(currentDate);
const years = current.diff(birth, 'years');
const months = current.diff(birth, 'months') % 12;
const days = current.diff(birth.add(years, 'years').add(months, 'months'), 'days');
return {
years,
months,
days,
totalDays: current.diff(birth, 'days'),
description: `${years}岁${months}个月${days}天`
};
}
/**
* 获取节气信息
* @param {number} year - 年份
* @returns {Array} 节气列表
*/
getJieQiList(year) {
try {
const jieQiList = [];
for (let month = 1; month <= 12; month++) {
const solar = Lunar.Solar.fromYmd(year, month, 1);
const lunar = solar.getLunar();
const jieQi = lunar.getJieQi();
if (jieQi) {
jieQiList.push({
name: jieQi,
month,
description: `${year}年${month}月 ${jieQi}`
});
}
}
return jieQiList;
} catch (error) {
throw new Error(`节气获取失败: ${error.message}`);
}
}
}
module.exports = DateUtils;