From c08f5aa4aadfcae5324b405be408ff2a5d279269 Mon Sep 17 00:00:00 2001 From: ddshi <8811906+ddshi@user.noreply.gitee.com> Date: Thu, 29 Jan 2026 17:24:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(P3):=20=E4=BF=AE=E5=A4=8D=E5=80=92=E8=AE=A1?= =?UTF-8?q?=E6=97=B6=E8=BE=B9=E7=95=8C=E9=97=AE=E9=A2=98=EF=BC=88=E9=97=B0?= =?UTF-8?q?=E5=B9=B4=E3=80=81=E6=9C=88=E6=9C=AB=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../anniversary/AnniversaryList.tsx | 2 +- src/utils/countdown.ts | 89 ++++++++++++++----- tmpclaude-05e0-cwd | 1 + 3 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 tmpclaude-05e0-cwd diff --git a/src/components/anniversary/AnniversaryList.tsx b/src/components/anniversary/AnniversaryList.tsx index 063c002..9b9e924 100644 --- a/src/components/anniversary/AnniversaryList.tsx +++ b/src/components/anniversary/AnniversaryList.tsx @@ -52,7 +52,7 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar title: h.name, date: h.date.toISOString(), is_holiday: true, - is_lunar: false, + is_lunar: h.isLunar, repeat_type: 'yearly', type: 'anniversary', is_builtin: true, diff --git a/src/utils/countdown.ts b/src/utils/countdown.ts index 49893cc..df7fb00 100644 --- a/src/utils/countdown.ts +++ b/src/utils/countdown.ts @@ -10,6 +10,18 @@ export interface CountdownResult { isPast: boolean; } +/** + * 安全地创建日期,处理月末边界情况 + */ +function safeCreateDate(year: number, month: number, day: number): Date { + const date = new Date(year, month, day); + // 如果日期溢出(如下个月),自动调整到月末 + if (date.getMonth() !== month % 12) { + date.setMonth(month + 1, 0); + } + return date; +} + /** * 计算纪念日的倒计时 * 考虑重复规则,计算下一个 occurrence 的倒计时 @@ -21,36 +33,38 @@ export function calculateCountdown( ): CountdownResult { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - let targetDate = new Date(dateStr); + let targetDate: Date; let isPast = false; - // 如果是农历日期,转换为公历 + // 解析日期字符串 + // 格式假设为 ISO 格式 "YYYY-MM-DD" 或完整的 ISO datetime + const dateParts = dateStr.split('T')[0].split('-').map(Number); + const originalYear = dateParts[0]; + const originalMonth = dateParts[1] - 1; // JavaScript月份从0开始 + const originalDay = dateParts[2]; + if (isLunar) { - const lunarDate = Lunar.fromYmd( - targetDate.getFullYear(), - targetDate.getMonth() + 1, - targetDate.getDate() - ); + // 农历日期:直接用原始农历年/月/日创建农历对象 + const lunarDate = Lunar.fromYmd(originalYear, originalMonth + 1, originalDay); const solar = lunarDate.getSolar(); targetDate = new Date(solar.getYear(), solar.getMonth() - 1, solar.getDay()); + } else { + // 公历日期 + targetDate = new Date(originalYear, originalMonth, originalDay); } // 计算下一个 occurrence if (repeatType === 'yearly') { // 年度重复:找到今年或明年的对应日期 - const thisYearTarget = new Date(today.getFullYear(), targetDate.getMonth(), targetDate.getDate()); - if (thisYearTarget >= today) { - targetDate = thisYearTarget; - } else { - targetDate = new Date(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate()); + targetDate = safeCreateDate(today.getFullYear(), targetDate.getMonth(), targetDate.getDate()); + if (targetDate < today) { + targetDate = safeCreateDate(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate()); } } else if (repeatType === 'monthly') { // 月度重复:找到本月或下月的对应日期 - const thisMonthTarget = new Date(today.getFullYear(), today.getMonth(), targetDate.getDate()); - if (thisMonthTarget >= today) { - targetDate = thisMonthTarget; - } else { - targetDate = new Date(today.getFullYear(), today.getMonth() + 1, targetDate.getDate()); + targetDate = safeCreateDate(today.getFullYear(), today.getMonth(), targetDate.getDate()); + if (targetDate < today) { + targetDate = safeCreateDate(today.getFullYear(), today.getMonth() + 1, targetDate.getDate()); } } else { // 不重复 @@ -67,13 +81,14 @@ export function calculateCountdown( isPast = true; } - const days = Math.max(0, Math.floor(diff / (1000 * 60 * 60 * 24))); - const hours = Math.max(0, Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))); - const minutes = Math.max(0, Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))); - const seconds = Math.max(0, Math.floor((diff % (1000 * 60)) / 1000)); + const absDiff = Math.abs(diff); + const days = Math.floor(absDiff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((absDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((absDiff % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((absDiff % (1000 * 60)) / 1000); return { - days, + days: isPast ? -days : days, hours, minutes, seconds, @@ -99,7 +114,11 @@ export function formatCountdown(countdown: CountdownResult): string { return `${countdown.days}天`; } - return `${countdown.hours}时${countdown.minutes}分`; + if (countdown.hours > 0) { + return `${countdown.hours}时${countdown.minutes}分`; + } + + return `${countdown.minutes}分${countdown.seconds}秒`; } /** @@ -150,3 +169,27 @@ export function getFriendlyDateDescription( return `${month}月${day}日`; } + +/** + * 获取农历日期的公历日期 + */ +export function getSolarFromLunar(lunarMonth: number, lunarDay: number, year?: number): Date { + const targetYear = year || new Date().getFullYear(); + const lunar = Lunar.fromYmd(targetYear, lunarMonth, lunarDay); + const solar = lunar.getSolar(); + return new Date(solar.getYear(), solar.getMonth() - 1, solar.getDay()); +} + +/** + * 获取公历日期的农历日期 + */ +export function getLunarFromSolar(solarDate: Date): { month: number; day: number; monthInChinese: string; dayInChinese: string } { + const solar = Solar.fromYmd(solarDate.getFullYear(), solarDate.getMonth() + 1, solarDate.getDate()); + const lunar = solar.getLunar(); + return { + month: lunar.getMonth(), + day: lunar.getDay(), + monthInChinese: lunar.getMonthInChinese(), + dayInChinese: lunar.getDayInChinese(), + }; +} diff --git a/tmpclaude-05e0-cwd b/tmpclaude-05e0-cwd new file mode 100644 index 0000000..094e142 --- /dev/null +++ b/tmpclaude-05e0-cwd @@ -0,0 +1 @@ +/e/qia/client