跳到正文
返回博客
·约 2 分钟读完

Cron 表达式怎么写:5 位格式、常见例子和容易写错的地方

Cron 表达式由分钟、小时、日、月、星期 5 个字段组成。本文讲 * , - / 的写法、0 和 7 都是周日、日和星期同时限制时"满足任一即可"的规则、每 5 分钟和工作日 9 点等例子、带秒的 6 位写法、时区与夏令时的坑,以及 crontab 的基本命令。

简短回答: 传统 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 *。更稳妥的做法是确认服务器时区后再写,也可以用解析器选对应时区查看下几次运行时间。

作者

独立开发者,在 Obelisk Club 做本地优先的 Mac 与 iOS 效率应用。 关于作者 →