跳至內容
Toova
所有工具

Cron 表達式語法解析

Toova

Cron 表達式是那種開發者學一次、忘掉細節、然後每六個月又得回來查的主題之一。像 30 9 * * 1-5 這樣的字串能編碼出意外具體的排程 — 「每個工作日早上 9:30」 — 但語法剛好有夠多的邊界情況會造成真正的混亂:0 起始與 1 起始的欄位、星期日同時是 0 與 7、Unix 標準與 Quartz 六欄位擴充的差異,以及步進值與範圍結合時的微妙行為。

本指南從基本原理涵蓋 cron 語法,然後以具體範例逐一解說每個特殊字元。不論你是為 Linux 守護程序撰寫 cron 工作、設定 GitHub Actions 排程,還是建立 AWS EventBridge 規則,相同的底層概念都適用 — 不過途中會解說幾項重要的平台差異。

五個標準欄位

標準的 cron 表達式有五個以空格分隔的欄位,從左到右閱讀:分鐘、小時、月中第幾日、月份、星期幾。每個欄位都會限制工作沿該維度執行的時間。工作只在五個欄位同時匹配時才執行。

# 欄位       位置  範圍         特殊字元
# ─────────────────────────────────────────────────
# 分鐘            1      0-59          * / , -
# 小時            2      0-23          * / , -
# 月中第幾日      3      1-31          * / , - ? L W
# 月份            4      1-12 或 JAN-DEC   * / , -
# 星期幾          5      0-7(0=7=星期日)或 SUN-SAT  * / , - ? L #

解讀 30 9 * * 1-5 這樣的表達式:第 30 分鐘、第 9 小時、任意日期、任意月份、星期幾為 1 到 5(週一到週五)。結果:每個工作日上午 9:30。

欄位 1:分鐘(0–59)

分鐘欄位指定工作應在小時的哪些分鐘執行。0 表示整點,30 表示半小時,59 表示小時換值前一分鐘。使用 * 則讓工作每分鐘執行。

欄位 2:小時(0–23)

小時使用 24 小時制。0 是午夜,12 是中午,23 是晚上 11 點。Cron 中沒有 AM/PM 之分 — 上午 9 點的工作為小時 9,晚上 9 點的工作為小時 21

欄位 3:月中第幾日(1–31)

與其他欄位不同,月中第幾日是從 1 開始,而非 0。有效值為 1 至 31。如果你指定某個月份不存在的日期(例如四月的 31 日),工作該月就不會執行。在 Quartz 中,特殊字元 L 代表當月最後一天,不論該月有多少天。

欄位 4:月份(1–12)

月份值從 1(一月)到 12(十二月)。許多排程器也接受三字母縮寫:JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC。多數實作不區分大小寫。

欄位 5:星期幾(0–7)

這是擁有最多歷史包袱的欄位。07 都代表星期日 — 這是來自早期 Unix 的相容性怪癖。星期一是 1、星期二是 2、直到星期六是 6。三字母名稱也可用:SUNMONTUEWEDTHUFRISAT

當月中第幾日與星期幾都被指定(兩者都非 *)時,多數 Unix cron 守護程序會使用 OR 邏輯:工作會在「任一」欄位匹配時執行。這讓許多開發者感到意外。如果你想要「每月 15 日,但只在工作日」,你必須在工作腳本本身處理該邏輯,而非在 cron 表達式中。

特殊字元

星號(*) — 每個值

星號匹配欄位的所有有效值。在分鐘欄位中,* 表示分鐘 0 到 59。在月份欄位中,表示所有 12 個月份。它是 cron 表達式中最常見的字元。

斜線(/) — 步進值

斜線定義步進間隔。分鐘欄位中的 */5 表示「每 5 分鐘」。更精確地說,它表示「該範圍中可被 5 整除的每個值,從範圍的第一個值開始」。因此分鐘欄位中的 */5 會評估為 0、5、10、15、20、25、30、35、40、45、50、55。

步進可以與範圍結合:分鐘欄位中的 10-30/5 表示在第 10 分鐘與第 30 分鐘之間每 5 分鐘執行,產出 10、15、20、25、30。步進從範圍下限開始。

逗號(,) — 值列表

逗號建立特定值的列表。月份欄位中的 1,3,5 表示一月、三月與五月。小時欄位中的 9,17 表示 9:00 與 17:00。你可以列出所需數量的值,並與其他結構結合。

連字號(-) — 範圍

連字號指定包含範圍。星期幾欄位中的 1-5 表示星期一到星期五。小時欄位中的 9-17 表示上午 9 點到下午 5 點。範圍兩端都包含。

問號(?) — 無特定值(僅 Quartz)

標準 Unix cron 不支援 ?。在 Quartz Scheduler 中,它用於月中第幾日或星期幾欄位,表示「我不在乎這個欄位」。由於 Quartz 不一定能解決月中第幾日與星期幾之間的衝突,當其中一個被指定時,另一個必須設為 ?0 0 15 * ? 表示「每月 15 日,任意星期幾」。0 0 ? * MON 表示「每個星期一,任意日期」。

L — 最後(僅 Quartz)

月中第幾日欄位中的 L 表示當月最後一天。星期幾欄位中的 L 表示星期六,或當與數字結合時(例如 5L),表示當月該星期幾的最後一次出現。5L 是當月最後一個星期五。

W — 最近工作日(僅 Quartz)

月中第幾日欄位中的 W 會找到距指定日期最近的工作日(週一至週五)。15W 表示距 15 日最近的工作日。若 15 日是星期六,工作會在 14 日(星期五)執行。若是星期日,則在 16 日(星期一)執行。W 不會跨越月份邊界。

井號(#) — 月中第 N 個星期幾(僅 Quartz)

# 指定月中星期幾的第 N 次出現。5#2 表示當月第二個星期五。2#1 表示第一個星期二。如果指定的次數在該月份不存在,工作該月就不會執行。

常見的 Cron 模式

# 每分鐘
* * * * *

# 每天午夜(00:00)
0 0 * * *

# 每天上午 9:30
30 9 * * *

# 每週一上午 8:00
0 8 * * 1

# 每月第一天中午
0 12 1 * *

# 每 15 分鐘
*/15 * * * *

# 工作日下午 6:00
0 18 * * 1-5

你可以使用 Cron 解析器工具測試與驗證這些任一表達式,它會顯示任何表達式接下來五次的執行時間,並標示出無效語法。如果你需要確認特定 Unix 時間戳在你本地時區對應什麼時間,請使用時間戳轉換工具

6 欄位擴充:Quartz Scheduler

Quartz Scheduler — 在 Java 生態系中廣泛使用,且被許多企業排程器採用 — 在表達式最前面新增了強制性的秒欄位。格式變為:秒 分鐘 小時 月中第幾日 月份 星期幾

# Quartz 6 欄位:second minute hour day month weekday
# 在第 00 秒第 30 分鐘,每小時執行
0 30 * * * ?

# 每天午夜執行
0 0 0 * * ?

# 每 5 秒執行
0/5 * * * * ?

# 每月最後一天上午 10:00
0 0 10 L * ?

# 每月最後一個星期五下午 3:00
0 0 15 ? * 6L

與標準 cron 的關鍵差異:秒欄位(0–59)位於最前面且為必要;星期幾使用 1(星期日)到 7(星期六),而非 0–6;當月中第幾日或星期幾其中之一被指定時,另一個必須使用 ?;LW# 是有效的特殊字元。

切勿在不移除秒欄位並調整星期幾編號的情況下,將 Quartz 表達式貼到 Unix crontab 中。這些解析器並不互通。

不同系統中的 Cron

Linux/Unix crontab(Vixie cron)

原始且最常見的形式。五個以空格分隔的欄位,後接命令。CRON_TZTZ 等環境變數用於設定時區。使用者 crontab 透過 crontab -e 編輯;系統 crontab 位於 /etc/cron.d/@reboot@daily@hourly@weekly@monthly 捷徑被廣泛支援,作為常見模式的別名。

GitHub Actions

GitHub Actions 使用標準 5 欄位 cron 語法,但時區永遠是 UTC — 表達式本身無法指定本地時區。表達式在儲存庫層級進行評估,而非每個工作個別評估。請注意,在 GitHub 基礎設施高負載期間,排程的工作流程可能延遲數分鐘。

# GitHub Actions 排程語法(僅 5 欄位 UTC)
on:
  schedule:
    # 每天 UTC 02:30 執行
    - cron: '30 2 * * *'
    # 每週一 UTC 09:00 執行
    - cron: '0 9 * * 1'

官方的 GitHub Actions cron 文件指出最小間隔為 5 分鐘 — 比這更頻繁的表達式會被默默忽略。

AWS EventBridge(CloudWatch Events)

AWS EventBridge 同時支援頻率表達式(rate(5 minutes))與 cron 表達式。其 cron 變體為 6 欄位,但與 Quartz 不同:星期幾使用 Sun–Sat 名稱或 1–7(1 為星期日),所有時間為 UTC,且月中第幾日或星期幾其中之一必須為 ?。AWS 不支援 W# 字元。

# AWS EventBridge(UTC 中的 cron,6 欄位變體)
# 每天 UTC 上午 10:00 執行
cron(0 10 * * ? *)

# 每月最後一個工作日下午 6:00 執行
cron(0 18 L-1 * ? *)

Kubernetes CronJob

Kubernetes CronJob 使用標準 5 欄位 cron 語法。時區預設為 kube-controller-manager 行程的時區(在受管理的叢集上通常為 UTC)。Kubernetes 1.25 為 CronJob 規格新增了 timeZone 欄位,允許每個工作的時區設定,不必仰賴系統時區。

Node.js(node-cron / cron npm)

熱門的 node-cron 套件支援標準 5 欄位表達式,並可選擇在最前面加上第 6 個秒欄位。cron 套件則相容於 Quartz。兩者都支援時區字串(例如 America/New_York)作為建構函式選項。請查閱特定套件的文件,以確認它預期哪種格式。

常見陷阱

時區假設

Cron 以伺服器或容器時區執行,在雲端環境中通常為 UTC。原本想要「上午 9 點商業時間」執行的工作若設為 0 9 * * *,會在 UTC 上午 9 點執行 — 依時區偏移與夏令時間,在你本地可能是凌晨 4 點、5 點或上午 11 點。請務必設定 CRON_TZ、使用排程器的時區欄位,或明確將目標時間轉換為 UTC。時區轉換工具能幫你跨任何時區找出任何本地時間的 UTC 等價值。

月中第幾日 / 星期幾的 OR 邏輯

在 Unix cron 中,當月中第幾日與星期幾欄位都非 * 時,守護程序會在「任一」匹配時執行工作。因此 0 9 15 * 1 會在每月 15 日「且」每個星期一執行 — 而非只在恰好是 15 日的星期一執行。要實現 AND 邏輯,你必須在工作腳本內實作額外檢查。

混淆步進值與範圍

*/5 表示「從 0 開始每 5 分鐘」(0、5、10 ...)。5/5 表示「從 5 開始每 5 分鐘」(5、10、15 ...)。5-30/5 表示「在第 5 與第 30 分鐘之間每 5 分鐘」(5、10、15、20、25、30)。若你對起始值不夠謹慎,將這些與範圍混用會產出意外結果。

「每秒」陷阱

Cron 在標準 5 欄位格式中的最小解析度為一分鐘。你無法使用 crontab 排程每秒或每幾秒執行的 cron 工作。對於次分鐘排程,請使用語言層級的計時器(Node.js 的 setInterval、Python 的 APScheduler)或專用佇列系統,而非 cron。如果你需要每 30 秒,可執行兩個 cron 工作:一個是 * * * * *,另一個則在執行前先睡 30 秒。

遺漏月份編號

在標準 cron 中,月份從 1(一月)到 12(十二月)。在某些程式語言的日期 API 中,月份從 0 起始(0 = 一月、11 = 十二月)。若你以程式從日期物件產生 cron 表達式,請仔細檢查月份欄位是否差一。

驗證你的表達式

Toova 的 Cron 解析器會顯示任何表達式接下來五次的排定執行時間,同時支援 5 欄位與 6 欄位 Quartz 語法,並以行內方式標示無效值。若需快速合理性檢查,crontab.guru 是個知名的互動工具,能以通俗英文描述表達式。兩者都值得收藏。

處理時間戳與排定時間時,時間戳轉換工具會跨任何時區在 Unix 時間戳與人類可讀日期之間轉換。對於涉及時區敏感排程的工作,在撰寫最終表達式前,請將其與時區轉換工具搭配使用,以驗證偏移、夏令時間轉換與 UTC 等價值。