Refactor Date Calculator UI to Morandi theme and fix logic; fix Index page interaction
This commit is contained in:
310
codes/minipro/calculation/miniprogram/utils/lunar.ts
Normal file
310
codes/minipro/calculation/miniprogram/utils/lunar.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 农历(阴历)工具库
|
||||
* 支持 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;
|
||||
}
|
||||
Reference in New Issue
Block a user