/** * 农历(阴历)工具库 * 支持 1900-2100 年的公历/农历互转 */ // 农历数据表 (1900-2100) // 每个元素编码该年的农历信息: // - 低 12 bit: 各月大小 (1=30天, 0=29天) // - 第 13-16 bit: 闰月月份 (0=无闰月) // - 第 17-20 bit: 闰月大小 (0=29天, 1=30天) const lunarInfo: number[] = [ 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, 0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a4d0, 0x0d150, 0x0f252, 0x0d520 ]; // 天干 const tianGan = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']; // 地支 const diZhi = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']; // 生肖 const shengXiao = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪']; // 农历月份名 const lunarMonthNames = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']; // 农历日名 const lunarDayNames = [ '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十' ]; // 24节气 const solarTermNames = [ '小寒', '大寒', '立春', '雨水', '惊蛰', '春分', '清明', '谷雨', '立夏', '小满', '芒种', '夏至', '小暑', '大暑', '立秋', '处暑', '白露', '秋分', '寒露', '霜降', '立冬', '小雪', '大雪', '冬至' ]; // 节气数据 (1900-2100) 用 delta 编码 const sTermInfo = [ 0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758 ]; /** * 返回农历 year 年的总天数 */ function lunarYearDays(year: number): number { let sum = 348; // 12 * 29 for (let i = 0x8000; i > 0x8; i >>= 1) { sum += (lunarInfo[year - 1900] & i) ? 1 : 0; } return sum + leapDays(year); } /** * 返回农历 year 年闰月的天数,无闰月返回 0 */ function leapDays(year: number): number { if (leapMonth(year)) { return (lunarInfo[year - 1900] & 0x10000) ? 30 : 29; } return 0; } /** * 返回农历 year 年闰月月份,无闰月返回 0 */ function leapMonth(year: number): number { return lunarInfo[year - 1900] & 0xf; } /** * 返回农历 year 年 month 月的天数 */ function monthDays(year: number, month: number): number { return (lunarInfo[year - 1900] & (0x10000 >> month)) ? 30 : 29; } /** * 公历转农历 */ export function solarToLunar(year: number, month: number, day: number) { // 基准日期: 1900年1月31日 = 农历1900年正月初一 const baseDate = new Date(1900, 0, 31); const targetDate = new Date(year, month - 1, day); let offset = Math.floor((targetDate.getTime() - baseDate.getTime()) / 86400000); // 计算农历年 let lunarYear = 1900; let temp = 0; for (lunarYear = 1900; lunarYear < 2101 && offset > 0; lunarYear++) { temp = lunarYearDays(lunarYear); offset -= temp; } if (offset < 0) { offset += temp; lunarYear--; } // 闰月 const leap = leapMonth(lunarYear); let isLeap = false; let lunarMonth = 1; for (let i = 1; i < 13 && offset > 0; i++) { // 闰月 if (leap > 0 && i === (leap + 1) && !isLeap) { --i; isLeap = true; temp = leapDays(lunarYear); } else { temp = monthDays(lunarYear, i); } if (isLeap && i === (leap + 1)) { isLeap = false; } offset -= temp; if (!isLeap) { lunarMonth = i; } } if (offset === 0 && leap > 0 && lunarMonth === leap + 1) { if (isLeap) { isLeap = false; } else { isLeap = true; --lunarMonth; } } if (offset < 0) { offset += temp; --lunarMonth; if (lunarMonth <= 0) { lunarMonth = 12; lunarYear--; } } const lunarDay = offset + 1; // 天干地支 const ganIndex = (lunarYear - 4) % 10; const zhiIndex = (lunarYear - 4) % 12; const ganZhi = tianGan[ganIndex] + diZhi[zhiIndex]; const animal = shengXiao[zhiIndex]; return { year: lunarYear, month: lunarMonth, day: lunarDay, isLeap: isLeap, monthName: (isLeap ? '闰' : '') + lunarMonthNames[lunarMonth - 1] + '月', dayName: lunarDayNames[lunarDay - 1], ganZhi: ganZhi, animal: animal, // 完整中文表示 fullText: `${ganZhi}年(${animal}年)${isLeap ? '闰' : ''}${lunarMonthNames[lunarMonth - 1]}月${lunarDayNames[lunarDay - 1]}` }; } /** * 农历转公历 */ export function lunarToSolar(lunarYear: number, lunarMonth: number, lunarDay: number, isLeapMonth: boolean = false) { // 检查闰月是否有效 const leap = leapMonth(lunarYear); if (isLeapMonth && leap !== lunarMonth) { // 该年该月不是闰月,回退为非闰 isLeapMonth = false; } // 计算从基准日偏移天数 let offset = 0; for (let y = 1900; y < lunarYear; y++) { offset += lunarYearDays(y); } // 加上本年各月天数 let leapM = leapMonth(lunarYear); let isAfterLeap = false; for (let m = 1; m < lunarMonth; m++) { // 如果有闰月且在目标月之前 if (leapM > 0 && m === leapM && !isAfterLeap) { offset += leapDays(lunarYear); isAfterLeap = true; } offset += monthDays(lunarYear, m); } // 如果目标就是闰月 if (isLeapMonth) { offset += monthDays(lunarYear, lunarMonth); } // 如果闰月在目标月之前或等于目标月且不是闰月 if (!isLeapMonth && leapM > 0 && leapM === lunarMonth && !isAfterLeap) { // 不需要额外操作 } offset += lunarDay - 1; // 基准日期: 1900年1月31日 const baseDate = new Date(1900, 0, 31); const targetDate = new Date(baseDate.getTime() + offset * 86400000); return { year: targetDate.getFullYear(), month: targetDate.getMonth() + 1, day: targetDate.getDate() }; } /** * 获取某年某月某日的节气(如果有的话) */ export function getSolarTerm(year: number, month: number, day: number): string | null { // 每个月有两个节气 const termIndex1 = (month - 1) * 2; // 本月第一个节气 const termIndex2 = termIndex1 + 1; // 本月第二个节气 const d1 = getSolarTermDate(year, termIndex1); const d2 = getSolarTermDate(year, termIndex2); if (day === d1) return solarTermNames[termIndex1]; if (day === d2) return solarTermNames[termIndex2]; return null; } /** * 计算某年第 n 个节气的日期(返回日) */ function getSolarTermDate(year: number, n: number): number { const offDate = new Date((31556925974.7 * (year - 1900) + sTermInfo[n] * 60000) + Date.UTC(1900, 0, 6, 2, 5)); return offDate.getUTCDate(); } /** * 获取农历年份的天干地支和生肖 */ export function getYearInfo(year: number) { const ganIndex = (year - 4) % 10; const zhiIndex = (year - 4) % 12; return { ganZhi: tianGan[ganIndex] + diZhi[zhiIndex], animal: shengXiao[zhiIndex] }; } /** * 获取农历月份名 */ export function getLunarMonthName(month: number, isLeap: boolean = false): string { return (isLeap ? '闰' : '') + lunarMonthNames[month - 1] + '月'; } /** * 获取农历日名 */ export function getLunarDayName(day: number): string { return lunarDayNames[day - 1]; } /** * 获取常见节日 */ export function getFestival(month: number, day: number): string | null { const solarFestivals: Record = { '1-1': '元旦', '2-14': '情人节', '3-8': '妇女节', '3-12': '植树节', '4-1': '愚人节', '5-1': '劳动节', '5-4': '青年节', '6-1': '儿童节', '7-1': '建党节', '8-1': '建军节', '9-10': '教师节', '10-1': '国庆节', '10-31': '万圣节', '12-24': '平安夜', '12-25': '圣诞节' }; return solarFestivals[`${month}-${day}`] || null; } /** * 获取农历节日 */ export function getLunarFestival(month: number, day: number): string | null { const lunarFestivals: Record = { '1-1': '春节', '1-15': '元宵节', '2-2': '龙抬头', '5-5': '端午节', '7-7': '七夕', '7-15': '中元节', '8-15': '中秋节', '9-9': '重阳节', '12-8': '腊八节', '12-23': '小年', '12-30': '除夕' }; return lunarFestivals[`${month}-${day}`] || null; }