# Cron 表达式怎么写:5 位格式、常见例子和容易写错的地方 > Cron 表达式由分钟、小时、日、月、星期 5 个字段组成。本文讲 * , - / 的写法、0 和 7 都是周日、日和星期同时限制时"满足任一即可"的规则、每 5 分钟和工作日 9 点等例子、带秒的 6 位写法、时区与夏令时的坑,以及 crontab 的基本命令。 - Canonical page: https://obelisk.club/blog/cron-expression-guide-zh - Author: Peter Zhang - Published: 2026-10-20 - Tags: cron, crontab, linux, macos, 开发工具, 中文 **简短回答:** 传统 cron 表达式有 5 个字段,顺序是 `分钟 小时 日 月 星期`,用空格隔开。`*` 表示每个值,`,` 列出多个值,`-` 表示范围,`/` 表示步长。例如 `30 9 * * 1-5` 是周一到周五每天 09:30。最容易写错的是:日和星期都写了具体值时,cron 是"满足任意一个就运行",不是"两个都满足"。 写完想确认含义和下几次运行时间,可以用免费的 [Cron 表达式解析器](https://obelisk.club/tools/cron-explainer?lang=zh),在浏览器里完成,不上传内容。 ## 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 表达式解析器](https://obelisk.club/tools/cron-explainer?lang=zh)会用中文说明表达式含义,指出写错的字段,列出所选时区接下来 10 次运行时间,并标出夏令时造成的特殊情况。需要换算服务器时间,可以用[时区转换器](https://obelisk.club/tools/timezone-converter?lang=zh);日志里的数字时间可以用 [Unix 时间戳转换器](https://obelisk.club/tools/timestamp-converter?lang=zh)。想看英文的语法梳理,可以读 [Cron expressions: how the syntax works](https://obelisk.club/topics/cron-expressions)(英文)。 ## 核对方式 我用解析器的运行时间函数,对 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 * * *`。更稳妥的做法是确认服务器时区后再写,也可以用解析器选对应时区查看下几次运行时间。