Sparkdo.Crontab 3.0.0-preview.1

This is a prerelease version of Sparkdo.Crontab.
dotnet add package Sparkdo.Crontab --version 3.0.0-preview.1
                    
NuGet\Install-Package Sparkdo.Crontab -Version 3.0.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Sparkdo.Crontab" Version="3.0.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Sparkdo.Crontab" Version="3.0.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="Sparkdo.Crontab" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Sparkdo.Crontab --version 3.0.0-preview.1
                    
#r "nuget: Sparkdo.Crontab, 3.0.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Sparkdo.Crontab@3.0.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Sparkdo.Crontab&version=3.0.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Sparkdo.Crontab&version=3.0.0-preview.1&prerelease
                    
Install as a Cake Tool

Sparkdo.Crontab

.NET 10/11 的纯算法 Cron 库:把表达式解析成不可变的 Cron 对象,回答"下一次、上一次、这段时间内的每一次是什么时候"。不托管后台任务、不保存状态、不执行作业——调度语义属于 Sparkdo Scheduler。零外部依赖,AOT / trimming / single-file 友好,Next 主路径 0 B 分配。

dotnet add package Sparkdo.Crontab
using Sparkdo.Crontab;

var cron = Cron.Parse("*/15 * * * *");
var start = new DateTimeOffset(2026, 1, 1, 0, 7, 0, TimeSpan.Zero);

cron.Next(start);      // 2026-01-01 00:15:00 +00:00(严格晚于)
cron.Previous(start);  // 2026-01-01 00:00:00 +00:00(严格早于)
cron.Between(start, start.AddHours(1));            // 00:15、00:30、00:45(升序,默认不含两端)
cron.Matches(new DateTimeOffset(2026, 1, 1, 0, 45, 0, TimeSpan.Zero)); // true

表达式格式

默认 Standard = 5 段"分 时 日 月 周"。六段必须显式声明是秒还是年:

CronFormat 段数 字段顺序 示例
Standard 5 分 时 日 月 周 0 9 * * 1-5
Seconds 6 秒 分 时 日 月 周 0 */5 * * * *
Years 6 分 时 日 月 周 年 0 0 1 1 * 2030
SecondsAndYears 7 秒 分 时 日 月 周 年 0 0 0 1 1 * 2030
Cron.Parse("0 */5 * * * *", CronFormat.Seconds); // 每 5 分钟:从 00:07 → 00:10
Cron.Parse("0 0 1 1 * 2030", CronFormat.Years);  // 2030 年元旦 00:00
Cron.Parse("0 */5 * * * *");                    // CronParseException:字段数量应为 5,实际为 6。

取值范围:秒/分 0-59、时 0-23、日 1-31、月 1-12、周 0-7(0 与 7 都是周日)、年 1-9999。

字段语法速查

写法 含义 实测例(Standard,起点为固定 offset UTC)
* / ? 任意值(? 只允许写在日、周字段,语义等同 *) 0 9 ? * MON-FRI
a,b,c / a-b / a-b/n 列表 / 闭区间 / 区间内步长 1-10/3 * * * * 匹配 01、04、07、10
*/n / s/n 从字段最小值 / 从 s 起步长 n 5/15 * * * * 从 00:06 → 00:20
JAN–DEC / SUN–SAT 三字母名称(大小写无关,全拼报错) 0 0 1 JAN-DEC/6 * → 1 月与 7 月 1 日
L / LW / nW(日字段) 月末日 / 最后一个工作日 / 最接近 n 日的工作日(不跨月) 0 0 15W * * 2026-08 → 08-14
nL / n#k(周字段) 月内最后一个星期 n / 第 k 个星期 n(k ∈ 1–5) 0 0 * * MON#3 2026-01 → 01-19
R(秒/分/时字段) 解析期随机取一个合法值,同一实例恒定;不可作缓存键或落库标识(文本相同即等值,但各实例定值彼此独立) R * * * * 用于打散整点热点
@daily 等宏 见下表,大小写无关 Cron.Parse("@daily").ToString() → 0 0 * * *

日字段与周字段是 AND(与 Quartz 的 OR 习惯不同):0 9 15 * MON 只匹配"15 日且是周一",从 2026-01-01 → 2026-06-15 09:00。没有限制的一侧写 * 或 ?。

星期端点会做 7 归一化:5-7 = 五六日、7-0 = 仅周日、7/2 = 日二四六。倒置范围(8-5)在解析期拒绝。

宏与工厂

预设 / 宏 展开 工厂 生成表达式
Cron.Secondly / @secondly * * * * * * HourlyAt(minute) {minute} * * * *
Cron.Minutely / @minutely * * * * * DailyAt(hour, minute = 0) {minute} {hour} * * *
Cron.Hourly / @hourly 0 * * * * WeeklyAt(day, hour = 0, minute = 0) {minute} {hour} * * {0..6}
Cron.Daily / @daily 0 0 * * * MonthlyAt(day, hour = 0, minute = 0) {minute} {hour} {day} * *
Cron.Weekly / @weekly 0 0 * * 0 YearlyAt(month, day, hour = 0, minute = 0) {minute} {hour} {day} {month} *
Cron.Monthly / @monthly 0 0 1 * * EverySecond() / EveryMinute() 返回共享预设实例
Cron.Yearly / @yearly 0 0 1 1 *
Cron.Workday / @workday 0 0 * * 1-5

工厂参数越界(DailyAt(24)、HourlyAt(60))或组合出永不触发的规则(YearlyAt(2, 31))都在调用时抛 CronParseException。

校验外部输入

try
{
    var cron = Cron.Parse(input);
}
catch (CronParseException ex)
{
    // ex.Expression 整串原文 / ex.Field 出错的逗号分段("0,15,bad" 只给 "bad")或整字段原文 / ex.Position 该原文内 0-based 下标 / ex.Reason 中文诊断
}

// 只要布尔结论时用 TryParse / IsValid(TryParse 只吞 CronParseException,其它异常照常传播)
if (Cron.TryParse(input, out var cron) && cron.Next(DateTimeOffset.UtcNow) <= deadline) { }

解析期即拒绝:空/null 表达式、字段数不符、值越界、零步长、倒置范围、空段、未知宏、特殊语法用错字段、月内序号越界、全拼名称,以及"日字段在月字段可选月份内永无命中"的表达式(0 0 31 2 *)与闰日配非闰年(0 0 29 2 * 2100)。

时间语义

不给 TimeZone 时只按 baseTime.Offset 做墙钟算术,不读机器时区、不感知 DST,结果的 offset 恒等于输入的 offset:

Cron.Parse("0 9 * * *").Next(new DateTimeOffset(2026, 1, 1, 8, 0, 0, TimeSpan.FromHours(8)));
// → 2026-01-01 09:00:00 +08:00

需要本地时间与 DST 规则时显式传 TimeZoneInfo(时区 ID 在 Windows 与 Linux 不同,请按部署环境验证):

var zone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai");
var options = CronOptions.InTimeZone(zone);
Cron.Parse("0 9 * * *").Next(DateTimeOffset.UtcNow, options); // → 该时区 09:00,offset 随 DST 变化

CronOptions(readonly record struct,可用 with 派生)四个旋钮:

属性 默认 作用
TimeZone null null = 固定 offset;非 null = 按该时区墙钟解释规则。
Boundary Exclusive IncludeStart 让 Next/Between 含起点,IncludeEnd 让 Previous/Between 含终点,IncludeBoth 两端都含。
InvalidTimePolicy Skip 春季拨快造成的不存在时刻:Skip 让落在 gap 内的触发点作废,ShiftForward 前移到 gap 后第一个有效分钟。例:30 2 8 3 * 在 America/New_York 的 2026-03-08 无 02:30,该规则当天只有这一个点,默认直接给 2027-03-08(30 2,3 8 3 * 仍会给当天的 03:30)。
AmbiguousTimePolicy EarlierOffset 秋季回拨出现两遍的本地时刻:取较早/较晚 offset;Both 让 Between 返回两条并按真实 instant 升序排列。

三条容易踩的规则:

  • 起点先按最小单位截断。Standard 格式下 * * * * * 对 00:07:30 的 Next 是 00:08:00;即使给了 IncludeStart 也不会返回 00:07:30。要按秒推进请用 Seconds 格式。
  • Matches 按 instant 自身 offset 的墙钟判定,不做时区转换、不读 CronOptions;需要按某时区判断请用 Matches(instant, timeZone) 重载。规则没有秒字段时,秒非 0 一律 false。
  • 窗口内找不到就抛。无年字段约束的规则在查询方向 5 年内搜索,越界抛 InvalidOperationException(年字段有界时上限自动扩到该年份);Between 惰性求值,窗口耗尽的那一步同样抛。

不可变与可共享

Cron 无状态、线程安全,可长期持有并跨线程共享;相等性按表达式文本 Ordinal 比较(ToString() 原样返回输入文本,因此空白变体不等值——落库或做缓存键前先规范化)。例外:含 R 的表达式文本相同即等值,但各实例的随机定值彼此独立、触发时刻可以不同,不可作缓存键或落库标识。解析有约 1 KB 冷路径成本,热查询 80–300 ns / 0 B,所以高频路径请缓存 Cron 实例而不是反复 Parse。

边界

  • 不提供 DI、Hosting、后台 worker、任务队列、持久化、分布式锁、misfire / catch-up 与执行语义。
  • 不做 Cron → 人类可读文本格式化,不做表达式改写或等价归一化。
  • 不读机器本地时区;DST 数据来自 BCL TimeZoneInfo。
  • 不暴露已解析的字段结构(Cron 是不透明规则句柄)。

完整语法语义、DST 策略矩阵、异常一览、基准数据与用法配方见源码仓库 src/crontab/README.md。

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net11.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Sparkdo.Crontab:

Package Downloads
Sparkdo.Scheduler

Sparkdo Scheduler 运行时与默认 in-memory provider,内嵌源生成器分析器资产。

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0-preview.1 79 10/30/2026
2.0.0-preview.2 115 7/20/2026
2.0.0-preview.1 91 7/18/2026