涉及各种定时任务时,常常不满足于只能指定间隔时间,Cron表达式隆重登场。Cron表达式是Linux系统标配,用于系统定时任务,只是Linux的Cron标准最小仅支持分钟,不支持秒。而我们的Cron表达式最小支持秒。

Nuget包:NewLife.Core

源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Threading/Cron.cs

视频:https://www.bilibili.com/video/BV1a44y1f77q

Cron简介

基本构成:秒+分+时+天+月+星期+年
每段构成:
* 所有可能的值,该类型片段全部可选
, 列出枚举值
- 范围,横杠表示的一个区间可选
/ 指定数值的增量,在上述可选数字内,间隔多少选一个
? 不指定值,仅日期和星期域支持该字符
# 确定每个月第几个星期几,L表示倒数,仅星期域支持该字符

数字,具体某个数值可选

逗号多选,逗号分隔的多个数字或区间可选



例如:
*/2 每两秒一次
0,1,2 每分钟的0秒1秒2秒各一次
5/20 每分钟的5秒25秒45秒各一次

*1-10,13,25/3 每小时的1分4分7分10分13分25分,每一秒各一次
0 0 0 1 每个月1日的0点整
0 0 2 * * 1-5 每个工作日的凌晨2点

0 0 0 ? ? 1-7#1 每月第一周的任意一天(周一~周日)的0点整

0 0 0 ? ? 3-5#L2 每个月倒数第二个星期三到星期五的0点整


星期部分采用Linux和.NET风格,0表示周日,1表示周一。
可设置Sunday为1,1表示周日,2表示周一。

用法

Cron类支持通过构造函数指定表达式字符串,也可以Parse方法分析表达式

/// <summary>实例化Cron表达式</summary>
/// <param name="expression"></param>
public Cron(String expression) => Parse(expression);

/// <summary>分析表达式</summary>
/// <param name="expression"></param>
/// <returns></returns>
public Boolean Parse(String expression);

/// <summary>指定时间是否位于表达式之内</summary>
/// <param name="time"></param>
/// <returns></returns>
public Boolean IsTime(DateTime time);

/// <summary>获得指定时间之后的下一次执行时间,不含指定时间</summary>
/// <param name="time"></param>
/// <returns></returns>
public DateTime GetNext(DateTime time);

Cron类实现得轻量级,本质上是穷举了每一个时间段。IsTime方法正是判断目标时间是否落入这些时间段之中,也就是是否匹配该表达式。

GetNext 用于获取指定时间之后的下一次匹配时间,常用于判断定时任务的下一次执行时间。

示例如下:

var cron = new Cron("0 * * * *");
Assert.True(cron.IsTime(DateTime.Parse("8:00:00")));
Assert.False(cron.IsTime(DateTime.Parse("8:00:01")));
Assert.Single(cron.Seconds);
Assert.Equal(0, cron.Seconds[0]);

cron = new Cron("0-10 * * * *");
Assert.True(cron.IsTime(DateTime.Parse("8:00:00")));
Assert.True(cron.IsTime(DateTime.Parse("8:00:03")));
Assert.Equal(11, cron.Seconds.Length);
Assert.Equal(0, cron.Seconds[0]);
Assert.Equal(10, cron.Seconds[10]);

cron = new Cron("*/2 * * * *");
Assert.True(cron.IsTime(DateTime.Parse("8:00:00")));
Assert.True(cron.IsTime(DateTime.Parse("8:00:02")));
Assert.False(cron.IsTime(DateTime.Parse("8:00:03")));
Assert.Equal(30, cron.Seconds.Length);

cron = new Cron("5/20 * * * *");
Assert.True(cron.IsTime(DateTime.Parse("8:00:05")));
Assert.True(cron.IsTime(DateTime.Parse("8:00:25")));
Assert.False(cron.IsTime(DateTime.Parse("8:00:20")));
Assert.Equal(3, cron.Seconds.Length);

// 下一次,5秒后
var dt = DateTime.Today;
var dt2 = cron.GetNext(dt);
Assert.Equal(dt.AddSeconds(5), dt2);

// 后续每次间隔20秒
dt2 = cron.GetNext(dt2);
Assert.Equal(dt.AddSeconds(25), dt2);

dt2 = cron.GetNext(dt2);
Assert.Equal(dt.AddSeconds(45), dt2);

总结

Cron表达式在某些场合非常重要,在魔方中作为标准定时任务实现,基础类库TimerX也支持Cron表达式。

高级定时器TimerX