Asoka.Wang 21f044712c 1
2025-08-27 18:39:19 +08:00

632 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}