// 农历(阴历)与节日计算:公历转农历采用 1900-2100 查表法,无需外部依赖。 // 导出 getCalendarInfo(date):返回 { lunarText, festival },供 footer 显示用。 // 农历数据表:每项表示 1900..2100 年当年的农历信息(闰月月份、大小月、闰月天数) const LUNAR_INFO = [ 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, 0x0b5a0, 0x195a6, 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 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, 0x0a2d0, 0x0d150, 0x0f252, 0x0d520, ]; // 农历月份名(正月..腊月) const LUNAR_MONTHS = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊']; // 农历日名(初一..三十) const LUNAR_DAYS = [ '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十', ]; // 农历节日:(月, 日) -> 名称 const LUNAR_FESTIVALS = { '1,1': '春节', '1,15': '元宵节', '2,2': '龙抬头', '5,5': '端午节', '7,7': '七夕', '7,15': '中元节', '8,15': '中秋节', '9,9': '重阳节', '12,8': '腊八节', '12,23': '北方小年', '12,24': '南方小年', }; // 公历节日:(月, 日) -> 名称 const SOLAR_FESTIVALS = { '1,1': '元旦', '2,14': '情人节', '3,8': '妇女节', '3,12': '植树节', '4,1': '愚人节', '5,1': '劳动节', '5,4': '青年节', '6,1': '儿童节', '7,1': '建党节', '8,1': '建军节', '9,10': '教师节', '10,1': '国庆节', '12,24': '平安夜', '12,25': '圣诞节', }; // 农历某年是否闰年 function leapMonth(y) { return LUNAR_INFO[y - 1900] & 0xf; } // 农历某年闰月天数(无闰月返回 0) function leapDays(y) { return leapMonth(y) ? ((LUNAR_INFO[y - 1900] & 0x10000) ? 30 : 29) : 0; } // 农历某年某月天数(m 为 1..12) function monthDays(y, m) { return (LUNAR_INFO[y - 1900] & (0x10000 >> m)) ? 30 : 29; } // 农历某年总天数 function lYearDays(y) { let sum = 348; for (let i = 0x8000; i > 0x8; i >>= 1) sum += (LUNAR_INFO[y - 1900] & i) ? 1 : 0; return sum + leapDays(y); } // 公历 Date -> 农历 { year, month, day, isLeap } function solar2lunar(date) { // 用 UTC 构造纯日期,消除时分秒与本地时区对天数差的影响 const base = Date.UTC(1900, 0, 31); // 1900-01-31 为农历庚子年正月初一 const target = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()); let offset = Math.round((target - base) / 86400000); let temp = 0; let year = 1900; for (; year < 2101 && offset > 0; year++) { temp = lYearDays(year); offset -= temp; } if (offset < 0) { offset += temp; year--; } const leap = leapMonth(year); let isLeap = false; let month; for (month = 1; month < 13 && offset > 0; month++) { if (leap > 0 && month === (leap + 1) && !isLeap) { --month; isLeap = true; temp = leapDays(year); } else { temp = monthDays(year, month); } if (isLeap && month === (leap + 1)) isLeap = false; offset -= temp; } if (offset === 0 && leap > 0 && month === leap + 1) { if (isLeap) { isLeap = false; } else { isLeap = true; --month; } } if (offset < 0) { offset += temp; --month; } return { year, month, day: offset + 1, isLeap }; } // 返回该月最后一天(用于判断除夕) function lastDayOfMonth(y, m) { return monthDays(y, m); } // 计算某公历日期对应的农历文字与节日 // date: Date 对象;返回 { lunarText, festival } export function getCalendarInfo(date) { const y = date.getFullYear(); const m = date.getMonth() + 1; // 公历 1..12 const d = date.getDate(); const lunar = solar2lunar(date); // 农历文字:如「九月廿二」,闰月加「闰」前缀 const lunarText = (lunar.isLeap ? '闰' : '') + LUNAR_MONTHS[lunar.month - 1] + '月' + LUNAR_DAYS[lunar.day - 1]; // 节日:先查农历节日,其次公历节日,均无则空 let festival = ''; const lkey = `${lunar.month},${lunar.day}`; if (LUNAR_FESTIVALS[lkey]) { festival = LUNAR_FESTIVALS[lkey]; } else if (lunar.month === 12 && lunar.day === lastDayOfMonth(lunar.year, 12)) { festival = '除夕'; // 腊月最后一天 } else { festival = SOLAR_FESTIVALS[`${m},${d}`] || ''; } return { lunarText, festival }; }