简短回答: 传统 cron 表达式有 5 个字段,顺序是 分钟 小时 日 月 星期,用空格隔开。 表示每个值,, 列出多个值,- 表示范围,/ 表示步长。例如 30 9 * 1-5 是周一到周五每天 09:30。最容易写错的是:日和星期都写了具体值时,cron 是"满足任意一个就运行",不是"两个都满足"。
写完想确认含义和下几次运行时间,可以用免费的 Cron 表达式解析器,在浏览器里完成,不上传内容。
5 个字段
| 位置 | 字段 | 取值 |
|---|---|---|
| 1 | 分钟 | 0-59 |
| 2 | 小时 | 0-23 |
| 3 | 日 | 1-31 |
| 4 | 月 | 1-12 |
| 5 | 星期 | 0-7(0 和 7 都是周日) |
符号: 每个值;1,15 列表;9-17 范围;/5 每隔 5;0-20/2 在 0 到 20 里每隔 2。macOS 自带的 man 5 crontab 里写明星期 0 或 7 都是周日。月和星期可以用英文缩写(如 jan、mon),但同一页手册写着"名称的范围或列表不允许",所以在 macOS 的 cron 里写星期范围,用数字 1-5 比 MON-FRI 保险。这一点我只读了手册,没有在真实的 crontab 里试过。
常见例子
下面的运行时间,都是用本站解析器在 Asia/Shanghai 时区、从 2026-10-20 00:00(周二)起算的结果:
/5 *:每 5 分钟,00:00、00:05、00:10……0 9 1-5:工作日 09:00,依次是 10-20(周二)到 10-23(周五),然后跳到 10-26(周一)。0 0 1:每月 1 日 00:00。0 /2:每 2 小时的整点,00:00、02:00……22:00。0 0 7:和0 0 0一样,每周日 00:00。0 0 29 2 *:只在 2 月 29 日运行,下几次是 2028-02-29、2032-02-29、2036-02-29。
月末没有标准写法。很多文章里的 L(最后一天)是 Quartz 一类调度器的语法,传统 cron 里没有。我把 0 18 L 贴进解析器,它直接报错,说明 L 是 Quartz 语法。要做"每月最后一天",常见做法是每天都运行,在命令里再判断明天是不是 1 号;这个做法我没有在本文里测试。
日和星期同时写:满足任一就运行
这是最容易踩的坑。日(第 3 位)和星期(第 5 位)*都不是 开头*时,只要其中一个满足就会运行。macOS 手册里的例子是 30 4 1,15 5:每月 1 日、15 日,外加每个周五。
实测 0 0 1 * 1(你以为是"1 号且是周一"):
- 2026-10-26(周一)、2026-11-01(周日,1 号)、2026-11-02(周一)、2026-11-09(周一)……
周日的 11-01 也运行了,因为它是 1 号。想要"每月第一个周一",0 0 1-7 1 也不行,实测它会在 11-01、11-02、11-03、11-04 连续运行。纯 cron 表达不出来,常见做法是写 0 0 * 1,再在命令里判断日期是否小于等于 7。
另一个细节:只要日或星期之一*以 开头*,两者就必须同时满足。我测了 0 0 /2 * 1:结果只有 2026-11-09、11-23、12-07、12-21、2027-01-11,都是奇数日的周一,10-26 因为是偶数日被排除了。这是 Vixie cron 和 cronie 的处理方式,别的调度器可能不同,我没有在其他实现上验证。
带秒的 6 位写法
Spring 等框架、node-cron 的表达式前面多一个秒字段,Quartz 还有 ?、L、W、# 和可选的年份,规则与传统 crontab 不同,不能直接复制到 crontab 里。传统 crontab 只认 5 位。本站解析器默认按 5 位读,你贴 6 位的 0 0 它会提示"这里有 6 个字段",需要勾选"带秒字段"选项才按秒读;勾选后 /10 会列出每 10 秒一次。Quartz 的 ?、L、W、# 不支持,会直接报错。其他框架在星期编号等细节上的差别,我没有逐个验证。
时区和夏令时
cron 按运行它的机器的本地时区判断时间,不是按你写表达式时想的时区。北京时间没有夏令时,影响不大;用 UTC 服务器的话,北京时间 09:00 要写成 UTC 01:00。
有夏令时的地区,以美国纽约 2027 年为例(用解析器算的,并用 Python zoneinfo 核对了这两个时间点):3 月 14 日 01:59 后直接变成 03:00,02:30 不存在;11 月 7 日 01:30 出现两次(先是 UTC-4,再是 UTC-5)。
30 2 *:解析器给出 2027-03-14 03:00 运行一次(标为"被推迟")。30 1 *:11 月 7 日只运行第一次的 01:30。30(分钟以*开头):11 月 7 日 01:30 运行两次。
这是 Vixie cron 和 cronie 的规则,我没有在真实的 cron 守护进程里复现;别的调度器不一定相同。最稳的办法是让重要任务避开 01:00-03:00,或者服务器统一用 UTC。
crontab 基本命令(macOS / Linux)
crontab -l:列出当前用户的定时任务,只读。我运行过,能正常输出,内容这里不引用。crontab -e:编辑,我没有运行。- 命令里的
%在 crontab 中有特殊含义(会变成换行),写date +%Y要写成date +\%Y。这是 macOS 手册里写明的。 - 写完不要凭感觉,用解析器看一遍下次运行时间。
用工具省事
Cron 表达式解析器会用中文说明表达式含义,指出写错的字段,列出所选时区接下来 10 次运行时间,并标出夏令时造成的特殊情况。需要换算服务器时间,可以用时区转换器;日志里的数字时间可以用 Unix 时间戳转换器。想看英文的语法梳理,可以读 Cron expressions: how the syntax works(英文)。
核对方式
我用解析器的运行时间函数,对 33 个表达式(含 6 位写法)在 Asia/Shanghai 各取 10 次运行时间,同时用独立写的 Python(zoneinfo)按日期逐个枚举计算。第一次对比有 1 个不一致,是 0 0 29 2 *:原因是我的 Python 只往后算 12 年,只找到 3 个闰年;把范围放宽后 33 个全部一致。夏令时部分只核对了时间点,没有逐条独立重算。
常见问题
Cron 表达式是 5 位还是 6 位?
传统 crontab 是 5 位:分钟、小时、日、月、星期。Spring、node-cron 等会在前面加一个秒字段,成为 6 位;Quartz 还有年份。写之前先确认你用的是哪一种。
0 0 1 * 1 是"每月 1 号且是周一"吗?
不是。日和星期都写了具体值时,满足任意一个就会运行,所以它在每月 1 日和每个周一都运行。实测 2026-11-01 是周日,也运行了。
cron 里的 7 是什么?
7 和 0 一样是周日。0 0 7 与 0 0 0 的运行时间相同,我对比过两者的 10 次结果一致。
北京时间 09:00 的任务,服务器是 UTC 要怎么写?
UTC 比北京时间晚 8 小时,所以写 0 1 *。更稳妥的做法是确认服务器时区后再写,也可以用解析器选对应时区查看下几次运行时间。