From 59ed635dcf34e62b0589c9cce674ffd57f98fde9 Mon Sep 17 00:00:00 2001 From: yuming Date: Tue, 2 Jun 2026 06:34:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20lunarToSolar=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E6=80=A7=E5=B0=91=201=20=E5=A4=A9=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BASE_DATE 用 new Date(1900, 0, 31)(本地时间)在 1900 年早期日期上某些 JS 引擎有时区偏差,导致每个农历月初对应公历都比真实少 1 天。改用 Date.UTC(1900, 0, 31) 算时间戳,再用 UTC 字段重新构造本地 Date 对象, 彻底避免歧义。 同时月循环改用 `leapM <= m` 写法(与权威 jjonline/calendar.js 一致), 和 solarToLunar 保持完美互逆。 修复验证(互逆 9 个用例全 OK): - 2026-02-17 ↔ 正月初一 - 2026-06-09 ↔ 农历四月廿四(用户实测发现的差 1 天) - 2023-03-22 ↔ 闰二月初一 - 2023-04-19 ↔ 闰二月廿九 - 2025-07-25 ↔ 闰六月初一 - 2025-08-22 ↔ 闰六月廿九 Co-Authored-By: Claude Opus 4.7 (1M context) --- server/src/lunar.js | 23 ++++++++++++----------- utils/lunar.js | 23 ++++++++++++----------- 2 files changed, 24 insertions(+), 22 deletions(-) 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()) } /**