using System; using System.Collections.Generic; using System.Linq; namespace NPP.SmartSchedue.Api.Contracts.Domain.Common { /// /// 日期范围类 /// 智能排班系统中的时间区间管理,支持工作日计算、工时统计和时间跨度分析 /// 深度业务思考:提供完整的时间范围操作,支持复杂的排班业务场景 /// public class DateRange { #region 属性定义 /// /// 开始日期 /// public DateTime StartDate { get; set; } /// /// 结束日期 /// public DateTime EndDate { get; set; } /// /// 时间跨度 /// 计算开始日期和结束日期之间的时间差 /// public TimeSpan Duration => EndDate.Date - StartDate.Date; /// /// 总天数(包含开始和结束日期) /// public int TotalDays => Duration.Days + 1; /// /// 纯粹的天数差(不包含结束日期) /// public int DurationDays => Duration.Days; /// /// 工作日天数 /// 排除周末和节假日的实际工作天数 /// public int WorkingDays => CalculateWorkingDays(); /// /// 自然工作日天数 /// 仅排除周末,不考虑节假日 /// public int NaturalWorkingDays => CalculateNaturalWorkingDays(); /// /// 周末天数 /// public int WeekendDays => CalculateWeekendDays(); /// /// 节假日天数 /// public int HolidayDays => CalculateHolidayDays(); /// /// 是否为有效的日期范围 /// public bool IsValid => StartDate <= EndDate; /// /// 是否为单日范围 /// public bool IsSingleDay => StartDate.Date == EndDate.Date; /// /// 是否跨越多个月份 /// public bool SpansMultipleMonths => StartDate.Month != EndDate.Month || StartDate.Year != EndDate.Year; /// /// 是否跨越多个年份 /// public bool SpansMultipleYears => StartDate.Year != EndDate.Year; #endregion #region 构造函数 /// /// 默认构造函数 /// public DateRange() { StartDate = DateTime.Today; EndDate = DateTime.Today; } /// /// 指定开始和结束日期的构造函数 /// /// 开始日期 /// 结束日期 public DateRange(DateTime startDate, DateTime endDate) { StartDate = startDate.Date; EndDate = endDate.Date; if (!IsValid) { throw new ArgumentException("结束日期不能早于开始日期"); } } /// /// 从指定日期开始,持续指定天数的构造函数 /// /// 开始日期 /// 持续天数 public DateRange(DateTime startDate, int days) { if (days < 0) { throw new ArgumentException("天数不能为负数"); } StartDate = startDate.Date; EndDate = startDate.Date.AddDays(days - 1); } #endregion #region 工时计算方法 /// /// 计算标准工时 /// 基于每天8小时的标准工作时间 /// /// 每日标准工时,默认8小时 /// 总标准工时 public decimal CalculateStandardHours(decimal dailyStandardHours = 8.0m) { return WorkingDays * dailyStandardHours; } /// /// 计算自然日标准工时 /// 基于总天数计算,不考虑工作日 /// /// 每日标准工时,默认8小时 /// 总标准工时 public decimal CalculateNaturalStandardHours(decimal dailyStandardHours = 8.0m) { return TotalDays * dailyStandardHours; } /// /// 计算工作日工时 /// 仅计算工作日的工时,排除周末和节假日 /// /// 每个工作日的工时,默认8小时 /// 工作日总工时 public decimal CalculateWorkingHours(decimal dailyWorkingHours = 8.0m) { return WorkingDays * dailyWorkingHours; } /// /// 计算可用工时 /// 考虑具体的工作时间配置 /// /// 工作时间配置 /// 可用总工时 public decimal CalculateAvailableHours(WorkingTimeConfiguration workingTimeConfig = null) { if (workingTimeConfig == null) { return CalculateWorkingHours(); } decimal totalHours = 0; var currentDate = StartDate; while (currentDate <= EndDate) { if (IsWorkingDay(currentDate)) { totalHours += workingTimeConfig.GetDailyWorkingHours(currentDate); } currentDate = currentDate.AddDays(1); } return totalHours; } #endregion #region 日期判断方法 /// /// 判断指定日期是否在范围内 /// /// 要判断的日期 /// 是否在范围内 public bool Contains(DateTime date) { var checkDate = date.Date; return checkDate >= StartDate && checkDate <= EndDate; } /// /// 判断是否与另一个日期范围重叠 /// /// 另一个日期范围 /// 是否重叠 public bool Overlaps(DateRange other) { if (other == null) return false; return StartDate <= other.EndDate && EndDate >= other.StartDate; } /// /// 判断是否完全包含另一个日期范围 /// /// 另一个日期范围 /// 是否完全包含 public bool Contains(DateRange other) { if (other == null) return false; return StartDate <= other.StartDate && EndDate >= other.EndDate; } /// /// 判断指定日期是否为工作日 /// /// 要判断的日期 /// 是否为工作日 public bool IsWorkingDay(DateTime date) { // 排除周末 if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday) { return false; } // 排除节假日 return !IsHoliday(date); } /// /// 判断指定日期是否为节假日 /// /// 要判断的日期 /// 是否为节假日 public bool IsHoliday(DateTime date) { // 这里可以集成具体的节假日数据源 // 暂时返回false,实际项目中需要根据具体需求实现 return HolidayProvider.IsHoliday(date); } #endregion #region 范围操作方法 /// /// 获取与另一个日期范围的交集 /// /// 另一个日期范围 /// 交集范围,如果没有交集则返回null public DateRange Intersect(DateRange other) { if (other == null || !Overlaps(other)) { return null; } var intersectStart = StartDate > other.StartDate ? StartDate : other.StartDate; var intersectEnd = EndDate < other.EndDate ? EndDate : other.EndDate; return new DateRange(intersectStart, intersectEnd); } /// /// 获取与另一个日期范围的并集 /// /// 另一个日期范围 /// 并集范围 public DateRange Union(DateRange other) { if (other == null) { return new DateRange(StartDate, EndDate); } var unionStart = StartDate < other.StartDate ? StartDate : other.StartDate; var unionEnd = EndDate > other.EndDate ? EndDate : other.EndDate; return new DateRange(unionStart, unionEnd); } /// /// 扩展日期范围 /// /// 向前扩展的天数 /// 向后扩展的天数 /// 扩展后的日期范围 public DateRange Extend(int daysBefore, int daysAfter) { return new DateRange( StartDate.AddDays(-daysBefore), EndDate.AddDays(daysAfter) ); } /// /// 拆分日期范围为指定天数的子范围 /// /// 每个子范围的天数 /// 子范围列表 public List Split(int chunkSize) { if (chunkSize <= 0) { throw new ArgumentException("块大小必须大于0"); } var chunks = new List(); var currentStart = StartDate; while (currentStart <= EndDate) { var currentEnd = currentStart.AddDays(chunkSize - 1); if (currentEnd > EndDate) { currentEnd = EndDate; } chunks.Add(new DateRange(currentStart, currentEnd)); currentStart = currentEnd.AddDays(1); } return chunks; } #endregion #region 日期枚举方法 /// /// 获取范围内的所有日期 /// /// 日期列表 public List GetAllDates() { var dates = new List(); var currentDate = StartDate; while (currentDate <= EndDate) { dates.Add(currentDate); currentDate = currentDate.AddDays(1); } return dates; } /// /// 获取范围内的所有工作日 /// /// 工作日列表 public List GetWorkingDays() { return GetAllDates().Where(IsWorkingDay).ToList(); } /// /// 获取范围内的所有周末 /// /// 周末日期列表 public List GetWeekends() { return GetAllDates().Where(date => date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday).ToList(); } /// /// 获取范围内的所有节假日 /// /// 节假日列表 public List GetHolidays() { return GetAllDates().Where(IsHoliday).ToList(); } #endregion #region 私有计算方法 /// /// 计算工作日天数 /// private int CalculateWorkingDays() { return GetWorkingDays().Count; } /// /// 计算自然工作日天数(仅排除周末) /// private int CalculateNaturalWorkingDays() { var workDays = 0; var currentDate = StartDate; while (currentDate <= EndDate) { if (currentDate.DayOfWeek != DayOfWeek.Saturday && currentDate.DayOfWeek != DayOfWeek.Sunday) { workDays++; } currentDate = currentDate.AddDays(1); } return workDays; } /// /// 计算周末天数 /// private int CalculateWeekendDays() { return GetWeekends().Count; } /// /// 计算节假日天数 /// private int CalculateHolidayDays() { return GetHolidays().Count; } #endregion #region 静态工厂方法 /// /// 创建今天的日期范围 /// public static DateRange Today() { return new DateRange(DateTime.Today, DateTime.Today); } /// /// 创建本周的日期范围 /// public static DateRange ThisWeek() { var today = DateTime.Today; var startOfWeek = today.AddDays(-(int)today.DayOfWeek); var endOfWeek = startOfWeek.AddDays(6); return new DateRange(startOfWeek, endOfWeek); } /// /// 创建本月的日期范围 /// public static DateRange ThisMonth() { var today = DateTime.Today; var startOfMonth = new DateTime(today.Year, today.Month, 1); var endOfMonth = startOfMonth.AddMonths(1).AddDays(-1); return new DateRange(startOfMonth, endOfMonth); } /// /// 创建本年的日期范围 /// public static DateRange ThisYear() { var today = DateTime.Today; var startOfYear = new DateTime(today.Year, 1, 1); var endOfYear = new DateTime(today.Year, 12, 31); return new DateRange(startOfYear, endOfYear); } /// /// 创建指定月份的日期范围 /// public static DateRange ForMonth(int year, int month) { var startOfMonth = new DateTime(year, month, 1); var endOfMonth = startOfMonth.AddMonths(1).AddDays(-1); return new DateRange(startOfMonth, endOfMonth); } /// /// 创建指定年份的日期范围 /// public static DateRange ForYear(int year) { var startOfYear = new DateTime(year, 1, 1); var endOfYear = new DateTime(year, 12, 31); return new DateRange(startOfYear, endOfYear); } #endregion #region 重写方法 /// /// 重写ToString方法 /// public override string ToString() { if (IsSingleDay) { return StartDate.ToString("yyyy-MM-dd"); } return $"{StartDate:yyyy-MM-dd} ~ {EndDate:yyyy-MM-dd} ({TotalDays}天)"; } /// /// 重写Equals方法 /// public override bool Equals(object obj) { if (obj is DateRange other) { return StartDate == other.StartDate && EndDate == other.EndDate; } return false; } /// /// 重写GetHashCode方法 /// public override int GetHashCode() { return HashCode.Combine(StartDate, EndDate); } #endregion } #region 支持类定义 /// /// 工作时间配置 /// public class WorkingTimeConfiguration { /// /// 标准每日工作时间 /// public decimal StandardDailyHours { get; set; } = 8.0m; /// /// 特殊日期工作时间配置 /// Key: 日期, Value: 工作时间 /// public Dictionary SpecialDayHours { get; set; } = new(); /// /// 获取指定日期的工作时间 /// public decimal GetDailyWorkingHours(DateTime date) { return SpecialDayHours.GetValueOrDefault(date.Date, StandardDailyHours); } } /// /// 节假日提供者 /// public static class HolidayProvider { /// /// 节假日缓存 /// private static readonly HashSet _holidays = new(); /// /// 判断是否为节假日 /// public static bool IsHoliday(DateTime date) { // 这里可以集成具体的节假日数据源 // 例如:国家法定节假日API、企业自定义节假日等 return _holidays.Contains(date.Date); } /// /// 添加节假日 /// public static void AddHoliday(DateTime date) { _holidays.Add(date.Date); } /// /// 批量添加节假日 /// public static void AddHolidays(IEnumerable dates) { foreach (var date in dates) { _holidays.Add(date.Date); } } /// /// 移除节假日 /// public static void RemoveHoliday(DateTime date) { _holidays.Remove(date.Date); } /// /// 清空所有节假日 /// public static void ClearHolidays() { _holidays.Clear(); } } #endregion }