diff --git a/server/src/lunar.js b/server/src/lunar.js index bd771a5..a2a07b8 100644 --- a/server/src/lunar.js +++ b/server/src/lunar.js @@ -141,12 +141,11 @@ function solarToLunar(solarDate) { } /** - * 农历转公历(指定年份) - * @param {Number} lunarYear - * @param {Number} lunarMonth - * @param {Number} lunarDay - * @param {Boolean} isLeap 是否闰月 - * @returns {Date} + * 农历转公历(指定年份)。算法贴近 jjonline/calendar.js 权威实现。 + * 关键修正:早期版用 `new Date(1900, 0, 31)`(本地时间)作为 BASE,在 1900 年早期日期 + * 上某些 JS 引擎有时区偏差,导致每个农历月初对应公历都少 1 天。改用 UTC 时间戳后 + * 再构造本地 Date 对象,避免歧义。 + * @returns {Date} 本地视角下的公历日期(getDate 取出来的是该农历日对应的公历日) */ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { let offset = 0 @@ -156,12 +155,12 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { } const leapM = _leapMonth(lunarYear) - let hasLeap = false + let isAdd = false for (let m = 1; m < lunarMonth; m++) { - if (leapM > 0 && m === leapM && !hasLeap) { + if (!isAdd && leapM > 0 && leapM <= m) { offset += _leapDays(lunarYear) - hasLeap = true + isAdd = true } offset += _monthDays(lunarYear, m) } @@ -172,8 +171,10 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { offset += lunarDay - 1 - const result = new Date(BASE_DATE.getTime() + offset * 86400000) - return result + // 用 UTC 计算时间戳避免早期年份时区歧义;再用 UTC 字段构造本地 Date 对象 + const utc = Date.UTC(1900, 0, 31) + offset * 86400000 + const d = new Date(utc) + return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) } /** diff --git a/utils/lunar.js b/utils/lunar.js index bd771a5..a2a07b8 100644 --- a/utils/lunar.js +++ b/utils/lunar.js @@ -141,12 +141,11 @@ function solarToLunar(solarDate) { } /** - * 农历转公历(指定年份) - * @param {Number} lunarYear - * @param {Number} lunarMonth - * @param {Number} lunarDay - * @param {Boolean} isLeap 是否闰月 - * @returns {Date} + * 农历转公历(指定年份)。算法贴近 jjonline/calendar.js 权威实现。 + * 关键修正:早期版用 `new Date(1900, 0, 31)`(本地时间)作为 BASE,在 1900 年早期日期 + * 上某些 JS 引擎有时区偏差,导致每个农历月初对应公历都少 1 天。改用 UTC 时间戳后 + * 再构造本地 Date 对象,避免歧义。 + * @returns {Date} 本地视角下的公历日期(getDate 取出来的是该农历日对应的公历日) */ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { let offset = 0 @@ -156,12 +155,12 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { } const leapM = _leapMonth(lunarYear) - let hasLeap = false + let isAdd = false for (let m = 1; m < lunarMonth; m++) { - if (leapM > 0 && m === leapM && !hasLeap) { + if (!isAdd && leapM > 0 && leapM <= m) { offset += _leapDays(lunarYear) - hasLeap = true + isAdd = true } offset += _monthDays(lunarYear, m) } @@ -172,8 +171,10 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { offset += lunarDay - 1 - const result = new Date(BASE_DATE.getTime() + offset * 86400000) - return result + // 用 UTC 计算时间戳避免早期年份时区歧义;再用 UTC 字段构造本地 Date 对象 + const utc = Date.UTC(1900, 0, 31) + offset * 86400000 + const d = new Date(utc) + return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()) } /**