首页老黄历全面升级 + 修复农历转换核心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
+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,