Files
youlegames/codes/minipro/calculation/miniprogram/utils/lunar.ts

311 lines
9.9 KiB
TypeScript

/**
* 农历(阴历)工具库
* 支持 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<string, string> = {
'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<string, string> = {
'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;
}