让 GitHub Actions 的 cron 按日本时间运行:UTC 换算速查表与星期偏移、延迟的对策
在 GitHub Actions 的 schedule 里写了“0 9 * * 1-5”,工作流却在日本时间傍晚 6 点才运行。这并不是写错了,而是因为 GitHub Actions 的 cron 一律按 UTC(协调世界时)解读。本文整理了把日本时间(JST)换算成 UTC cron 表达式的方法和速查表、指定早上 0 点到 8 点时出现的“星期和日期退回前一天”问题、一行写不下时如何拆成两行,以及 schedule 延迟或不运行的原因与对策。
为什么差 9 小时?GitHub Actions 的 cron 固定为 UTC
在 GitHub Actions 中,只要在工作流的 YAML 里写上 on.schedule 和 cron 表达式,就能让任务在固定时间运行。此时,无论仓库所有者身在哪个国家、运行器(runner)位于何处,cron 表达式都会被当作 UTC(协调世界时)来解读。它不会根据电脑的时钟或仓库设置自动换成日本时间。
日本时间(JST)比 UTC 快 9 小时。也就是说,UTC 的 0:00 是日本的 9:00,UTC 的 9:00 是日本的 18:00。如果按日本时间写成“0 9 * * *”,它会在 UTC 9:00,也就是日本时间 18:00 运行。开头提到的“傍晚 6 点才运行”正是这种情况。
- 日本时间 → UTC:小时减 9(9:00 → 0:00,18:30 → 9:30)
- 结果为负数时(日本时间 0 点到 8 点多),加上 24,并视为“前一天”的时间(8:00 → 前一天 23:00)
- UTC → 日本时间:小时加 9。达到 24 或以上时减去 24,并视为“后一天”
- 日本没有夏令时,时差不会随季节变化(始终是 9 小时)
日本时间 → UTC 的 cron 表达式速查表
下表列出了常用时间按日本时间写的 cron 表达式,以及在 GitHub Actions 中应写的 UTC 表达式。把右列直接粘贴到 - cron: 中,就会在左列的日本时间运行(实际还会加上后文说明的延迟)。
| 想运行的时间(日本时间) | 按日本时间写的 cron | 在 GitHub Actions 中写的表达式(UTC) |
|---|---|---|
| 每天 9:00 | 0 9 * * * | 0 0 * * * |
| 每天 8:30 | 30 8 * * * | 30 23 * * *(前一天 23:30) |
| 每天 18:00 | 0 18 * * * | 0 9 * * * |
| 每天 0:00(日期变更时) | 0 0 * * * | 0 15 * * *(前一天 15:00) |
| 工作日(周一至周五)9:00 | 0 9 * * 1-5 | 0 0 * * 1-5 |
| 工作日(周一至周五)8:00 | 0 8 * * 1-5 | 0 23 * * 0-4(周日至周四 23:00) |
| 工作日(周一至周五)6:00 | 0 6 * * 1-5 | 0 21 * * 0-4(周日至周四 21:00) |
| 工作日 9 点到 18 点每个整点 | 0 9-18 * * 1-5 | 0 0-9 * * 1-5 |
| 每周一 0:00 | 0 0 * * 1 | 0 15 * * 0(周日 15:00) |
| 每 2 小时(0、2、4……点) | 0 */2 * * * | 0 1-23/2 * * * |
| 每月 15 日 9:00 | 0 9 15 * * | 0 0 15 * * |
| 每月 1 日 3:00 | 0 3 1 * * | 一行无法准确写出(见后文) |
表中需要注意的是指定日本时间 0 点到 8 点多的那几行。把时间往回拨 9 小时就会落到前一天,所以不仅是小时,星期或日期也必须退回一格。如果把“工作日 8:00”只改小时写成“0 23 * * 1-5”,就会在 UTC 周一至周五的 23:00,也就是日本时间周二至周六的 8:00 运行,结果周一早上不运行,周六早上反而运行。
“每 2 小时”也是容易忽略的例子。日本时间的 0、2、4……22 点在 UTC 中是 15、17、19、21、23、1、3……13 点,全是奇数小时,所以保留“*/2”会整体偏差 1 小时。需要改写成表示 UTC 奇数小时的“1-23/2”。
早上 0 点到 8 点会变成“前一天、前一个星期几”:偏移的算法
把日本时间换算成 UTC 时,日期是否变化只取决于“小时”。日本时间 9 点以后仍是同一天,0 点到 8 点多则变成前一天。修改星期和日期字段的步骤如下。
- 1
小时减 9
8:00 的话是 8 − 9 = −1。出现负数时加 24 得到 23,并记住“退回前一天”。如果小时在 9 或以上,直接使用相减的结果,日期不变。
- 2
星期字段退回一格
退回前一天时,星期的数字减 1。周一至周五(1-5)变为周日至周四(0-4),周一(1)变为周日(0),周日(0)变为周六(6)。星期为 * 时无需修改。
- 3
日期字段退回一格
按日期指定时同样减 1,15 日变为 14 日。但 1 日往回退会变成“上个月的最后一天”,而月末是 28 到 31 日,每个月不同,所以 cron 的一行无法准确写出。
- 4
用下次执行时间核对
把修改后的表达式接下来约 10 次的执行时间按日本时间列出来,确认是否与原本的意图(例如周一早上 8 点运行)一致。心算的错误会在这里暴露出来。
只有一个时间时心算也能完成,但多个时间、星期和日期组合在一起时就很容易出错。cron 表达式检查器的“日本时间与 UTC 转换”中,输入按日本时间写的表达式,就会输出连星期和日期偏移都考虑在内的 UTC 表达式,并自动核对转换前后接下来 40 次的执行时间是否一致。
一行写不下时,把 - cron: 拆成两行
像日本时间“工作日 7:00 和 12:00”这样,9 点之前和之后的时间混在一起时,换算成 UTC 后只有 7:00 会退回前一天。退回前一天的时间和不退回的时间,星期字段不同,所以无法合并成一个 cron 表达式。
| 日本时间 | UTC 时间 | 星期字段 | 在 GitHub Actions 中写的表达式 |
|---|---|---|---|
| 工作日 12:00 | 3:00(同一天) | 保持 1-5 | 0 3 * * 1-5 |
| 工作日 7:00 | 22:00(前一天) | 退回为 0-4 | 0 22 * * 0-4 |
GitHub Actions 的 on.schedule 可以并列多个 cron,所以这种情况下分成“- cron: '0 3 * * 1-5'”和“- cron: '0 22 * * 0-4'”两行即可。只要符合其中任何一个时间,工作流就会运行。如果想在任务中知道是哪个时间触发的,github.event.schedule 里存有当次触发的 cron 字符串,可以用 if 条件分支处理。
schedule 延迟或不运行的原因与对策
即使正确换算成了 UTC,GitHub Actions 的 schedule 也未必会在“指定时间准点”运行。GitHub 的文档也说明,负载高的时段可能会延迟,繁忙时排队中的任务也可能被丢弃。下面整理常见原因。
| 现象 | 原因 | 对策 |
|---|---|---|
| 每次都延迟 5 到 30 分钟左右 | 每小时 0 分是全世界 schedule 集中的时间 | 把分钟改成 0 以外(如 17 分或 43 分) |
| 偶尔整整一次没有运行 | 繁忙时可能被从队列中丢弃 | 让任务即使漏跑也无妨;重要处理考虑另用其他机制 |
| 写“*/1”“*/3”也只有每 5 分钟运行一次 | schedule 最短间隔为 5 分钟 | 需要短于 5 分钟的间隔时改用其他调度器 |
| 放在分支上却不运行 | schedule 只读取默认分支最新提交中的文件 | 合并到 main(默认分支) |
| 放置一段时间后停了 | 公开仓库连续 60 天没有活动,schedule 会被停用 | 在 Actions 标签页重新启用工作流 |
| 在 fork 中不运行 | fork 出来的仓库往往默认停用 Actions | 在 fork 一侧启用 Actions |
效果最明显的是“错开分钟”。如果不必在 9:00 整点运行,只要写成相当于日本时间 9:17 的“17 0 * * *”,就能避开 0 分的集中,延迟会明显缩短。反过来,必须在上班前完成的处理,最好预留延迟,提前 30 分钟到 1 小时设定。
设置后要确认:手动运行和检查下次执行时间
schedule 要等到下一个时间点才知道能否正确运行。每日或每周的工作流,发现错误可能要好几天。在 push 前后确认以下三点,可以减少返工。
- 在 on: 下同时加上 workflow_dispatch:,先从 Actions 标签页手动运行,确认任务内容能成功
- 在 cron 表达式检查器中选择 GitHub Actions 并粘贴表达式,按日本时间列出下次执行时间,检查星期和时间是否符合意图
- 第一次运行结束后,把运行记录中的时间(UTC)加 9 小时,对照是否在预想的日本时间运行
如果对 cron 表达式本身的写法(5 个字段和 */5 等符号)没有把握,可以在crontab 的写法与速查表中复习基础。像“每月第 2 个周一”“月末”这样只靠 GitHub Actions 的 cron 难以表达的指定,在cron 日期与星期的 OR 规则及第 n 个星期几、月末的写法中介绍了在命令一侧判断日期的方法。在 AWS 上实现同样的功能,请参阅EventBridge cron 表达式的写法。
常见问题
- 怎样让 GitHub Actions 的 cron 在日本时间 9 点运行?
- GitHub Actions 的 cron 按 UTC 解读,所以要指定比日本时间早 9 小时的 UTC 0:00。每天就是“0 0 * * *”,只在工作日就是“0 0 * * 1-5”。日本时间 9 点以后日期和星期都不会偏移,只需把小时减 9。
- GitHub Actions 的 schedule 能把时区设为日本时间吗?
- cron 表达式按 UTC 解读,基本做法是把日本时间换算成 UTC 再写。如果只想让任务中使用的日期和时间变成日本时间,可以在步骤或任务的环境变量中设置 TZ: Asia/Tokyo。这只影响 date 命令等的显示,不会改变计划的时间。
- 日本时间早上 8 点换算成 UTC 后,星期会怎样?
- 日本时间 8:00 是 UTC 前一天的 23:00,所以星期也要退回一格。工作日(周一至周五)8:00 在 UTC 中是周日至周四的 23:00,写成“0 23 * * 0-4”。如果只改小时写成“0 23 * * 1-5”,就会在日本时间周二至周六的早上运行。
- 为什么 GitHub Actions 的 schedule 不按时运行?
- 每小时 0 分全世界的工作流都会集中,可能延迟几分钟到几十分钟,繁忙时甚至被跳过。把分钟字段改成 0 以外(如 17 分)能减少延迟。另外也请确认:schedule 只读取默认分支的文件;公开仓库连续 60 天没有更新会被停用。
- GitHub Actions 的 cron 能每分钟运行吗?
- 不能。schedule 的最短间隔是 5 分钟。即使写成“* * * * *”或“*/1 * * * *”,也不会以短于 5 分钟的间隔运行。需要按分钟监控等场景,请使用外部监控服务或常驻服务器上的 cron 等其他机制。
本文介绍的工具
相关文章
crontab 的写法与速查表:5 个字段与符号的含义、常用示例和注册命令
“每天 3 点备份”“每 5 分钟运行一次监控脚本”这类定时任务,在 Linux 上只要在 crontab 里写一行就能设置。不过“分 时 日 月 星期”这 5 个字段与 * , - / 这些符号的组合,刚开始很容易看错,常会出现 1 小时运行 60 次、本想每 7 分钟一次却出现 4 分钟间隔之类的事故。本文以示例为主,讲解 crontab 写法的基础和符号含义、可直接套用的速查表、crontab -e、-l、-r 的用法,以及编写命令部分时的注意事项。
阅读文章 →cron 的日期与星期是 OR 关系:第 2 个周一、月末、最后一个周五、隔周的正确写法
想表示“每月第 1 个周一 9 点”而写成“0 9 1-7 * 1”,结果除了每周一之外,每月 1 到 7 日每天都运行了。cron 中这个非常常见的失误,源于 cron 特有的规则:同时指定日期和星期时,只要符合“其中之一”就会运行。本文结合实际执行日期的示例,讲解日期与星期构成 OR 的机制及其例外,在 crontab 中正确运行第 n 个星期几、月末、最后一个周五、隔周的写法,以及 AWS EventBridge 和 Spring 中可用的 L、#、W 的写法。
阅读文章 →EventBridge cron 表达式的写法:? 的含义、星期数字、日本时间的指定与从 crontab 改写
在 AWS 上定时运行 Lambda 或 ECS 任务时使用的 EventBridge cron 表达式,看起来和 Linux 的 crontab 很像,但字段数量、? 的用法、星期数字和时区都略有不同。按 crontab 的习惯写成“0 9 * * 1-5”会报错,直接照搬星期数字会差一天,时间按 UTC 解读又会差 9 小时,绊脚的地方接二连三。本文整理了 EventBridge cron 表达式的 6 个字段与 ? 的规则、星期数字、L、W、# 的用法、与 rate 表达式的区别、规则和 Scheduler 中日本时间的处理,以及与 crontab、Kubernetes、Spring、systemd 的改写对照表。
阅读文章 →