原算法在月循环外的 if (offset < 0) 分支根据 isLeap 重新判断加哪个月份天数, 但闰月期间的非初一日期会因为变量切换被错算到下一个普通月。 用 jjonline/calendar.js 的权威实现替换:循环内统一 offset -= temp, 退出循环后用保留的 temp 加回,简洁且正确。 修复验证: - 2023-03-22 → 闰二月初一 ✓(之前也对) - 2023-03-23 → 闰二月初二 ✓(之前错为「三月初二」) - 2023-04-19 → 闰二月廿九 ✓(之前错为「三月廿九」) - 2025-08-22 → 闰六月廿九 ✓(之前错为「七月廿九」) 维护手册新增踩坑 #13。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -232,6 +232,7 @@ git push
|
|||||||
10. **Gitea Actions 的 Secrets 改完不会自动触发流水线**——必须手动 re-run,或者推一个新 commit。
|
10. **Gitea Actions 的 Secrets 改完不会自动触发流水线**——必须手动 re-run,或者推一个新 commit。
|
||||||
11. **农历 `lunarInfo` 数据表来源要可信**。早期版本数据有错位(多个年份的闰月信息错),导致 solarToLunar 算的春节日期跟实际差 1-2 个月。已替换为权威源 `github.com/jjonline/calendar.js` 的版本。如未来要扩展年份范围(>2100),务必同样从该源取。
|
11. **农历 `lunarInfo` 数据表来源要可信**。早期版本数据有错位(多个年份的闰月信息错),导致 solarToLunar 算的春节日期跟实际差 1-2 个月。已替换为权威源 `github.com/jjonline/calendar.js` 的版本。如未来要扩展年份范围(>2100),务必同样从该源取。
|
||||||
12. **农历存字段必须含 `isLeapMonth`**。只存 `lunarMonth`/`lunarDay` 会丢闰月信息,闰月生日的人每年被错误提醒到普通月份。修复需同时改:数据库列、后端 FIELDS、前端 formData、`lunar.lunarToSolar` 第 4 参数透传。
|
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)做端到端验证,不能只测闰月初一。
|
||||||
|
|
||||||
## 与其他记忆的关系
|
## 与其他记忆的关系
|
||||||
|
|
||||||
|
|||||||
+43
-36
@@ -74,62 +74,69 @@ function _monthDays(year, month) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 公历转农历
|
* 公历转农历
|
||||||
|
* 算法移植自 github.com/jjonline/calendar.js(权威),与本地原版的差异在月循环退出时
|
||||||
|
* 必须保留 temp 保存最后一次月份天数,否则闰月非初一的日期会被错误地归到下一个普通月份。
|
||||||
* @param {Date} solarDate
|
* @param {Date} solarDate
|
||||||
* @returns {{ year, month, day, isLeap, lunarText }}
|
* @returns {{ year, month, day, isLeap, lunarText }}
|
||||||
*/
|
*/
|
||||||
function solarToLunar(solarDate) {
|
function solarToLunar(solarDate) {
|
||||||
const date = new Date(solarDate.getFullYear(), solarDate.getMonth(), solarDate.getDate())
|
const y = solarDate.getFullYear()
|
||||||
let offset = Math.round((date - BASE_DATE) / 86400000)
|
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
|
let isLeap = false
|
||||||
|
|
||||||
for (lunarYear = 1900; lunarYear < 2100 && offset > 0; lunarYear++) {
|
for (i = 1; i < 13 && offset > 0; i++) {
|
||||||
const days = _lunarYearDays(lunarYear)
|
if (leap > 0 && i === (leap + 1) && isLeap === false) {
|
||||||
offset -= days
|
--i
|
||||||
}
|
isLeap = true
|
||||||
if (offset < 0) {
|
temp = _leapDays(year)
|
||||||
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
|
|
||||||
} else {
|
} 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) {
|
// 闰月下标重叠导致 offset 恰好为 0 时的取反
|
||||||
if (isLeapYear) {
|
if (offset === 0 && leap > 0 && i === leap + 1) {
|
||||||
isLeapYear = false
|
if (isLeap) {
|
||||||
|
isLeap = false
|
||||||
} else {
|
} else {
|
||||||
isLeapYear = true
|
isLeap = true
|
||||||
--lunarMonth
|
--i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
offset += isLeapYear ? _leapDays(lunarYear) : _monthDays(lunarYear, lunarMonth)
|
offset += temp
|
||||||
if (isLeapYear) isLeapYear = false
|
--i
|
||||||
else --lunarMonth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lunarDay = offset + 1
|
const month = i
|
||||||
isLeap = isLeapYear
|
const day = offset + 1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
year: lunarYear,
|
year,
|
||||||
month: lunarMonth,
|
month,
|
||||||
day: lunarDay,
|
day,
|
||||||
isLeap,
|
isLeap,
|
||||||
lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(lunarMonth)}月${getLunarDayName(lunarDay)}`
|
lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(month)}月${getLunarDayName(day)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-36
@@ -74,62 +74,69 @@ function _monthDays(year, month) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 公历转农历
|
* 公历转农历
|
||||||
|
* 算法移植自 github.com/jjonline/calendar.js(权威),与本地原版的差异在月循环退出时
|
||||||
|
* 必须保留 temp 保存最后一次月份天数,否则闰月非初一的日期会被错误地归到下一个普通月份。
|
||||||
* @param {Date} solarDate
|
* @param {Date} solarDate
|
||||||
* @returns {{ year, month, day, isLeap, lunarText }}
|
* @returns {{ year, month, day, isLeap, lunarText }}
|
||||||
*/
|
*/
|
||||||
function solarToLunar(solarDate) {
|
function solarToLunar(solarDate) {
|
||||||
const date = new Date(solarDate.getFullYear(), solarDate.getMonth(), solarDate.getDate())
|
const y = solarDate.getFullYear()
|
||||||
let offset = Math.round((date - BASE_DATE) / 86400000)
|
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
|
let isLeap = false
|
||||||
|
|
||||||
for (lunarYear = 1900; lunarYear < 2100 && offset > 0; lunarYear++) {
|
for (i = 1; i < 13 && offset > 0; i++) {
|
||||||
const days = _lunarYearDays(lunarYear)
|
if (leap > 0 && i === (leap + 1) && isLeap === false) {
|
||||||
offset -= days
|
--i
|
||||||
}
|
isLeap = true
|
||||||
if (offset < 0) {
|
temp = _leapDays(year)
|
||||||
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
|
|
||||||
} else {
|
} 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) {
|
// 闰月下标重叠导致 offset 恰好为 0 时的取反
|
||||||
if (isLeapYear) {
|
if (offset === 0 && leap > 0 && i === leap + 1) {
|
||||||
isLeapYear = false
|
if (isLeap) {
|
||||||
|
isLeap = false
|
||||||
} else {
|
} else {
|
||||||
isLeapYear = true
|
isLeap = true
|
||||||
--lunarMonth
|
--i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
offset += isLeapYear ? _leapDays(lunarYear) : _monthDays(lunarYear, lunarMonth)
|
offset += temp
|
||||||
if (isLeapYear) isLeapYear = false
|
--i
|
||||||
else --lunarMonth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lunarDay = offset + 1
|
const month = i
|
||||||
isLeap = isLeapYear
|
const day = offset + 1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
year: lunarYear,
|
year,
|
||||||
month: lunarMonth,
|
month,
|
||||||
day: lunarDay,
|
day,
|
||||||
isLeap,
|
isLeap,
|
||||||
lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(lunarMonth)}月${getLunarDayName(lunarDay)}`
|
lunarText: `${isLeap ? '闰' : ''}${getLunarMonthName(month)}月${getLunarDayName(day)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user