基于Outlook Graph API的重复日程生成逻辑问题排查
重复日程生成代码的逻辑错误分析
从Microsoft Outlook Graph API获取重复日程数据时,生成重复日期的代码在部分场景下完全失效,比如给定每周一至周五、时间范围2023-10-26至2023-11-10的重复模式。以下是对代码逻辑错误的分析:
重复模式示例
{ "recurrence": { "pattern": { "type": "weekly", "interval": 1, "month": 0, "dayOfMonth": 0, "daysOfWeek": ["monday", "tuesday", "wednesday", "thursday", "friday"], "firstDayOfWeek": "sunday", "index": "first" }, "range": { "type": "endDate", "startDate": "2023-10-26T00:00:00.000Z", "endDate": "2023-11-10T23:59:00.000Z", "recurrenceTimeZone": "Romance Standard Time", "numberOfOccurrences": 0 } } }
代码实现
主函数:generateRecurringDates
export const generateRecurringDates = (appointment, end_date, start_date) => { const { pattern, range } = appointment.recurrence; const startDate = new Date(range.startDate); let endDate; let endTime; if(range.type == 'noEnd'){ endDate = new Date(end_date); endTime ='23:59:00.000Z' }else{ endDate = new Date(range.endDate); endTime =endDate.toISOString().split('T')[1] } const recurringDates = []; let currentDate = startDate; let startTime =currentDate.toISOString().split('T')[1] let end; if(endDate >= new Date(end_date)){ end = new Date(end_date) }else{ end = endDate } let start; let added = true; if(startDate >= new Date(start_date)){ currentDate = start = startDate }else{ added = false currentDate = start = new Date(start_date) } while (currentDate <= end) { if(pattern?.daysOfWeek?.length > 0){ pattern?.daysOfWeek?.map((dayName) => { var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; if(pattern.type == 'relativeMonthly'){ recurringDates.push({ start: currentDate.toISOString().split('T')[0], end: currentDate.toISOString().split('T')[0], title: appointment.title }); }else{ if(pattern?.daysOfWeek?.includes(days[currentDate.getDay()].toString()) && currentDate <= end){ recurringDates.push({ start: currentDate.toISOString().split('T')[0], end: currentDate.toISOString().split('T')[0], title: appointment.title }); } } currentDate = adjustDate(currentDate, pattern,dayName); } ); }else{ if(currentDate <= end){ if(pattern.dayOfMonth > 0){ if(pattern.month > 0 && (pattern.month == (currentDate.getMonth()+1)) && pattern.dayOfMonth == currentDate.getDate()){ recurringDates.push({ start: currentDate.toISOString().split('T')[0], end: currentDate.toISOString().split('T')[0] }); }else if(pattern.month == 0 && pattern.dayOfMonth == currentDate.getDate()){ recurringDates.push({ start: currentDate.toISOString().split('T')[0], end: currentDate.toISOString().split('T')[0] }); } }else{ recurringDates.push({ start: currentDate.toISOString().split('T')[0], end: currentDate.toISOString().split('T')[0] }); } } currentDate = adjustDate(currentDate, pattern); } } return recurringDates; }
辅助函数:adjustDate
function adjustDate(date, pattern, dayName ='') { let newDate = new Date(date); switch (pattern.type) { case "daily": newDate.setDate(newDate.getDate() + pattern.interval); break; case "weekly": if (pattern.interval == 1) { let days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; } if (dayName != '') { var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; var day = days.indexOf(dayName.toLowerCase()); let addDay = ((day + 7 - newDate.getDay()) % 7); if(addDay == 0){ newDate.setDate(newDate.getDate() +1) }else{ newDate.setDate(newDate.getDate() + (((day + 7 - newDate.getDay()) % 7) || 7)) } } else { newDate.setDate(newDate.getDate() + pattern.interval * 7); } break; case "absoluteMonthly": if (pattern.dayOfMonth) { const currentMonth = newDate.getMonth(); const targetDayOfMonth = pattern.dayOfMonth; if (targetDayOfMonth > newDate.getDate()) { newDate.setDate(targetDayOfMonth); } else { newDate.setMonth(currentMonth + 1); newDate.setDate(targetDayOfMonth); } } else { newDate.setMonth(newDate.getMonth() + pattern.interval); } break; case "relativeMonthly": if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) { var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; var day = days.indexOf(dayName.toLowerCase()); var nextOccurrence = findNthDayOfMonth(newDate.getFullYear(), newDate.getMonth(), day, pattern.index); newDate = new Date(nextOccurrence); } else { newDate.setMonth(newDate.getMonth() + pattern.interval); } break; case "absoluteYearly": if (pattern.dayOfMonth > 0) { const targetDate = new Date(newDate); targetDate.setMonth(pattern.month - 1); targetDate.setDate(pattern.dayOfMonth); if (targetDate <= newDate) { newDate.setFullYear(newDate.getFullYear() + pattern.interval); newDate.setMonth(pattern.month - 1); newDate.setDate(pattern.dayOfMonth); } else { newDate.setMonth(pattern.month - 1); newDate.setDate(pattern.dayOfMonth); } } else { newDate.setFullYear(newDate.getFullYear() + pattern.interval); newDate.setMonth(pattern.month - 1); newDate.setDate(1); while (newDate.getDay() !== 0) { newDate.setDate(newDate.getDate() + 1); } newDate.setDate(newDate.getDate() + (pattern.interval - 1) * 7); } break; case "relativeYearly": if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) { var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; var day = days.indexOf(dayName.toLowerCase()); var nextOccurrence = findNthDayOfMonth(newDate.getFullYear() + pattern.interval, pattern.month - 1, day, pattern.index); newDate = new Date(nextOccurrence); } else { newDate.setFullYear(newDate.getFullYear() + pattern.interval); } break; default: break; } return newDate; }
逻辑错误分析
1. 每周重复模式的日期调整逻辑错误
在adjustDate的weekly分支中,当传入dayName时,日期计算逻辑存在致命问题:
- 当
currentDate已经是目标星期几(即addDay === 0)时,代码会强制加1天,直接跳过当前符合条件的日期; - 计算
addDay的冗余逻辑(((day + 7 - newDate.getDay()) % 7) || 7)完全多余,因为取模结果已经是0-6的有效值,无需额外处理。
2. while循环与map遍历的逻辑冲突
在generateRecurringDates的while循环内,对pattern.daysOfWeek使用map遍历:
- 每次遍历都会调用
adjustDate修改currentDate,导致currentDate在一次while循环中连续跳转到5个不同的日期(周一到周五); while循环的条件是currentDate <= end,后续循环会直接处理已经跳过的日期范围,导致大量日期遗漏或重复生成。
3. 日期范围处理的冗余与错误
added变量定义后未被使用,属于无效代码;- 当
startDate < start_date时,直接将currentDate设为start_date,但未检查该日期是否属于daysOfWeek指定的星期,会生成不符合要求的日期; - 完全忽略了
range.recurrenceTimeZone时区参数,直接使用本地时间解析日期,导致时区转换错误,生成的日期与Outlook中的实际日程不符。
4. 重复日期添加的条件判断错误
在map遍历daysOfWeek时,判断条件pattern?.daysOfWeek?.includes(days[currentDate.getDay()].toString())完全多余:
- 因为当前遍历的
dayName本身就是daysOfWeek中的元素,且adjustDate已经将currentDate调整为目标星期几; - 该判断会导致当
currentDate因调整逻辑出错时,错误跳过日期添加,进一步加剧问题。
5. 未处理firstDayOfWeek参数
Outlook重复模式中的firstDayOfWeek字段定义了每周的起始日,但代码完全未考虑该参数:
- 比如当
firstDayOfWeek为sunday时,每周的重复周期应从周日开始计算,但代码的星期索引逻辑未结合该参数调整,导致跨周的重复日程计算错误。
内容的提问来源于stack exchange,提问作者COdeingNinja
相关产品推荐
相关产品推荐

