修复 lunarToSolar 系统性少 1 天的 bug
部署到群晖 / deploy (push) Successful in 42s

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) <noreply@anthropic.com>
This commit is contained in:
yuming
2026-06-02 06:34:00 +08:00
parent 320209a390
commit 59ed635dcf
2 changed files with 24 additions and 22 deletions
+12 -11
View File
@@ -141,12 +141,11 @@ function solarToLunar(solarDate) {
} }
/** /**
* 农历转公历(指定年份) * 农历转公历(指定年份)。算法贴近 jjonline/calendar.js 权威实现。
* @param {Number} lunarYear * 关键修正:早期版用 `new Date(1900, 0, 31)`(本地时间)作为 BASE,在 1900 年早期日期
* @param {Number} lunarMonth * 上某些 JS 引擎有时区偏差,导致每个农历月初对应公历都少 1 天。改用 UTC 时间戳后
* @param {Number} lunarDay * 再构造本地 Date 对象,避免歧义。
* @param {Boolean} isLeap 是否闰月 * @returns {Date} 本地视角下的公历日期(getDate 取出来的是该农历日对应的公历日)
* @returns {Date}
*/ */
function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) {
let offset = 0 let offset = 0
@@ -156,12 +155,12 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) {
} }
const leapM = _leapMonth(lunarYear) const leapM = _leapMonth(lunarYear)
let hasLeap = false let isAdd = false
for (let m = 1; m < lunarMonth; m++) { for (let m = 1; m < lunarMonth; m++) {
if (leapM > 0 && m === leapM && !hasLeap) { if (!isAdd && leapM > 0 && leapM <= m) {
offset += _leapDays(lunarYear) offset += _leapDays(lunarYear)
hasLeap = true isAdd = true
} }
offset += _monthDays(lunarYear, m) offset += _monthDays(lunarYear, m)
} }
@@ -172,8 +171,10 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) {
offset += lunarDay - 1 offset += lunarDay - 1
const result = new Date(BASE_DATE.getTime() + offset * 86400000) // 用 UTC 计算时间戳避免早期年份时区歧义;再用 UTC 字段构造本地 Date 对象
return result const utc = Date.UTC(1900, 0, 31) + offset * 86400000
const d = new Date(utc)
return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())
} }
/** /**
+12 -11
View File
@@ -141,12 +141,11 @@ function solarToLunar(solarDate) {
} }
/** /**
* 农历转公历(指定年份) * 农历转公历(指定年份)。算法贴近 jjonline/calendar.js 权威实现。
* @param {Number} lunarYear * 关键修正:早期版用 `new Date(1900, 0, 31)`(本地时间)作为 BASE,在 1900 年早期日期
* @param {Number} lunarMonth * 上某些 JS 引擎有时区偏差,导致每个农历月初对应公历都少 1 天。改用 UTC 时间戳后
* @param {Number} lunarDay * 再构造本地 Date 对象,避免歧义。
* @param {Boolean} isLeap 是否闰月 * @returns {Date} 本地视角下的公历日期(getDate 取出来的是该农历日对应的公历日)
* @returns {Date}
*/ */
function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) { function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) {
let offset = 0 let offset = 0
@@ -156,12 +155,12 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) {
} }
const leapM = _leapMonth(lunarYear) const leapM = _leapMonth(lunarYear)
let hasLeap = false let isAdd = false
for (let m = 1; m < lunarMonth; m++) { for (let m = 1; m < lunarMonth; m++) {
if (leapM > 0 && m === leapM && !hasLeap) { if (!isAdd && leapM > 0 && leapM <= m) {
offset += _leapDays(lunarYear) offset += _leapDays(lunarYear)
hasLeap = true isAdd = true
} }
offset += _monthDays(lunarYear, m) offset += _monthDays(lunarYear, m)
} }
@@ -172,8 +171,10 @@ function lunarToSolar(lunarYear, lunarMonth, lunarDay, isLeap) {
offset += lunarDay - 1 offset += lunarDay - 1
const result = new Date(BASE_DATE.getTime() + offset * 86400000) // 用 UTC 计算时间戳避免早期年份时区歧义;再用 UTC 字段构造本地 Date 对象
return result const utc = Date.UTC(1900, 0, 31) + offset * 86400000
const d = new Date(utc)
return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())
} }
/** /**