diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 8217b7d..fbf9b43 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -232,6 +232,7 @@ git push 10. **Gitea Actions 的 Secrets 改完不会自动触发流水线**——必须手动 re-run,或者推一个新 commit。 11. **农历 `lunarInfo` 数据表来源要可信**。早期版本数据有错位(多个年份的闰月信息错),导致 solarToLunar 算的春节日期跟实际差 1-2 个月。已替换为权威源 `github.com/jjonline/calendar.js` 的版本。如未来要扩展年份范围(>2100),务必同样从该源取。 12. **农历存字段必须含 `isLeapMonth`**。只存 `lunarMonth`/`lunarDay` 会丢闰月信息,闰月生日的人每年被错误提醒到普通月份。修复需同时改:数据库列、后端 FIELDS、前端 formData、`lunar.lunarToSolar` 第 4 参数透传。 +13. **`solarToLunar` 月循环必须保留 `temp` 变量**。早期版本在闰月分支里 `offset -= leapDays`、非闰分支 `offset -= monthDays`,循环外的 `if (offset < 0)` 又根据 isLeap 重新判断要加哪个——闰月期间的"非初一日期"会被错算到下一个普通月(例如「闰二月初二」算成「三月初二」)。权威实现(jjonline/calendar.js)让循环内统一 `offset -= temp` 并把 `temp` 保留到循环外用,是更稳的写法。修改算法时一定要用 *闰月连续日期*(如 2023-03-22 到 2023-04-19)做端到端验证,不能只测闰月初一。 ## 与其他记忆的关系 diff --git a/server/src/lunar.js b/server/src/lunar.js index cd86e11..bd771a5 100644 --- a/server/src/lunar.js +++ b/server/src/lunar.js @@ -74,62 +74,69 @@ function _monthDays(year, month) { /** * 公历转农历 + * 算法移植自 github.com/jjonline/calendar.js(权威),与本地原版的差异在月循环退出时 + * 必须保留 temp 保存最后一次月份天数,否则闰月非初一的日期会被错误地归到下一个普通月份。 * @param {Date} solarDate * @returns {{ year, month, day, isLeap, lunarText }} */ function solarToLunar(solarDate) { - const date = new Date(solarDate.getFullYear(), solarDate.getMonth(), solarDate.getDate()) - let offset = Math.round((date - BASE_DATE) / 86400000) + const y = solarDate.getFullYear() + const m = solarDate.getMonth() + 1 + const d = solarDate.getDate() - let lunarYear, lunarMonth, lunarDay + let offset = (Date.UTC(y, m - 1, d) - Date.UTC(1900, 0, 31)) / 86400000 + let i, leap = 0, temp = 0 + + for (i = 1900; i < 2101 && offset > 0; i++) { + temp = _lunarYearDays(i) + offset -= temp + } + if (offset < 0) { + offset += temp + i-- + } + + const year = i + leap = _leapMonth(i) let isLeap = false - for (lunarYear = 1900; lunarYear < 2100 && offset > 0; lunarYear++) { - const days = _lunarYearDays(lunarYear) - offset -= days - } - if (offset < 0) { - offset += _lunarYearDays(--lunarYear) - } - - const leapM = _leapMonth(lunarYear) - let isLeapYear = false - - for (lunarMonth = 1; lunarMonth < 13 && offset > 0; lunarMonth++) { - if (leapM > 0 && lunarMonth === leapM + 1 && !isLeapYear) { - --lunarMonth - isLeapYear = true - const leapDays = _leapDays(lunarYear) - offset -= leapDays + for (i = 1; i < 13 && offset > 0; i++) { + if (leap > 0 && i === (leap + 1) && isLeap === false) { + --i + isLeap = true + temp = _leapDays(year) } else { - offset -= _monthDays(lunarYear, lunarMonth) + temp = _monthDays(year, i) } - if (isLeapYear && lunarMonth === leapM + 1) isLeapYear = false + if (isLeap === true && i === (leap + 1)) { + isLeap = false + } + offset -= temp } - if (offset === 0 && leapM > 0 && lunarMonth === leapM + 1) { - if (isLeapYear) { - isLeapYear = false + // 闰月下标重叠导致 offset 恰好为 0 时的取反 + if (offset === 0 && leap > 0 && i === leap + 1) { + if (isLeap) { + isLeap = false } else { - isLeapYear = true - --lunarMonth + isLeap = true + --i } } if (offset < 0) { - offset += isLeapYear ? _leapDays(lunarYear) : _monthDays(lunarYear, lunarMonth) - if (isLeapYear) isLeapYear = false - else --lunarMonth + offset += temp + --i } - lunarDay = offset + 1 - isLeap = isLeapYear + const month = i + const day = offset + 1 return { - year: lunarYear, - month: lunarMonth, - day: lunarDay, + year, + month, + day, isLeap, - lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(lunarMonth)}月${getLunarDayName(lunarDay)}` + lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(month)}月${getLunarDayName(day)}` } } diff --git a/utils/lunar.js b/utils/lunar.js index cd86e11..bd771a5 100644 --- a/utils/lunar.js +++ b/utils/lunar.js @@ -74,62 +74,69 @@ function _monthDays(year, month) { /** * 公历转农历 + * 算法移植自 github.com/jjonline/calendar.js(权威),与本地原版的差异在月循环退出时 + * 必须保留 temp 保存最后一次月份天数,否则闰月非初一的日期会被错误地归到下一个普通月份。 * @param {Date} solarDate * @returns {{ year, month, day, isLeap, lunarText }} */ function solarToLunar(solarDate) { - const date = new Date(solarDate.getFullYear(), solarDate.getMonth(), solarDate.getDate()) - let offset = Math.round((date - BASE_DATE) / 86400000) + const y = solarDate.getFullYear() + const m = solarDate.getMonth() + 1 + const d = solarDate.getDate() - let lunarYear, lunarMonth, lunarDay + let offset = (Date.UTC(y, m - 1, d) - Date.UTC(1900, 0, 31)) / 86400000 + let i, leap = 0, temp = 0 + + for (i = 1900; i < 2101 && offset > 0; i++) { + temp = _lunarYearDays(i) + offset -= temp + } + if (offset < 0) { + offset += temp + i-- + } + + const year = i + leap = _leapMonth(i) let isLeap = false - for (lunarYear = 1900; lunarYear < 2100 && offset > 0; lunarYear++) { - const days = _lunarYearDays(lunarYear) - offset -= days - } - if (offset < 0) { - offset += _lunarYearDays(--lunarYear) - } - - const leapM = _leapMonth(lunarYear) - let isLeapYear = false - - for (lunarMonth = 1; lunarMonth < 13 && offset > 0; lunarMonth++) { - if (leapM > 0 && lunarMonth === leapM + 1 && !isLeapYear) { - --lunarMonth - isLeapYear = true - const leapDays = _leapDays(lunarYear) - offset -= leapDays + for (i = 1; i < 13 && offset > 0; i++) { + if (leap > 0 && i === (leap + 1) && isLeap === false) { + --i + isLeap = true + temp = _leapDays(year) } else { - offset -= _monthDays(lunarYear, lunarMonth) + temp = _monthDays(year, i) } - if (isLeapYear && lunarMonth === leapM + 1) isLeapYear = false + if (isLeap === true && i === (leap + 1)) { + isLeap = false + } + offset -= temp } - if (offset === 0 && leapM > 0 && lunarMonth === leapM + 1) { - if (isLeapYear) { - isLeapYear = false + // 闰月下标重叠导致 offset 恰好为 0 时的取反 + if (offset === 0 && leap > 0 && i === leap + 1) { + if (isLeap) { + isLeap = false } else { - isLeapYear = true - --lunarMonth + isLeap = true + --i } } if (offset < 0) { - offset += isLeapYear ? _leapDays(lunarYear) : _monthDays(lunarYear, lunarMonth) - if (isLeapYear) isLeapYear = false - else --lunarMonth + offset += temp + --i } - lunarDay = offset + 1 - isLeap = isLeapYear + const month = i + const day = offset + 1 return { - year: lunarYear, - month: lunarMonth, - day: lunarDay, + year, + month, + day, isLeap, - lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(lunarMonth)}月${getLunarDayName(lunarDay)}` + lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(month)}月${getLunarDayName(day)}` } }