Falcon.SugarApi/Falcon.SugarApi/TimedBackgroundTask/CronExpression.cs
2025-02-20 09:39:04 +08:00

235 lines
9.1 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 Falcon.SugarApi;
using Falcon.SugarApi.TimedBackgroundTask;
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Cron表达式
/// </summary>
public class CronExpression
{
/// <summary>
/// 可选的秒范围枚举
/// </summary>
private List<int> Seconds { get; }
/// <summary>
/// 可选的分范围枚举
/// </summary>
private List<int> Minutes { get; }
/// <summary>
/// 可选的时范围枚举
/// </summary>
private List<int> Hours { get; }
/// <summary>
/// 可选的天范围枚举
/// </summary>
private List<int> DaysOfMonth { get; }
/// <summary>
/// 可选的月范围枚举
/// </summary>
private List<int> Months { get; }
/// <summary>
/// 可选的星期范围枚举
/// </summary>
private List<int> DaysOfWeek { get; }
/// <summary>
/// 可选的年范围枚举
/// </summary>
private List<int> Years { get; set; }
/// <summary>
/// 通过提供cron表达式构造对象
/// <para>表达式以空格分隔,按顺序每一段分别为“秒,分,时,日,月,星期,年”</para>
/// <para>取值范围秒分时取值范围0-59日取值范围1-31月取值范围1-12星期取值范围0-6年取值范围两年内</para>
/// <para>取值*表示可以匹配任意值,比如*表示每一秒,* *表示所有分秒。</para>
/// <para>取值-表示取值范围比如1-5 3匹配3分1秒到3分5秒所有时间。</para>
/// <para>取值/表示周期取值比如0/10表示0秒开始每10秒匹配一次。</para>
/// <para>取值可以用分割表示取值枚举比如0 10,20,30 5 表示匹配每天早上5点10分、5点20分和5点30分。</para>
/// <para>表达式右侧的*可以省略。比如0 15 4表示每天上午4点15分和0 15 4 * * * *相同</para>
/// <para>星期取值范围0-60表示星期天1表示星期一以此类推。</para>
/// <para>日取值范围1-31不用考虑大月小月和二月的特殊情况方法会自动过滤这些特殊日期</para>
/// </summary>
/// <param name="cronExpression">cron表达式</param>
/// <exception cref="ArgumentException"></exception>
public CronExpression(string cronExpression) {
var fields = cronExpression.Split(new[] { ' ' },StringSplitOptions.RemoveEmptyEntries);
Seconds = fields.Length > 0 ? GetRange(fields[0],0,59) : Enumerable.Range(0,60).ToList();
Minutes = fields.Length > 1 ? GetRange(fields[1],0,59) : Enumerable.Range(0,60).ToList();
Hours = fields.Length > 2 ? GetRange(fields[2],0,23) : Enumerable.Range(0,23).ToList();
DaysOfMonth = fields.Length > 3 ? GetRange(fields[3],1,31) : Enumerable.Range(1,31).ToList();
Months = fields.Length > 4 ? GetRange(fields[4],1,12) : Enumerable.Range(1,12).ToList();
DaysOfWeek = fields.Length > 5 ? GetRange(fields[5],0,6) : Enumerable.Range(0,7).ToList();
var nowYear = DateTime.Now.Year;
Years = fields.Length > 6 ? GetRange(fields[6],nowYear,nowYear + 2) : Enumerable.Range(nowYear,2).ToList();
}
/// <summary>
/// 通过提供的时间获取下一次时间
/// </summary>
/// <param name="afterTime">上一次的时间</param>
/// <returns>下一次到达时间</returns>
public DateTime GetNextOccurrence(DateTime afterTime) {
var ct = new CronResult(afterTime.AddSeconds(1));
while(!ct.IsAllAdjust) {
if(!ct.IsYearAdjust) {
AdjustYear(ct);
}
if(!ct.IsMonthAdjust) {
AdjustMonth(ct);
if(!ct.IsYearAdjust) {
continue;
}
}
if(!ct.IsDayAdjust) {
AdjustDay(ct);
if(!ct.IsYearAdjust || !ct.IsMonthAdjust || !ct.IsDayAdjust) {
continue;
}
}
if(!ct.IsHourAdjust) {
AdjustHour(ct);
if(!ct.IsYearAdjust || !ct.IsMonthAdjust || !ct.IsDayAdjust) {
continue;
}
}
if(!ct.IsMinuteAdjust) {
AdjustMinute(ct);
if(!ct.IsYearAdjust || !ct.IsMonthAdjust || !ct.IsDayAdjust || !ct.IsHourAdjust) {
continue;
}
}
if(!ct.IsSecondAdjust) {
AdjustSecond(ct);
}
}
return ct.AdjustTime;
}
private void AdjustYear(CronResult date) {
var next = Years.Where(a => a >= date.AdjustTime.Year);
if(next.Any()) {
date.SetAdjustTime(date.AdjustTime.AddYears(next.First() - date.AdjustTime.Year),TimePartEnum.Year);
date.IsYearAdjust = true;
}
else {
date.IsNullVal = true;
date.IsYearAdjust = true;
date.IsMonthAdjust = true;
date.IsDayAdjust = true;
date.IsHourAdjust = true;
date.IsMinuteAdjust = true;
date.IsSecondAdjust = true;
date.AdjustTime = DateTime.MaxValue;
}
}
private void AdjustMonth(CronResult date) {
var next = Months.Where(m => m >= date.AdjustTime.Month);
if(next.Any()) {
date.SetAdjustTime(date.AdjustTime.AddMonths(next.First() - date.AdjustTime.Month),TimePartEnum.YearMonth);
date.IsMonthAdjust = true;
}
else {
date.SetAdjustTime(date.AdjustTime.AddYears(1),TimePartEnum.YearMonth);
}
}
private void AdjustDay(CronResult date) {
var dt = date.AdjustTime;
int year = dt.Year;
int month = dt.Month;
int day = dt.Day;
int maxDayInMonth = DateTime.DaysInMonth(year,month);
var next = DaysOfMonth.Where(a => a >= day && a <= maxDayInMonth && DaysOfWeek.Contains((int)dt.DayOfWeek));
if(next.Any()) {
date.SetAdjustTime(dt.AddDays(next.First() - day),TimePartEnum.YearDay);
date.IsDayAdjust = true;
return;
}
if(!DaysOfWeek.Contains((int)dt.DayOfWeek)) {
date.SetAdjustTime(dt.AddDays(1),TimePartEnum.YearDay);
return;
}
date.SetAdjustTime(dt.AddMonths(1),TimePartEnum.YearMonth);
}
private void AdjustHour(CronResult date) {
var next = Hours.Where(m => m >= date.AdjustTime.Hour);
if(next.Any()) {
date.SetAdjustTime(date.AdjustTime.AddHours(next.First() - date.AdjustTime.Hour),TimePartEnum.YearHour);
date.IsHourAdjust = true;
return;
}
date.SetAdjustTime(date.AdjustTime.AddDays(1),TimePartEnum.YearHour);
}
private void AdjustMinute(CronResult date) {
var next = Minutes.Where(m => m >= date.AdjustTime.Minute);
if(next.Any()) {
date.SetAdjustTime(date.AdjustTime.AddMinutes(next.First() - date.AdjustTime.Minute),TimePartEnum.YearMinute);
date.IsMinuteAdjust = true;
return;
}
date.SetAdjustTime(date.AdjustTime.AddHours(1),TimePartEnum.YearMinute);
}
private void AdjustSecond(CronResult date) {
var next = Seconds.Where(m => m >= date.AdjustTime.Second);
if(next.Any()) {
date.SetAdjustTime(date.AdjustTime.AddSeconds(next.First() - date.AdjustTime.Second),TimePartEnum.YearSecond);
date.IsSecondAdjust = true;
return;
}
date.SetAdjustTime(date.AdjustTime.AddMinutes(1),TimePartEnum.YearSecond);
}
private static List<int> GetRange(string exp,int min,int max) {
var charList = "0123456789*-/,".ToArray();
if(exp.ToCharArray().Any(a => a.NotIn(charList))) {
throw new ArgumentOutOfRangeException(nameof(exp));
}
var list = new List<int>();
if(exp == "*") {
return Enumerable.Range(min,max - min + 1).ToList();
}
if(int.TryParse(exp,out int iexp) && iexp.Between(min,max)) {
list.Add(iexp);
return list;
}
if(exp.Contains('-')) {
var g = exp.Split('-',StringSplitOptions.RemoveEmptyEntries);
var i = int.Parse(g[0]);
var x = int.Parse(g[1]);
i = Math.Max(i,min);
x = Math.Min(x,max);
while(i <= x) {
list.Add(i++);
}
return list;
}
if(exp.Contains('/')) {
var g = exp.Split('/',StringSplitOptions.RemoveEmptyEntries);
var f = Math.Max(min,int.Parse(g[0]));
var s = int.Parse(g[1]);
while(f < max) {
list.Add(f);
f += s;
}
return list;
}
if(exp.Contains(',')) {
foreach(var ie in exp.Split(',')) {
if(int.TryParse(ie,out int iei) && iei.Between(min,max)) {
list.Add(iei);
}
else {
throw new ArgumentException("给定的exp表达式错误逗号分割的每一项都必须是数字",nameof(exp));
}
}
return list;
}
throw new ArgumentException("提供的cron表达式错误",nameof(exp));
}
}