Cron 表达式速查与避坑指南

定时任务写错一次,轻则没跑成,重则半夜把服务打挂。这篇把五字段语义、常用表达式和特殊字符整理成速查表,再逐个拆解让任务翻车的经典坑,附上验证方法。

五个字段各管什么

标准 cron 表达式由 5 个字段组成,用空格分隔,依次是分、时、日、月、周。站内 Cron 表达式生成器就按这套规则解析,输入即给出中英文解释和接下来 5 次执行时间,改一个字符立刻重算。

字段取值范围支持写法示例
分0 到 59数字、逗号列表、区间、步进0,30。
时0 到 23同上,区间支持回绕*/6。
日1 到 31可用问号代替星号1,15。
月1 到 12数字或 jan 到 dec 缩写1,4,7,10。
周0 到 70 和 7 都表示周日,缩写 sun 到 sat1-5。

三点补充。输入不限大小写,MON 和 mon 都认。周字段写 7 会被归到周日,等价于 0。问号只在日和周两个字段里有效,写到分或时里会直接报错。字段数必须正好 5 个,多一个少一个都不行,空格可以连续多个。

月和周的英文缩写来自英文月名与星期名的前三个字母。jan 是一月,dec 是十二月,sat 是周六。写数字最稳妥,缩写适合从别人博客抄来的表达式对不上号时对照用。

把字段想成时间的三层过滤更容易记住。月先筛掉大部分日子,日和周再筛掉剩余日子里的绝大多数,最后分和时决定当天哪一分钟动手。写表达式时按这个顺序推一遍,命中逻辑就不容易绕晕。备份类任务挑业务低峰,清理类任务错开备份,重活别挤在同一个整点。

常用示例速查表

下表 14 条覆盖大部分日常场景。前六条与站内生成器的常用预设一致,粘进工具即可看到解释与执行时间预演。

表达式含义
* * * * *每分钟执行一次。
7 * * * *每小时第 7 分执行一次。
0 * * * *每小时整点执行。
30 8 * * *每天 08:30。
0 9 * * 1-5工作日 09:00。
0 10 * * 6每周六 10:00。
30 8,20 * * *每天 08:30 与 20:30 各一次。
0 */6 * * *每 6 小时,落在 0、6、12、18 点。
*/15 * * * *每 15 分钟一次。
0 9 1 * *每月 1 号 09:00。
0 9 1,15 * *每月 1 号与 15 号 09:00。
0 9 1 1,4,7,10 *每季度首月 1 号 09:00。
0 9 1 1 *每年 1 月 1 号 09:00。
0 22-2 * * *每天 22 点到次日 2 点整点,区间回绕写法。

季度任务写 1,4,7,10 最直观。月域 1/3 在本工具里等价于 1,4,7,10,但数字加步进不是所有实现都认,跨环境使用前先验证。每年一次的任务写死月和日,别依赖月末相关写法。

在站内工具里验证的流程

速查表给了起点,验证靠工具。流程四步,两分钟内能走完。

  1. 把表达式粘进输入框,先读中英文解释,看语义和你想的是否一致。
  2. 看"接下来 5 次执行时间",核对日期、星期与时刻。
  3. 拿不准某个字段,点字段输入框下方的快捷按钮,或直接改单字段,预演会跟着变。
  4. 确认无误后复制表达式,粘贴到部署目标,别手抄,抄错一个字符全盘皆错。

涉及日周同限的写法,工具会专门给出提示,预演日期里也能看出"或"的痕迹。执行时间预演按你设备的本地时钟计算,与服务器时区不一致时,以服务器时区为准再换算一次。

常见错误写法对照

下面这些写法都见过真实案例,逐条给出问题所在。

写法问题
60 0 * * *分钟超出 0 到 59,直接报错。
0 9 * * 8周字段超出 0 到 7,直接报错。
0 0 31 2 *2 月没有 31 号,永远不会执行。
0 ? * * *问号写进了时域,问号只允许出现在日和周。
* * * *只有 4 个字段,字段数必须正好 5。
@hourly站内工具不支持简写,请展开成 0 * * * *。

选型时先想清执行频率的上限。心跳与监控类可以放宽到每分钟,报表类按小时或按天,账务类按月固定日。频率越高,任务本身越要轻,重任务密集调度会把机器压垮。写进表达式之前,把"什么时候该跑、不该跑"一句话说清,再翻译成五个字段。

特殊字符与简写

符号写法决定命中哪些时刻。下表逐个说明,并标注站内工具是否支持。

写法含义站内工具
*匹配字段内所有值。支持。
,列表,各值取并集。支持。
-闭区间;小时域支持跨零点回绕,如 22-2。支持。
/步进。星号加步进从字段最小值起算,数字加步进从该数字起算到字段最大值。支持。
?占位符,等价于星号,仅日和周字段可用。支持。
缩写月支持 jan 到 dec,周支持 sun 到 sat。支持。
L表示本月最后一天,或倒数第几个周几。不支持,需在目标环境验证。
W表示离指定日最近的可用工作日。不支持,需在目标环境验证。
#表示当月第几个周几,如 5#3。不支持,需在目标环境验证。
@简写@daily、@weekly、@monthly、@reboot 一类简写。不支持,需在目标环境验证。

站内工具只接受 5 个空格分隔的字段,@daily 会被判成字段数错误。想表达每天零点,直接写 0 0 * * *。L、W、# 是各家实现的扩展,cronie、systemd timer 和各类云调度器行为不一。用之前先查目标环境的文档,或在测试环境跑一轮,确认命中时间符合预期再上生产。

避坑:六个最容易翻车的地方

日周同限是或不是且

0 0 1 * 1 常被当成"每月 1 号且逢周一"。实际语义是"每月 1 号或每个周一"。日和周两个字段同时给出限制时,命中任意一个就执行,这是标准 cron 的规则。站内工具检测到这种写法会给出提示,预演里也会露馅:执行日期明显多于每月一天。只想按日期跑,周域写星号。只想按星期跑,日域写星号或问号。

月末 31 号陷阱

0 0 31 * * 想表达每月最后一天,实际只在有 31 号的月份执行。2 月永远没有 31 号,4、6、9、11 月只有 30 天,任务整月静默,而且没有任何报错。这种错误最难发现,因为任务在别的月份明明跑得好好的。站内工具不支持 L 写法,无法直接表达"本月最后一天"。常见替代方案是每天跑一次脚本,脚本里判断明天是不是 1 号,是再干活。这个方案不依赖扩展语法,跨环境通用。

时区:表达式里没有时区字段

cron 的五个字段只描述钟面时间,不描述时区。同一串 0 9 * * *,在北京时间的机器上是本地九点,在 UTC 的服务器上就是 UTC 九点,差出八小时。服务器默认时区常为 UTC,云主机也多如此。部署前先跑 date 命令看服务器时区,再决定字段怎么填。站内工具按你设备的本地时间演算执行时刻,与服务器时区可能不同,跨环境迁移时留意这层差异。

容器与函数计算环境还要多看一层。容器内的时区常取决于镜像设置,镜像默认 UTC,装了时区包才会跟随宿主。有些实现提供 CRON_TZ 或时区后缀一类的扩展,属于非标准能力,先确认目标环境是否支持再依赖。最稳的做法是把服务器时区、表达式语义、预期时刻三者在纸上对一遍。

夏令时跳变

实行夏令时的时区,每年有两次异常。春季时钟拨快一小时,凌晨某些钟面时刻当天不存在,恰好排在这些时刻的任务当天不会触发。秋季时钟拨回,同一钟面时刻出现两次,按钟面匹配的任务当天可能跑两次。UTC 没有夏令时,服务器统一用 UTC 能绕开大部分问题。坚持用本地时区的,跳变日前后多看一眼日志,确认任务执行次数符合预期。

步进从哪个值起步

星号加步进从字段最小值开始。分钟 */5 命中 0、5、10 直到 55,不是从部署任务那一刻对齐。月域 */3 命中 1、4、7、10,正好对应四个季度的首月。数字加步进从该数字开始:小时 9/2 命中 9、11、13 直到 23。想要整点起每 30 分钟一次,写 0/30 或 0,30 都行,写 5/30 就会从第 5 分钟开始。部署完看一遍执行记录,就能确认起步点对不对。

列表与步进可以混用,分钟写 0,10-30/10 会命中 0、10、20、30。动笔前先想想是否值得,能拆成两条简单表达式就拆,半年后读配置的人会感谢你。

区间回绕并非到处通用

小时域写 22-2,站内工具会命中 22、23、0、1、2,跨零点的值班任务很好用。但不少经典实现只支持从小到大的区间,把 22-2 当空集或直接报错。这类差异不会写在文档第一页。把表达式迁去新环境时,先用"下一次执行时间"预演验证,再上生产。

上线前的三个动作

第一,把表达式粘进站内生成器,读一遍中英文解释,确认语义与预期一致。第二,看接下来 5 次执行时间,重点核对日期与星期,尤其涉及日周同写的时候。第三,上生产后第一周盯日志,核对实际执行次数与时刻。三步都过,这串表达式才算可信。改表达式和改代码一样,都要留验证痕迹。

把最终表达式连同服务器时区一起写进部署文档,注明语义与验证日期。半年后接手的人不需要重新推导你的意图。这条习惯比任何速查表都管用。

文中相关工具