Skip to content

26.2 Cron 表达式

📝 模块更新日志 新特性*

+ 新增 `Crontab.IsValid(...)` 静态方法,判断 `Cron` 表达式是否有效 4\.8\.7\.17 ⏱️2023\.03\.20 [\#I6OHO4](https://gitee.com/dotnetchina/Furion/issues/I6OHO4)
+ 新增 `crontab.GetSleepTimeSpan(baseTime)` 实例方法 4\.8\.4\.10 ⏱️2023\.01\.09 [\#I69HM4](https://gitee.com/dotnetchina/Furion/issues/I69HM4)
+ 新增 `Crontab.ParseAt(..)` 静态方法 4\.8\.2\.6 ⏱️2022\.11\.30 [035cc23](https://gitee.com/dotnetchina/Furion/commit/035cc23a20045e9673a1406b94e72030f5f18375)
+ 新增 `Crontab` 所有 `Macro At` 静态方法 4\.8\.2\.6 ⏱️2022\.11\.30 [a15b69d](https://gitee.com/dotnetchina/Furion/commit/a15b69d0eeb4bdc8d0ec042cfec22ff0049dc89f)
+ 新增 `Crontab.Workday` 表示周一至周五的 `Macro` 静态属性 4\.8\.2\.6 ⏱️2022\.11\.30 [a15b69d](https://gitee.com/dotnetchina/Furion/commit/a15b69d0eeb4bdc8d0ec042cfec22ff0049dc89f)
  • 问题修复

    • 修复 Cron 表达式步长解析器错误 4.8.8.25 ⏱️2023.06.14 #I7D9XU
    • 修复 Cron 表达式 * 符号解析器不够严谨,如:*1111aaaaa 也被解析为 * 4.8.7.17 ⏱️2023.03.20 #I6OHO4

版本说明在 Furion 4.8.0+ 版本采用 TimeCrontab 作为 Cron 表达式解析。

版本说明以下内容仅限 Furion 4.8.0 + 版本使用。

26.2.1 关于 Cron 表达式

Cron 表达式是一个字符串,字符串以 56 个空格隔开,分为 67 个域,每一个域代表一个含义,Cron 表达式通常是作为实现定时任务的基石。

26.2.2 快速入门

26.2.2.1 常规格式

常规格式:分 时 天 月 周

var crontab = Crontab.Parse("* * * * *");  
var nextOccurrence = crontab.GetNextOccurrence(DateTime.Now);  

26.2.2.2 支持年份

支持年份:分 时 天 月 周 年

var crontab = Crontab.Parse("* * * * * *", CronStringFormat.WithYears);  
var nextOccurrence = crontab.GetNextOccurrence(DateTime.Now);  

26.2.2.3 支持秒数

支持秒数:秒 分 时 天 月 周

var crontab = Crontab.Parse("* * * * * *", CronStringFormat.WithSeconds);  
var nextOccurrence = crontab.GetNextOccurrence(DateTime.Now);  

26.2.2.4 支持秒和年

支持秒和年:秒 分 时 天 月 周 年

var crontab = Crontab.Parse("* * * * * * *", CronStringFormat.WithSecondsAndYears);  
var nextOccurrence = crontab.GetNextOccurrence(DateTime.Now);  

26.2.2.5 Macro 标识符和静态属性

为了方便常见的 Cron 表达式,如 每天每月每小时 等等,所以提供了 Macro 标识符和静态属性:

// macro 字符串  
var secondly = Crontab.Parse("@secondly");    // 每秒 .0000000  
var minutely = Crontab.Parse("@minutely");    // 每分钟 00  
var hourly = Crontab.Parse("@hourly");    // 每小时 00:00  
var daily = Crontab.Parse("@daily");  // 每天 00:00:00  
var monthly = Crontab.Parse("@monthly");  // 每月 1 号 00:00:00  
var weekly = Crontab.Parse("@weekly");    // 每周日 00:00:00  
var yearly = Crontab.Parse("@yearly");    // 每年 1 月 1 号 00:00:00  
var workday = Crontab.Parse("@workday");    // 每周一至周五 00:00:00  

// 静态属性  
var secondly = Crontab.Secondly;    // 每秒 .0000000  
var minutely = Crontab.Minutely;    // 每分钟 00  
var hourly = Crontab.Hourly;    // 每小时 00:00  
var daily = Crontab.Daily;  // 每天 00:00:00  
var monthly = Crontab.Monthly;  // 每月 1 号 00:00:00  
var weekly = Crontab.Weekly;    // 每周日 00:00:00  
var yearly = Crontab.Yearly;    // 每年 1 月 1 号 00:00:00  
var workday = Crontab.Workday;    // 每周一至周五 00:00:00  

26.2.2.6 Macro At 标识符

// 每第 3 秒  
var crontab = Crontab.SecondlyAt(3);  
// 每第 3,5,6 秒  
var crontab = Crontab.SecondlyAt(3, 5, 6);  

// 每分钟第 3 秒  
var crontab = Crontab.MinutelyAt(3);  
// 每分钟第 3,5,6 秒  
var crontab = Crontab.MinutelyAt(3, 5, 6);  

// 每小时第 3 分钟  
var crontab = Crontab.HourlyAt(3);  
// 每小时第 3,5,6 分钟  
var crontab = Crontab.HourlyAt(3, 5, 6);  

// 每天第 3 小时正(点)  
var crontab = Crontab.DailyAt(3);  
// 每天第 3,5,6 小时正(点)  
var crontab = Crontab.DailyAt(3, 5, 6);  

// 每月第 3 天零点正  
var crontab = Crontab.MonthlyAt(3);  
// 每月第 3,5,6 天零点正  
var crontab = Crontab.MonthlyAt(3, 5, 6);  

// 每周星期 3 零点正  
var crontab = Crontab.WeeklyAt(3);  
var crontab = Crontab.WeeklyAt("WED");  // SUN(星期天),MON,TUE,WED,THU,FRI,SAT  
// 每周星期 3,5,6 零点正  
var crontab = Crontab.WeeklyAt(3, 5, 6);  
var crontab = Crontab.WeeklyAt("WED", "FRI", "SAT");  
// 还支持混合  
var crontab = Crontab.WeeklyAt(3, "FRI", 6);  

// 每年第 3 月 1 日零点正  
var crontab = Crontab.YearlyAt(3);  
var crontab = Crontab.YearlyAt("MAR");  // JAN(一月),FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC  
// 每年第 3,5,6 月 1 日零点正  
var crontab = Crontab.YearlyAt(3);  
var crontab = Crontab.YearlyAt(3, 5, 6);  
var crontab = Crontab.YearlyAt("MAR", "MAY", "JUN");  
// 还支持混合  
var crontab = Crontab.YearlyAt(3, "MAY", 6);  

26.2.3 Cron 各字段说明

字段 允许值 允许特别符号 格式化
0-59 \* , - / CronStringFormat.WithSecondsCronStringFormat.WithSecondsAndYears
分钟 0-59 \* , - / ALL
小时 0-23 \* , - / ALL
1-31 \* , - / ? L W ALL
月份 1-12 or JAN-DEC \* , - / ALL
星期 0-6 or SUN-SAT \* , - / ? L # ALL
年份 0001–9999 \* , - / CronStringFormat.WithYearsCronStringFormat.WithSecondsAndYears
  • *:表示匹配该域的任意值,假如在 分钟 域使用 *,即表示每分钟都会触发事件。
  • ?:只能用在 星期 两个域。它也匹配域的任意值,但实际不会。因为 星期 会相互影响。例如想在 每月的20日 触发调度,不管 20 日到底是星期几,则只能使用如下写法:13 13 15 20 * ?, 其中最后一位只能用 ?,而不能使用 *,如果使用 * 表示不管星期几都会触发,实际上并不是这样。
  • -:表示范围,例如在 分钟 域使用 5-20,表示从 5分20分钟 每分钟触发一次。
  • /:表示起始时间开始触发,然后每隔固定时间触发一次,例如在 分钟 域使用 5/20,则意味着 5分钟 触发一次,而 25,45 等分别触发一次。
  • ,:表示列出枚举值。例如:在 分钟 域使用 5,20,则意味着在 第5第20分钟 分别触发一次。
  • L:表示最后,只能出现在 星期月份 域,如果在 星期 域使用 5L,意味着在 最后的一个星期四 触发。
  • W:表示有效工作日(周一到周五),只能出现在 域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 使用 5W,如果 5日是星期六,则将在最近的工作日:星期五,即 4日 触发。如果 5日是星期天,则在6日(周一)触发;如果 5日在星期一到星期五中的一天,则就在 5日 触发。另外一点,W 的最近寻找不会跨过月份。
  • LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个非周六周末的日期。
  • #:用于确定每个月第几个星期几,只能出现在 星期 域。例如在 2#3,表示某月的第二个星期三。

26.2.4 CronStringFormat 格式化

默认情况下,Cron 表达式不支持 的,如有需求,可配置 CronStringFormat 枚举参数。

  • CronStringFormat 提供以下枚举值:
    • CronStringFormat.Default:默认格式,书写顺序:分 时 天 月 周
    • CronStringFormat.WithYears:带年份格式,书写顺序:分 时 天 月 周 年
    • CronStringFormat.WithSeconds:带秒格式,书写顺序:秒 分 时 天 月 周
    • CronStringFormat.WithSecondsAndYears:带秒和年格式,书写顺序:秒 分 时 天 月 周 年

26.2.5 在线生成 Cron 表达式

对于大多数开发者来说,编写 Cron 表达式是有难度的,所以推荐使用在线 Cron 表达式生成器。

https://cron.qqe2.com/

26.2.6 实现简单定时任务

小建议建议使用 【26.1 调度作业】 章节内容实现强大的分布式定时任务。

通过 Cron 表达式解析和 while 循环可以实现简单的定时任务。

26.2.6.1 while + Task 方式

// 阻塞方式  
var crontab = Crontab.Parse("* * * * * *", CronStringFormat.WithSeconds);  
while(true)  
{  
    Thread.Sleep(crontab.GetSleepMilliseconds(DateTime.Now));  
    Console.WriteLine(DateTime.Now.ToString("G"));  
}  

// 无阻塞方式  
var crontab = Crontab.Parse("* * * * * *", CronStringFormat.WithSeconds);  
Task.Factory.StartNew(async () =>  
{  
    while (true)  
    {  
        await Task.Delay(crontab.GetSleepMilliseconds(DateTime.Now));  
        Console.WriteLine(DateTime.Now.ToString("G"));  
    }  
}, TaskCreationOptions.LongRunning);  

26.2.6.2 BackgroundService 方式

using Furion.TimeCrontab;  

namespace WorkerService;  

public class Worker : BackgroundService  
{  
    private readonly ILogger<Worker> _logger;  
    private readonly Crontab _crontab;  

    public Worker(ILogger<Worker> logger)  
    {  
        _logger = logger;  
        _crontab = Crontab.Parse("* * * * * *", CronStringFormat.WithSeconds);  
    }  

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)  
    {  
        while (!stoppingToken.IsCancellationRequested)  
        {  
            var taskFactory = new TaskFactory(System.Threading.Tasks.TaskScheduler.Current);  
            await taskFactory.StartNew(async () =>  
            {  
                // 你的业务代码写到这里面  

                _logger.LogInformation("Worker running at: {time}", DateTime.Now);  

                await Task.CompletedTask;  
            }, stoppingToken);  

            await Task.Delay(_crontab.GetSleepTimeSpan(DateTime.Now), stoppingToken);  
        }  
    }  
}  

26.2.7 Crontab 对象属性和方法

// 实例属性  
var format = crontab.Format;  // 获取当前格式化配置  

// 静态属性  
var secondly = Crontab.Secondly;    // 每秒 .0000000  
var minutely = Crontab.Minutely;    // 每分钟 00  
var hourly = Crontab.Hourly;    // 每小时 00:00  
var daily = Crontab.Daily;  // 每天 00:00:00  
var monthly = Crontab.Monthly;  // 每月 1 号 00:00:00  
var weekly = Crontab.Weekly;    // 每周日 00:00:00  
var yearly = Crontab.Yearly;    // 每年 1 月 1 号 00:00:00  
var workday = Crontab.Workday;    // 每周一至周五 00:00:00  

// 实例方法  
// 获取下一个执行时间  
var nextOccurrence = crontab.GetNextOccurrence(起始时间);  
var nextOccurrence = crontab.GetNewDbContext(起始时间, 结束时间);  
// 获取特定时间所有执行执行时间  
var nextOccurrences = crontab.GetNextOccurrences(起始时间, 结束时间);  
// 获取当前时间和下一个发生时间相差毫秒数  
var sleepMilliseconds = crontab.GetSleepMilliseconds(起始时间);  
// 获取当前时间和下一个发生时间相差时间戳  
var sleepTimeSpan = crontab.GetSleepTimeSpan(起始时间); // Furion 4.8.4.10+ 版本有效  
// 将 crontab 对象转换成 cron 表达式  
var expression = crontab.ToString();  

// 静态方法  
// 解析表达式  
var crontab = Crontab.Parse("表达式", CronStringFormat.Default);  // 转换表达式为 Crontab 对象  
var crontab = Crontab.TryParse("表达式", CronStringFormat.Default);  // 转换表达式为 Crontab 对象  
var crontab = Crontab.ParseAt("Macro 符号", 1, 3, 5);   // 创建 Macro At Crontab 对象  
var isValid = Crontab.IsValid("表达式", CronStringFormat.Default);  // 判断表达式是否有效,Furion 4.8.7.17+ 支持  

// 每第 3 秒  
var crontab = Crontab.SecondlyAt(3);  
// 每第 3,5,6 秒  
var crontab = Crontab.SecondlyAt(3, 5, 6);  

// 每分钟第 3 秒  
var crontab = Crontab.MinutelyAt(3);  
// 每分钟第 3,5,6 秒  
var crontab = Crontab.MinutelyAt(3, 5, 6);  

// 每小时第 3 分钟  
var crontab = Crontab.HourlyAt(3);  
// 每小时第 3,5,6 分钟  
var crontab = Crontab.HourlyAt(3, 5, 6);  

// 每天第 3 小时正(点)  
var crontab = Crontab.DailyAt(3);  
// 每天第 3,5,6 小时正(点)  
var crontab = Crontab.DailyAt(3, 5, 6);  

// 每月第 3 天零点正  
var crontab = Crontab.MonthlyAt(3);  
// 每月第 3,5,6 天零点正  
var crontab = Crontab.MonthlyAt(3, 5, 6);  

// 每周星期 3 零点正  
var crontab = Crontab.WeeklyAt(3);  
var crontab = Crontab.WeeklyAt("WED");  // SUN(星期天),MON,TUE,WED,THU,FRI,SAT  
// 每周星期 3,5,6 零点正  
var crontab = Crontab.WeeklyAt(3, 5, 6);  
var crontab = Crontab.WeeklyAt("WED", "FRI", "SAT");  
// 还支持混合  
var crontab = Crontab.WeeklyAt(3, "FRI", 6);  

// 每年第 3 月 1 日零点正  
var crontab = Crontab.YearlyAt(3);  
var crontab = Crontab.YearlyAt("MAR");  // JAN(一月),FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC  
// 每年第 3,5,6 月 1 日零点正  
var crontab = Crontab.YearlyAt(3);  
var crontab = Crontab.YearlyAt(3, 5, 6);  
var crontab = Crontab.YearlyAt("MAR", "MAY", "JUN");  
// 还支持混合  
var crontab = Crontab.YearlyAt(3, "MAY", 6);  

26.2.8 反馈与建议

与我们交流给 Furion 提 Issue