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