From f99cf575014d5c1510bf0bed880115c9295aa212 Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 18 May 2026 13:53:30 +0800 Subject: [PATCH] feat: Add calendar module --- aspnet-core/LINGYUN.MicroService.Aspire.slnx | 3 + .../LINGYUN.Abp.Calendar/FodyWeavers.xml | 3 + .../LINGYUN.Abp.Calendar.csproj | 21 ++++ .../LINGYUN/Abp/Calendar/AbpCalendarModule.cs | 9 ++ .../Abp/Calendar/AbpCalendarOptions.cs | 40 ++++++ .../Abp/Calendar/IWorkdayCalendarService.cs | 24 ++++ .../Abp/Calendar/IWorkdayCalendarStore.cs | 18 +++ .../Abp/Calendar/NullWorkdayCalendarStore.cs | 19 +++ .../LINGYUN/Abp/Calendar/SpecialDateInfo.cs | 21 ++++ .../LINGYUN/Abp/Calendar/SpecialDateType.cs | 24 ++++ .../LINGYUN/Abp/Calendar/WorkTimeRange.cs | 27 ++++ .../Abp/Calendar/WorkdayCalendarInfo.cs | 34 +++++ .../Abp/Calendar/WorkdayCalendarService.cs | 117 ++++++++++++++++++ .../calendars/LINGYUN.Abp.Calendar/README.md | 15 +++ 14 files changed, 375 insertions(+) create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/FodyWeavers.xml create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN.Abp.Calendar.csproj create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarModule.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarOptions.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarService.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarStore.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/NullWorkdayCalendarStore.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateInfo.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateType.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkTimeRange.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarInfo.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarService.cs create mode 100644 aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/README.md diff --git a/aspnet-core/LINGYUN.MicroService.Aspire.slnx b/aspnet-core/LINGYUN.MicroService.Aspire.slnx index 166412f51..a19022d70 100644 --- a/aspnet-core/LINGYUN.MicroService.Aspire.slnx +++ b/aspnet-core/LINGYUN.MicroService.Aspire.slnx @@ -90,6 +90,9 @@ + + + diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/FodyWeavers.xml b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN.Abp.Calendar.csproj b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN.Abp.Calendar.csproj new file mode 100644 index 000000000..489ab7fe2 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN.Abp.Calendar.csproj @@ -0,0 +1,21 @@ + + + + + + + net8.0;net9.0;net10.0 + LINGYUN.Abp.WorkdayCalendar + LINGYUN.Abp.WorkdayCalendar + false + false + false + enable + + + + + + + + diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarModule.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarModule.cs new file mode 100644 index 000000000..a52b9aadf --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; +using Volo.Abp.Timing; + +namespace LINGYUN.Abp.Calendar; + +[DependsOn(typeof(AbpTimingModule))] +public class AbpCalendarModule : AbpModule +{ +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarOptions.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarOptions.cs new file mode 100644 index 000000000..a4f0278d9 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/AbpCalendarOptions.cs @@ -0,0 +1,40 @@ +using System; + +namespace LINGYUN.Abp.Calendar; + +public class AbpCalendarOptions +{ + /// + /// 默认工作日范围 + /// 默认: 周一到周五 + /// + public DayOfWeek[] DefaultWorkdays { get; set; } + /// + /// 是否启用工作时间限制 + /// 默认: true + /// + public bool EnableWorkTimeRestriction { get; set; } + /// + /// 默认工作时间范围 + /// 默认: 09:00-12:00、13:00-18:00 + /// + public WorkTimeRange[] DefaultWorkTimes { get; set; } + + public AbpCalendarOptions() + { + DefaultWorkdays = new[] + { + DayOfWeek.Monday, + DayOfWeek.Tuesday, + DayOfWeek.Wednesday, + DayOfWeek.Thursday, + DayOfWeek.Friday + }; + EnableWorkTimeRestriction = true; + DefaultWorkTimes = new[] + { + new WorkTimeRange(new TimeSpan(9, 0, 0), new TimeSpan(12, 0, 0)), + new WorkTimeRange(new TimeSpan(13, 0, 0), new TimeSpan(18, 0, 0)), + }; + } +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarService.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarService.cs new file mode 100644 index 000000000..0e5d0c796 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarService.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Calendar; + +public interface IWorkdayCalendarService +{ + /// + /// 检查指定时间是否在工作日 + /// + Task IsWorkdayAsync(DateOnly date); + /// + /// 检查指定时间是否在工作时间 + /// + /// + /// + Task IsInWorkTimeAsync(DateTime dateTime); + /// + /// 获取下一个工作日 + /// + /// + /// + Task GetNextWorkdayAsync(DateOnly date); +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarStore.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarStore.cs new file mode 100644 index 000000000..202239c17 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/IWorkdayCalendarStore.cs @@ -0,0 +1,18 @@ +using System; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Calendar; + +public interface IWorkdayCalendarStore +{ + /// + /// 获取特殊日期 + /// + /// 日期 + /// + Task FindSpecialDateAsync(DateOnly date); + /// + /// 获取默认工作日历 + /// + Task FindDefaultCalendarAsync(); +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/NullWorkdayCalendarStore.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/NullWorkdayCalendarStore.cs new file mode 100644 index 000000000..2ddbb9f63 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/NullWorkdayCalendarStore.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Calendar; + +[Dependency(TryRegister = true)] +public class NullWorkdayCalendarStore : IWorkdayCalendarStore, ISingletonDependency +{ + public Task FindDefaultCalendarAsync() + { + return Task.FromResult(null); + } + + public Task FindSpecialDateAsync(DateOnly date) + { + return Task.FromResult(null); + } +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateInfo.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateInfo.cs new file mode 100644 index 000000000..686a90b7e --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateInfo.cs @@ -0,0 +1,21 @@ +using System; + +namespace LINGYUN.Abp.Calendar; +/// +/// 特殊日期 +/// +public class SpecialDateInfo +{ + /// + /// 日期 + /// + public DateOnly Date { get; set; } + /// + /// 特殊日期类型 + /// + public SpecialDateType Type { get; set; } + /// + /// 特殊日期描述 + /// + public string? Description { get; set; } +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateType.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateType.cs new file mode 100644 index 000000000..3b034a423 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/SpecialDateType.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; + +namespace LINGYUN.Abp.Calendar; +/// +/// 特殊日期类型 +/// +public enum SpecialDateType +{ + /// + /// 法定节假日 + /// + [Description("法定节假日")] + Holiday = 0, + /// + /// 特殊工作日 + /// + [Description("特殊工作日")] + SpecialWorkday = 1, + /// + /// 自定义假期 + /// + [Description("自定义假期")] + CustomHoliday = 2 +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkTimeRange.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkTimeRange.cs new file mode 100644 index 000000000..0ec86ab9a --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkTimeRange.cs @@ -0,0 +1,27 @@ +using System; + +namespace LINGYUN.Abp.Calendar; +/// +/// 工作时间范围 +/// +public class WorkTimeRange +{ + /// + /// 开始时间 + /// + public TimeSpan StartTime { get; set; } + /// + /// 截止时间 + /// + public TimeSpan EndTime { get; set; } + public WorkTimeRange() + { + + } + + public WorkTimeRange(TimeSpan startTime, TimeSpan endTime) + { + StartTime = startTime; + EndTime = endTime; + } +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarInfo.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarInfo.cs new file mode 100644 index 000000000..07ab09520 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarInfo.cs @@ -0,0 +1,34 @@ +using System; + +namespace LINGYUN.Abp.Calendar; + +/// +/// 工作日历信息 +/// +public class WorkdayCalendarInfo +{ + /// + /// 日历名称 + /// + public string Name { get; set; } + /// + /// 默认日历 + /// + public bool IsDefault { get; set; } + /// + /// 工作日范围 + /// + public DayOfWeek[] Workdays { get; set; } + /// + /// 工作时间范围 + /// + public WorkTimeRange[]? WorkTimes { get; set; } + /// + /// 是否启用工作时间限制 + /// + public bool EnableWorkTimeRestriction { get; set; } + /// + /// 特殊日期列表 + /// + public SpecialDateInfo[]? SpecialDates { get; set; } +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarService.cs b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarService.cs new file mode 100644 index 000000000..e63ff3150 --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/LINGYUN/Abp/Calendar/WorkdayCalendarService.cs @@ -0,0 +1,117 @@ +using Microsoft.Extensions.Options; +using System; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace LINGYUN.Abp.Calendar; + +public class WorkdayCalendarService : IWorkdayCalendarService, ITransientDependency +{ + private readonly AbpCalendarOptions _options; + private readonly IWorkdayCalendarStore _workdayCalendarStore; + + public WorkdayCalendarService( + IOptions options, + IWorkdayCalendarStore workdayCalendarStore) + { + _options = options.Value; + _workdayCalendarStore = workdayCalendarStore; + } + + public async virtual Task GetNextWorkdayAsync(DateOnly date) + { + var nextDay = date.AddDays(1); + // 以31天为限 + var maxAttempts = 30; + + for (var i = 0; i < maxAttempts; i++) + { + if (await IsWorkdayAsync(nextDay)) + { + return nextDay; + } + nextDay = nextDay.AddDays(1); + } + + // 如果30天内都找不到工作日,返回原日期+1天 + return date.AddDays(1); + } + + public async virtual Task IsWorkdayAsync(DateOnly date) + { + // 特殊工作日定义 + var specialDate = await _workdayCalendarStore.FindSpecialDateAsync(date); + if (specialDate != null) + { + return specialDate.Type == SpecialDateType.SpecialWorkday; + } + // 获取默认工作日历定义 + var calendar = await _workdayCalendarStore.FindDefaultCalendarAsync(); + if (calendar == null) + { + // 未定义默认工作日历, 获取系统定义工作日范围 + return _options.DefaultWorkdays.Contains(date.DayOfWeek); + } + // 特殊工作日范围 + if (calendar.SpecialDates != null && + calendar.SpecialDates.Any(x => x.Date == date && x.Type == SpecialDateType.SpecialWorkday)) + { + return true; + } + // 工作日范围 + if (!calendar.Workdays.Contains(date.DayOfWeek)) + { + return false; + } + + return false; + } + + public async virtual Task IsInWorkTimeAsync(DateTime time) + { + var nowDate = DateOnly.FromDateTime(time); + // 特殊工作日定义 + var specialDate = await _workdayCalendarStore.FindSpecialDateAsync(nowDate); + if (specialDate != null) + { + return specialDate.Type == SpecialDateType.SpecialWorkday; + } + // 获取默认工作日历定义 + var calendar = await _workdayCalendarStore.FindDefaultCalendarAsync(); + if (calendar == null) + { + // 未定义默认工作日历, 获取系统定义工作日范围 + if (!_options.DefaultWorkdays.Contains(nowDate.DayOfWeek)) + { + return false; + } + if (_options.EnableWorkTimeRestriction && + !_options.DefaultWorkTimes.Any(timeRange => timeRange.StartTime >= time.TimeOfDay && timeRange.EndTime <= time.TimeOfDay)) + { + return false; + } + + return true; + } + // 特殊工作日范围 + if (calendar.SpecialDates != null && + calendar.SpecialDates.Any(x => x.Date == nowDate && x.Type == SpecialDateType.SpecialWorkday)) + { + return true; + } + // 工作日范围 + if (!calendar.Workdays.Contains(nowDate.DayOfWeek)) + { + return false; + } + // 工作时间范围 + if (calendar.EnableWorkTimeRestriction && + calendar.WorkTimes != null) + { + return calendar.WorkTimes.Any(x => x.StartTime >= time.TimeOfDay && x.EndTime <= time.TimeOfDay); + } + + return false; + } +} diff --git a/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/README.md b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/README.md new file mode 100644 index 000000000..3bb94652b --- /dev/null +++ b/aspnet-core/framework/calendars/LINGYUN.Abp.Calendar/README.md @@ -0,0 +1,15 @@ +# LINGYUN.Abp.Calendar + +日历模块, 定义系统日历参数, 用于特定场景. + +#### 注意 + + +## 功能特性 + + +## 配置使用 + + +## 更多信息 +