fix(P3): 修复倒计时边界问题(闰年、月末)

This commit is contained in:
ddshi 2026-01-29 17:24:53 +08:00
parent 82c291ef30
commit c08f5aa4aa
3 changed files with 68 additions and 24 deletions

View File

@ -52,7 +52,7 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
title: h.name, title: h.name,
date: h.date.toISOString(), date: h.date.toISOString(),
is_holiday: true, is_holiday: true,
is_lunar: false, is_lunar: h.isLunar,
repeat_type: 'yearly', repeat_type: 'yearly',
type: 'anniversary', type: 'anniversary',
is_builtin: true, is_builtin: true,

View File

@ -10,6 +10,18 @@ export interface CountdownResult {
isPast: boolean; 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 * occurrence
@ -21,36 +33,38 @@ export function calculateCountdown(
): CountdownResult { ): CountdownResult {
const now = new Date(); const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
let targetDate = new Date(dateStr); let targetDate: Date;
let isPast = false; 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) { if (isLunar) {
const lunarDate = Lunar.fromYmd( // 农历日期:直接用原始农历年/月/日创建农历对象
targetDate.getFullYear(), const lunarDate = Lunar.fromYmd(originalYear, originalMonth + 1, originalDay);
targetDate.getMonth() + 1,
targetDate.getDate()
);
const solar = lunarDate.getSolar(); const solar = lunarDate.getSolar();
targetDate = new Date(solar.getYear(), solar.getMonth() - 1, solar.getDay()); targetDate = new Date(solar.getYear(), solar.getMonth() - 1, solar.getDay());
} else {
// 公历日期
targetDate = new Date(originalYear, originalMonth, originalDay);
} }
// 计算下一个 occurrence // 计算下一个 occurrence
if (repeatType === 'yearly') { if (repeatType === 'yearly') {
// 年度重复:找到今年或明年的对应日期 // 年度重复:找到今年或明年的对应日期
const thisYearTarget = new Date(today.getFullYear(), targetDate.getMonth(), targetDate.getDate()); targetDate = safeCreateDate(today.getFullYear(), targetDate.getMonth(), targetDate.getDate());
if (thisYearTarget >= today) { if (targetDate < today) {
targetDate = thisYearTarget; targetDate = safeCreateDate(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate());
} else {
targetDate = new Date(today.getFullYear() + 1, targetDate.getMonth(), targetDate.getDate());
} }
} else if (repeatType === 'monthly') { } else if (repeatType === 'monthly') {
// 月度重复:找到本月或下月的对应日期 // 月度重复:找到本月或下月的对应日期
const thisMonthTarget = new Date(today.getFullYear(), today.getMonth(), targetDate.getDate()); targetDate = safeCreateDate(today.getFullYear(), today.getMonth(), targetDate.getDate());
if (thisMonthTarget >= today) { if (targetDate < today) {
targetDate = thisMonthTarget; targetDate = safeCreateDate(today.getFullYear(), today.getMonth() + 1, targetDate.getDate());
} else {
targetDate = new Date(today.getFullYear(), today.getMonth() + 1, targetDate.getDate());
} }
} else { } else {
// 不重复 // 不重复
@ -67,13 +81,14 @@ export function calculateCountdown(
isPast = true; isPast = true;
} }
const days = Math.max(0, Math.floor(diff / (1000 * 60 * 60 * 24))); const absDiff = Math.abs(diff);
const hours = Math.max(0, Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))); const days = Math.floor(absDiff / (1000 * 60 * 60 * 24));
const minutes = Math.max(0, Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))); const hours = Math.floor((absDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const seconds = Math.max(0, Math.floor((diff % (1000 * 60)) / 1000)); const minutes = Math.floor((absDiff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((absDiff % (1000 * 60)) / 1000);
return { return {
days, days: isPast ? -days : days,
hours, hours,
minutes, minutes,
seconds, seconds,
@ -99,7 +114,11 @@ export function formatCountdown(countdown: CountdownResult): string {
return `${countdown.days}`; 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}`; 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(),
};
}

1
tmpclaude-05e0-cwd Normal file
View File

@ -0,0 +1 @@
/e/qia/client