首页老黄历全面升级 + 修复农历转换核心bug
Publish Mini Program Dev Version / publish (push) Successful in 2m8s

【重要修复】重写农历双向转换算法(原来全错):
- solarToLunar/lunarToSolar 改用标准算法(基准1900-01-31正月初一,UTC避免时区误差)
- 验证:2026春节2-17、中秋9-25、端午6-19、2023闰二月全部正确

【首页大日历】
- 日期数字加大到240rpx,超醒目
- 节假日红色喜庆卡片+红数字(春节/国庆/中秋等法定假日)
- 工作日绿色卡片+绿数字(一眼知道要上班)
- 周末红色数字+米黄卡片
- 节气显示具体时刻(如 立秋 20:29)
- 初一/十五金色标记(可在设置关闭)
- 佛历提醒:距下一佛诞还有几天(可在设置开启)

【全屏放大弹窗(老年友好)】
- 大日历卡片/宜忌/详情/节日速查 点击均可全屏放大
- 宜忌放大后字号44rpx,详情放大后40rpx

【月历视图】
- swiper 左右滑动换月,预计算5个月居中显示
- 滑到边缘自动补月,可无限滚动
- 点选日期切回日视图

【节日速查】
- 首页显示近期5个节日(中秋/重阳等)+公历日期+倒计时
- 点击直接跳到该日老黄历

【设置页】
- 新增'显示佛历提醒'开关(默认关)
- 新增'初一十五高亮'开关(默认开)
This commit is contained in:
gouki
2026-08-07 12:49:57 +00:00
parent 5d40019ea5
commit 82c1a8d694
11 changed files with 1636 additions and 425 deletions
+52
View File
@@ -101,6 +101,54 @@ const BUDDHIST_FESTIVALS = {
'11-17': '阿弥陀佛圣诞', '12-8': '释迦牟尼佛成道日(腊八)', '12-29': '华严菩萨圣诞'
};
/** 法定节假日(公历) */
const LEGAL_HOLIDAYS_SOLAR = {
'1-1': '元旦', '5-1': '劳动节', '10-1': '国庆节'
};
/** 法定节假日(农历) */
const LEGAL_HOLIDAYS_LUNAR = {
'1-1': '春节', '5-5': '端午节', '8-15': '中秋节', '12-30': '除夕'
};
/** 二十四节气大致时刻(用于显示,小时:分钟) */
const SOLAR_TERM_TIMES = {
'立春': '04:01', '雨水': '23:51', '惊蛰': '22:07', '春分': '23:06',
'清明': '03:20', '谷雨': '10:24', '立夏': '20:26', '小满': '09:23',
'芒种': '00:26', '夏至': '17:14', '小暑': '10:38', '大暑': '04:07',
'立秋': '20:29', '处暑': '11:16', '白露': '23:32', '秋分': '09:04',
'寒露': '15:22', '霜降': '18:36', '立冬': '18:45', '小雪': '16:20',
'大雪': '11:46', '冬至': '05:48', '小寒': '23:05', '大寒': '16:30'
};
/** 常用节日速查(名称 → 类型+月日) */
const FESTIVAL_QUICK_SEARCH = [
{ name: '春节', type: 'lunar', date: '1-1' },
{ name: '元宵节', type: 'lunar', date: '1-15' },
{ name: '龙抬头', type: 'lunar', date: '2-2' },
{ name: '端午节', type: 'lunar', date: '5-5' },
{ name: '七夕', type: 'lunar', date: '7-7' },
{ name: '中元节', type: 'lunar', date: '7-15' },
{ name: '中秋节', type: 'lunar', date: '8-15' },
{ name: '重阳节', type: 'lunar', date: '9-9' },
{ name: '腊八节', type: 'lunar', date: '12-8' },
{ name: '小年', type: 'lunar', date: '12-23' },
{ name: '除夕', type: 'lunar', date: '12-30' },
{ name: '元旦', type: 'solar', date: '1-1' },
{ name: '情人节', type: 'solar', date: '2-14' },
{ name: '妇女节', type: 'solar', date: '3-8' },
{ name: '植树节', type: 'solar', date: '3-12' },
{ name: '劳动节', type: 'solar', date: '5-1' },
{ name: '青年节', type: 'solar', date: '5-4' },
{ name: '儿童节', type: 'solar', date: '6-1' },
{ name: '建党节', type: 'solar', date: '7-1' },
{ name: '建军节', type: 'solar', date: '8-1' },
{ name: '教师节', type: 'solar', date: '9-10' },
{ name: '国庆节', type: 'solar', date: '10-1' },
{ name: '平安夜', type: 'solar', date: '12-24' },
{ name: '圣诞节', type: 'solar', date: '12-25' }
];
/** 纳音表 */
const NAYIN_TABLE = [
'海中金', '炉中火', '大林木', '路旁土', '剑锋金', '山头火',
@@ -370,6 +418,10 @@ module.exports = {
SOLAR_FESTIVALS,
LUNAR_FESTIVALS,
BUDDHIST_FESTIVALS,
LEGAL_HOLIDAYS_SOLAR,
LEGAL_HOLIDAYS_LUNAR,
SOLAR_TERM_TIMES,
FESTIVAL_QUICK_SEARCH,
NAYIN_TABLE,
HOUR_NAMES,
HOUR_RANGES,
+195 -83
View File
@@ -13,6 +13,10 @@ const {
SOLAR_FESTIVALS,
LUNAR_FESTIVALS,
BUDDHIST_FESTIVALS,
LEGAL_HOLIDAYS_SOLAR,
LEGAL_HOLIDAYS_LUNAR,
SOLAR_TERM_TIMES,
FESTIVAL_QUICK_SEARCH,
NAYIN_TABLE,
HOUR_NAMES,
HOUR_RANGES,
@@ -84,124 +88,107 @@ function getLunarMonthDays(year, month) {
return (LUNAR_INFO[year - 1900] & (0x10000 >> month)) ? 30 : 29;
}
/** 公历转农历 */
/** 公历转农历(标准算法,基准 1900-01-31 = 农历1900年正月初一) */
function solarToLunar(year, month, day) {
if (year < 1900 || year > 2100) return null;
let offset = 0;
for (let y = 1900; y < year; y++) {
offset += getLunarYearDays(y);
// 与基准日 1900-01-31(农历1900年正月初一)的天数差(用 UTC 避免时区误差)
const baseDate = Date.UTC(1900, 0, 31);
const objDate = Date.UTC(year, month - 1, day);
let offset = Math.floor((objDate - baseDate) / 86400000);
let lunarYear = 1900;
let daysOfYear = 0;
// 确定农历年
for (; lunarYear < 2101 && offset > 0; lunarYear++) {
daysOfYear = getLunarYearDays(lunarYear);
if (offset < daysOfYear) break;
offset -= daysOfYear;
}
let leapMonth = getLeapMonth(year);
const leapMonth = getLeapMonth(lunarYear);
let isLeap = false;
let lunarMonth = 1;
let lunarDay = 1;
let daysInMonth = 0;
let daysOfMonth = 0;
// 计算从正月初一到目标日期的天数
let targetOffset = offset;
for (let m = 1; m < month; m++) {
targetOffset += getSolarMonthDays(year, m);
}
targetOffset += day - 1;
// 农历年剩余天数
let daysInLunarYear = getLunarYearDays(year);
let remaining = targetOffset - offset;
if (remaining < 0 || remaining >= daysInLunarYear) {
// 需要调整年份
if (remaining < 0) {
year--;
remaining += getLunarYearDays(year);
} else {
remaining -= daysInLunarYear;
year++;
}
leapMonth = getLeapMonth(year);
}
// 逐月计算
for (let m = 1; m <= 12; m++) {
daysInMonth = getLunarMonthDays(year, m);
if (remaining < daysInMonth) {
lunarMonth = m;
lunarDay = remaining + 1;
break;
}
remaining -= daysInMonth;
// 处理闰月
if (m === leapMonth && !isLeap) {
m--;
// 确定农历月日
for (; lunarMonth < 13 && offset > 0; lunarMonth++) {
// 闰月
if (leapMonth > 0 && lunarMonth === leapMonth + 1 && !isLeap) {
lunarMonth--;
isLeap = true;
daysInMonth = getLeapMonthDays(year);
if (remaining < daysInMonth) {
lunarMonth = m + 1;
lunarDay = remaining + 1;
isLeap = true;
break;
}
remaining -= daysInMonth;
daysOfMonth = getLeapMonthDays(lunarYear);
} else {
daysOfMonth = getLunarMonthDays(lunarYear, lunarMonth);
}
if (offset < daysOfMonth) break;
offset -= daysOfMonth;
// 解除闰月标记
if (isLeap && lunarMonth === leapMonth + 1) isLeap = false;
}
// offset 为 0 且恰好是闰月边界
if (offset === 0 && leapMonth > 0 && lunarMonth === leapMonth + 1) {
if (isLeap) {
isLeap = false;
} else {
isLeap = true;
lunarMonth--;
}
}
if (offset < 0) {
offset += daysOfMonth;
lunarMonth--;
}
return {
year,
year: lunarYear,
month: lunarMonth,
day: lunarDay,
isLeap: isLeap && lunarMonth === leapMonth
day: offset + 1,
isLeap
};
}
/** 农历转公历 */
/** 农历转公历(标准算法,基准 1900-01-31 = 农历1900年正月初一) */
function lunarToSolar(year, month, day, isLeap = false) {
if (year < 1900 || year > 2100) return null;
// 累计从 1900 年正月初一到目标农历日期的天数
let offset = 0;
for (let y = 1900; y < year; y++) {
offset += getLunarYearDays(y);
}
const leapMonth = getLeapMonth(year);
let days = 0;
// 累计当年 1 月到目标月前一月的 days
for (let m = 1; m < month; m++) {
days += getLunarMonthDays(year, m);
if (m === leapMonth && !isLeap) {
days += getLeapMonthDays(year);
offset += getLunarMonthDays(year, m);
// 经过闰月(在 m 月之后)
if (m === leapMonth) {
offset += getLeapMonthDays(year);
}
}
// 目标月本身是闰月:需先加上该月正常月天数
if (isLeap && month === leapMonth) {
days += getLunarMonthDays(year, month);
offset += getLunarMonthDays(year, month);
}
days += day - 1;
offset += days;
offset += day - 1;
// 从1900年1月31日(农历正月初一)开始计算
let solarYear = 1900;
let solarMonth = 1;
let solarDay = 31;
let remaining = offset;
while (remaining > 0) {
const daysInMonth = getSolarMonthDays(solarYear, solarMonth);
if (remaining < daysInMonth) {
solarDay = remaining + 1;
break;
}
remaining -= daysInMonth;
solarMonth++;
if (solarMonth > 12) {
solarMonth = 1;
solarYear++;
}
}
return { year: solarYear, month: solarMonth, day: solarDay };
// 基准日 1900-01-31 + offset
const baseMs = Date.UTC(1900, 0, 31);
const target = new Date(baseMs + offset * 86400000);
return {
year: target.getUTCFullYear(),
month: target.getUTCMonth() + 1,
day: target.getUTCDate()
};
}
/** 获取日期的干支 */
@@ -1109,6 +1096,124 @@ function getBuddhistFestival(lunarMonth, lunarDay) {
return BUDDHIST_FESTIVALS[`${lunarMonth}-${lunarDay}`] || null;
}
/** 判断是否为法定节假日 */
function getLegalHoliday(year, month, day) {
const solarKey = `${month}-${day}`;
if (LEGAL_HOLIDAYS_SOLAR[solarKey]) return LEGAL_HOLIDAYS_SOLAR[solarKey];
const lunar = solarToLunar(year, month, day);
if (lunar) {
const lunarKey = `${lunar.month}-${lunar.day}`;
if (LEGAL_HOLIDAYS_LUNAR[lunarKey]) return LEGAL_HOLIDAYS_LUNAR[lunarKey];
}
return null;
}
/** 计算某天是星期几(0=周日) */
function getWeekDay(year, month, day) {
return new Date(year, month - 1, day).getDay();
}
/** 判断是否为工作日(周一到周五且非法定节假日) */
function isWorkday(year, month, day) {
if (getLegalHoliday(year, month, day)) return false;
const wd = getWeekDay(year, month, day);
return wd >= 1 && wd <= 5;
}
/** 农历转公历(某年) */
function lunarFestivalToSolar(year, lunarMonth, lunarDay) {
// 除夕特殊:腊月最后一天
if (lunarMonth === 12 && lunarDay === 30) {
const daysInMonth = getLunarMonthDays(year, 12);
const leap = getLeapMonth(year);
if (leap === 12) {
return lunarToSolar(year, 12, daysInMonth, true);
}
return lunarToSolar(year, 12, daysInMonth, false);
}
return lunarToSolar(year, lunarMonth, lunarDay, false);
}
/** 计算某农历节日在公历 year 年的日期 */
function getFestivalSolarDate(year, fest) {
if (fest.type === 'solar') {
const [m, d] = fest.date.split('-').map(Number);
return { year, month: m, day: d };
}
const [lm, ld] = fest.date.split('-').map(Number);
// 农历节日可能落在上一公历年(如春节在1-2月)
// 先按当前公历年算
let solar = lunarFestivalToSolar(year, lm, ld);
if (!solar) return null;
return solar;
}
/** 计算两个日期相差天数(target - from */
function daysBetween(fromY, fromM, fromD, toY, toM, toD) {
const a = new Date(fromY, fromM - 1, fromD);
const b = new Date(toY, toM - 1, toD);
return Math.round((b - a) / (24 * 60 * 60 * 1000));
}
/** 获取即将到来的 N 个节日(含今天之后的) */
function getUpcomingFestivals(count = 6) {
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth() + 1;
const d = now.getDate();
const list = [];
for (const fest of FESTIVAL_QUICK_SEARCH) {
// 尝试今年和明年
for (const tryYear of [y, y + 1]) {
const solar = getFestivalSolarDate(tryYear, fest);
if (!solar) continue;
const diff = daysBetween(y, m, d, solar.year, solar.month, solar.day);
if (diff >= 0) {
list.push({
name: fest.name,
solarDate: `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`,
solarYear: solar.year,
solarMonth: solar.month,
solarDay: solar.day,
daysLeft: diff
});
break;
}
}
}
list.sort((a, b) => a.daysLeft - b.daysLeft);
return list.slice(0, count);
}
/** 获取下一个佛教节日及倒计时 */
function getNextBuddhistFestival() {
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth() + 1;
const d = now.getDate();
const entries = Object.entries(BUDDHIST_FESTIVALS);
const list = [];
for (const [key, name] of entries) {
const [lm, ld] = key.split('-').map(Number);
for (const tryYear of [y, y + 1]) {
const solar = lunarFestivalToSolar(tryYear, lm, ld);
if (!solar) continue;
const diff = daysBetween(y, m, d, solar.year, solar.month, solar.day);
if (diff >= 0) {
list.push({ name, solarDate: `${solar.year}-${String(solar.month).padStart(2, '0')}-${String(solar.day).padStart(2, '0')}`, daysLeft: diff });
break;
}
}
}
list.sort((a, b) => a.daysLeft - b.daysLeft);
return list[0] || null;
}
/** 神煞分析 */
function analyzeShensha(bazi) {
const result = [];
@@ -1245,6 +1350,13 @@ module.exports = {
calculatePlumBlossom,
calculateBoneWeight,
getBuddhistFestival,
getLegalHoliday,
isWorkday,
getWeekDay,
getFestivalSolarDate,
getUpcomingFestivals,
getNextBuddhistFestival,
daysBetween,
analyzeShensha,
analyzeFortuneGanzhi,
getYearMonths,