fix(P3): 修复倒计时边界问题(闰年、月末)
This commit is contained in:
parent
82c291ef30
commit
c08f5aa4aa
@ -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,
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
}
|
||||
|
||||
1
tmpclaude-05e0-cwd
Normal file
1
tmpclaude-05e0-cwd
Normal file
@ -0,0 +1 @@
|
||||
/e/qia/client
|
||||
Loading…
x
Reference in New Issue
Block a user