如何基于自定义重复模式高效生成List<DateTime>类型的日期序列?
完整实现代码
public static class RepeatDateGenerator { // 辅助方法:转换自定义DAY枚举为系统DayOfWeek private static DayOfWeek ConvertToSystemDayOfWeek(DAY day) { return day switch { DAY.Sunday => DayOfWeek.Sunday, DAY.Monday => DayOfWeek.Monday, DAY.Tuesday => DayOfWeek.Tuesday, DAY.Wednesday => DayOfWeek.Wednesday, DAY.Thursday => DayOfWeek.Thursday, DAY.Friday => DayOfWeek.Friday, DAY.Saturday => DayOfWeek.Saturday, _ => throw new ArgumentOutOfRangeException(nameof(day)) }; } // 辅助方法:获取指定年月的逻辑目标日期(比如第二个周六、最后一个周一) private static DateTime GetLogicalDate(int year, int month, LOGICALSTART start, DAY targetDay) { var sysTargetDay = ConvertToSystemDayOfWeek(targetDay); var firstDayOfMonth = new DateTime(year, month, 1); int daysToAdd = (sysTargetDay - firstDayOfMonth.DayOfWeek + 7) % 7; var firstMatchDay = firstDayOfMonth.AddDays(daysToAdd); return start switch { LOGICALSTART.First => firstMatchDay, LOGICALSTART.Second => firstMatchDay.AddDays(7), LOGICALSTART.Third => firstMatchDay.AddDays(14), LOGICALSTART.Fourth => firstMatchDay.AddDays(21), LOGICALSTART.Last => // 取最后一个匹配的周几,先算下个月第一天,往前找 firstDayOfMonth.AddMonths(1).AddDays(-1) .AddDays(-((firstDayOfMonth.AddMonths(1).AddDays(-1).DayOfWeek - sysTargetDay + 7) % 7)), _ => throw new ArgumentOutOfRangeException(nameof(start)) }; } public static List<DateTime> GenerateUpcomingDates(this Repeat repeat, DateTime from, DateTime till) { List<DateTime> result = new List<DateTime>(); var current = from.Date; switch (repeat.PatternMode) { case PATTERNMODE.Day: while (current <= till) { result.Add(current); current = current.AddDays(repeat.PatternValue); } break; case PATTERNMODE.Week: // 优化原有逻辑,不用每天遍历,按周跳 var weekStep = repeat.PatternValue * 7; while (current <= till) { var weekStart = current.AddDays(-(int)current.DayOfWeek); for (int i = 0; i < 7; i++) { var day = weekStart.AddDays(i); if (day < from) continue; if (day > till) break; bool isMatch = day.DayOfWeek switch { DayOfWeek.Sunday => repeat.Sunday, DayOfWeek.Monday => repeat.Monday, DayOfWeek.Tuesday => repeat.Tuesday, DayOfWeek.Wednesday => repeat.Wednesday, DayOfWeek.Thursday => repeat.Thursday, DayOfWeek.Friday => repeat.Friday, DayOfWeek.Saturday => repeat.Saturday, _ => false }; if (isMatch) result.Add(day); } current = current.AddDays(weekStep); } break; case PATTERNMODE.Month: while (current <= till) { DateTime targetDate; if (repeat.Constrain == CONSTRAIN.DayOfMonth) { // 处理当月没有指定DayOfMonth的情况,取当月最后一天 int daysInMonth = DateTime.DaysInMonth(current.Year, current.Month); int targetDay = Math.Min(repeat.DayOfMonth, daysInMonth); targetDate = new DateTime(current.Year, current.Month, targetDay); } else // Logical模式 { targetDate = GetLogicalDate(current.Year, current.Month, repeat.LogicalStart, repeat.LogicalDay); } if (targetDate >= from && targetDate <= till) { result.Add(targetDate); } // 按PatternValue跳月,实现每N个月的逻辑 current = current.AddMonths(repeat.PatternValue); // 对齐到月初避免跨月误差 current = new DateTime(current.Year, current.Month, 1); } break; case PATTERNMODE.Year: while (current <= till) { DateTime targetDate; if (repeat.Constrain == CONSTRAIN.DayOfMonth) { int daysInMonth = DateTime.DaysInMonth(current.Year, current.Month); int targetDay = Math.Min(repeat.DayOfMonth, daysInMonth); targetDate = new DateTime(current.Year, current.Month, targetDay); } else { targetDate = GetLogicalDate(current.Year, current.Month, repeat.LogicalStart, repeat.LogicalDay); } if (targetDate >= from && targetDate <= till) { result.Add(targetDate); } // 按PatternValue跳年 current = current.AddYears(repeat.PatternValue); current = new DateTime(current.Year, current.Month, 1); } break; } // 去重+排序避免异常情况 return result.Distinct().OrderBy(d => d).ToList(); } }
关键逻辑说明
- 修复月模式按日生成的问题:原来的逻辑是每天遍历且没有按
PatternValue跳月,修正后直接按月步长跳转,同时处理了小月没有指定日期的边界情况,比如2月没有31号时自动取当月最后一天。 - 实现逻辑日期规则:通过
GetLogicalDate辅助方法计算每月第N个周几、最后一个周几的日期,适配「每月第二个周六」这类需求。 - 「每N个月/每年」逻辑:处理月/年模式时直接按
PatternValue跳对应的时间单位,不需要每天遍历,执行效率提升明显。 - 原有周模式优化:调整为按周步长遍历,减少循环次数。
可选效率提升方案(优先级低)
如果后续需要处理更复杂的重复规则,可以直接用成熟的Cron表达式解析库,比如NCrontab,直接把前端选择的规则转换为Cron表达式,再通过库生成符合要求的日期列表,不需要自行处理所有边界逻辑。
内容的提问来源于stack exchange,提问作者Alen Smith
相关产品推荐
相关产品推荐

