cron 调度符号深度解析:5 个特殊字符 (* / , - ?) 与 @快捷方式全解
【免费下载链接】crona cron library for go项目地址: https://gitcode.com/gh_mirrors/cr/cron
使用 Go 编写定时任务时,
cron表达式是最常用的调度语法。本文带你一次看懂 cron 表达式中的 5 个特殊字符——星号*、斜杠/、逗号,、连字符-、问号?——以及@hourly、@every 5m等 @快捷方式的完整用法,并附上字段取值表和常见踩坑点,让你从零写对第一条 cron 调度规则。
🧭 一分钟看懂 cron 表达式的 5 个字段
标准 cron 表达式由5 个空格分隔的字段组成,从左到右依次是:
| 字段 | 取值范围 | 支持的特殊字符 | 说明 |
|---|---|---|---|
| 分钟 (Minutes) | 0–59 | * / , - | 每小时的第几分钟 |
| 小时 (Hours) | 0–23 | * / , - | 每天的第几点 |
| 日 (Day of month) | 1–31 | * / , - ? | 每月第几天 |
| 月 (Month) | 1–12 或 JAN–DEC | * / , - | 月份(支持英文缩写,不区分大小写) |
| 星期 (Day of week) | 0–6 或 SUN–SAT | * / , - ? | 周几(0 = 星期日) |
┌───────────── 分钟 (0-59) │ ┌───────────── 小时 (0-23) │ │ ┌───────────── 日 (1-31) │ │ │ ┌───────────── 月 (1-12, JAN-DEC) │ │ │ │ ┌───────────── 星期 (0-6, SUN-SAT) * * * * *几个容易忽略的细节:
- 默认解析器不包含秒字段。如果你需要秒级精度(类似 Quartz 的 6 位格式),可以用
cron.WithSeconds()选项开启,实现见 option.go。 - 月份和星期字段支持
JAN-DEC、SUN-SAT等名称,且大小写不敏感。 - 所有字段名、边界值和解析选项定义在 spec.go 和 parser.go。
🔤 5 大特殊字符逐个拆解
1️⃣ 星号*:匹配全部
*表示该字段所有取值都匹配,是最常见的偷懒写法。
| 表达式 | 含义 |
|---|---|
* * * * * | 每分钟执行 |
0 * * * * | 每小时的第 0 分钟(整点) |
0 3 * * * | 每天凌晨 3 点 |
2️⃣ 斜杠/:步长(步长是相对"起点"的)
/用于描述区间内的间隔,后面跟步长数字,有三种常见形态:
| 写法 | 含义 |
|---|---|
* /15(即*/15) | 整个范围从第一个值开始,每隔 15:0,15,30,45 |
3-59/15 | 从 3 开始每隔 15:3,18,33,48 |
5/15 | 等价于5-59/15:5,20,35,50 |
第三个形态N/step是本项目的一个特色语义:N/step被解释为N-MAX/step,即从 N 开始直到该字段最大值,中间不折返(wrap around)。这一点在 parser.go 中实现,测试用例0/15 * * * *、5/15 * * * *在 spec_test.go 中有明确验证。
⚠️ 常见误解:15/15不是"从 15 到 15",而是 "15 到最大值、每 15 一次",即15,30,45。
3️⃣ 逗号,:枚举多个值
,用来分隔一个列表,表示"在这些值之一":
| 表达式 | 含义 |
|---|---|
0 8,12,18 * * * | 每天 8 点、12 点、18 点 |
0 0 1 * JAN,JUL * | 每年 1 月和 7 月的 1 号 |
30 * * * MON,WED,FRI | 每周一、三、五的半点 |
逗号可以与范围、步长自由组合,例如1,15 */2 * * *。
4️⃣ 连字符-:定义范围
-定义闭区间(两端都包含):
| 表达式 | 含义 |
|---|---|
30 3-6,20-23 * * * | 每天 3–6 点、20–23 点的第 30 分钟 |
0 9-17 * * 1-5 | 工作日 9 点到 17 点整 |
0 0 * * MON-FRI | 工作日(名称形式) |
范围还可以嵌套步长:20-35/15表示20, 35(见 spec_test.go 的跨时区滚动测试)。
5️⃣ 问号?:让位给"日"或"星期"
?只能用在日和星期这两个字段,作用是"我不关心这一项,请交给另一项判断"。
这里藏着 cron 表达式最精妙的一个规则 👇
当"日"和"星期"都有限制时,二者是 OR 关系——任意一个匹配就算匹配;当其中一个字段是*或?(即"不限")时,二者变成 AND 关系——必须同时满足。
这个 AND/OR 切换逻辑由starBit标志位驱动,源码在 spec.go:
* * 1,15 * Sun → 每月 1 号、15 号,或 每周日(OR,满足其一即可) 0 8 ? * MON → 每周一早上 8 点(DOW 生效,Dom 用 ? 让位) 30 08 15 Jul ? → 每年 7 月 15 号 08:30(DOW 用 ? 让位)解析阶段?与*会展开成相同的取值范围(parser.go),真正的区别只在上面这个 AND/OR 判断中体现。项目测试里大量使用* * * * * ?这种写法(见 cron_test.go),正是为了显式地把"星期"让出来。
⏰ @快捷方式全解:5 个预定义计划 + @every 间隔
标准 5 位写法之外,cron还支持以@开头的预定义调度(descriptor),全部清单如下:
| 快捷方式 | 等价表达式 | 含义 |
|---|---|---|
@yearly/@annually | 0 0 1 1 * | 每年 1 月 1 日 0 点 |
@monthly | 0 0 1 * * | 每月 1 号 0 点 |
@weekly | 0 0 * * 0 | 每周日 0 点 |
@daily/@midnight | 0 0 * * * | 每天 0 点 |
@hourly | 0 * * * * | 每小时整点 |
实现位于 parser.go,每个快捷方式都会被展开为一张精确到秒的位图调度表。
@every:固定间隔调度
除了上面 5 个,还有万能写法@every <duration>,表示"从注册时刻起每隔固定时长执行一次":
| 表达式 | 含义 |
|---|---|
@every 30s | 每 30 秒 |
@every 1h30m | 每 1 小时 30 分 |
@every 15m | 每 15 分钟 |
时长格式遵循 Go 的time.ParseDuration规则。注意两点(见 constantdelay.go 与 doc.go):
- 间隔不支持小于 1 秒,会被向上取整到 1 秒;
- 间隔不扣减任务本身的执行耗时——任务跑 3 分钟、间隔设 5 分钟,那么两次执行之间实际只剩 2 分钟空闲。
🌏 顺手一提:用CRON_TZ指定时区
默认情况下所有调度都按本机时区解释。如果想让某条任务固定按东京时间 4:30 执行,只需在表达式前加一个前缀:
CRON_TZ=Asia/Tokyo 30 04 * * *解析器会识别CRON_TZ=或旧式的TZ=前缀并加载对应时区(parser.go)。也提醒一下官方文档的警告:夏令时"跳跃"过去的那个时刻(如不存在的凌晨 2:30),排在该时刻的任务不会被执行。
📚 核心文件导航
想深入源码,按这条路径走最高效:
| 文件 | 看什么 |
|---|---|
| doc.go | 包级文档:字段表、特殊字符、@计划、时区的权威说明 |
| parser.go | 表达式解析入口、/的N-MAX语义、@快捷方式展开 |
| spec.go | 位图调度、下一次触发时间的推算、Dom/Dow 的 AND/OR 规则 |
| option.go | WithSeconds、WithParser、WithLocation等配置项 |
| constantdelay.go | @every固定间隔调度的实现 |
| parser_test.go / spec_test.go | 各种边界的官方测试用例,是最好的"活文档" |
✅ 避坑清单
5/15≠ 只跑一次——它是5-59/15,即 5、20、35、50 分各一次;?只能用于日和星期,写在分钟或月字段会解析报错;- 日 + 星期同时写具体值 = OR,想让它们同时生效,其中一个必须写
*或?; - 默认没有秒字段,需要秒级精度时记得加
cron.WithSeconds(); @every从任务注册时刻计时,不是从整点开始,任务耗时也不在间隔内扣除。
掌握以上内容,你就能覆盖绝大多数定时任务的写法了:先用*铺底,再用,、-、/精细裁剪时间和日期,最后遇到整点、整月这类固定场景直接甩@快捷方式。
【免费下载链接】crona cron library for go项目地址: https://gitcode.com/gh_mirrors/cr/cron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考