雜記 › 技術筆記 › Crontab 表達式 0 0 13 * 5 到底哪天跑:這一頁只排 2 次,真實 cron 是 62 次

Crontab 表達式 0 0 13 * 5 到底哪天跑:這一頁只排 2 次,真實 cron 是 62 次

同一條 0 0 13 * 5,這一頁按交集只排 2 次,真實 cron 的聯集是 62 次;短月 31 號被跳過,永不存在日期掃滿 527040 分鐘才返回空。

同一條 crontab,測試頁和線上跑在不同日子

接手一台閒置機器時見過這樣一行:0 0 13 * 5,註釋寫著「每月 13 號週五跑一次 demo-report.csv」。把這行丟進解析頁,第一條預測時間是 2024-09-13;可執行日誌裡每個週五都躺著一行記錄。同一個表達式,一邊說 2024 年只有 2 次,一邊真實跑了 62 次——中間差的 60 次不是運氣,是兩套語義。

這一頁的輸入只有一行文字。我把 Crontab解析 範本裡的 nextRun 與 parseField 逐字抽出來,起點釘死在 2024-01-01 00:00:00 逐分鐘往後走,量到兩個語義空洞:日和週同時限定時的交集與聯集,以及短月、永不存在日期下的空返回。

空洞一:五個欄位被 && 串成一串

命中判定寫在範本 240–244 行,條件 f.minute && f.hour && f.day && f.month && f.week 是五段全交集,而真實 vixie-cron 在「日」與「週」都不是 * 時走「或」。給 2024 年算一筆帳:13 號有 12 天,週五有 52 天,既是 13 號又是週五只有 2 天,聯集是 12 + 52 - 2 = 62,交集只剩那 2 天,頁面等於把 62 次壓成 2 次、差 60 次。

描述文字也站在交集這邊:它給出的自然語言是「在每小時的第0分鐘0點每月13日週五執行一次」,讀起來是「且」,與 nextRun 自洽,卻一起偏離 cron 本身。五個欄位各自是什麼、介面怎麼示範,站內 Crontab 表達式看不懂?線上解析並預測下次執行時間 講透了,這裡只補它沒量過的分支。

同一個 0 0 13 * 5,2024 年命中幾次 本頁 nextRun 交集 2 次 真實 vixie-cron 聯集 62 次 12 個 13 號 + 52 個週五 - 2 個交集 = 62

圖 1:兩種讀法在同一年窗口裡的命中數。

只有一個欄位受限時,兩套語義合流

表達式本頁給出的前幾次(上海時區)歧義
0 0 * * 12024-01-08、01-15、01-22、01-29、02-05,全是週一 00:00無,只有週受限
0 0 1 * *2024-02-01、03-01、04-01、05-01、06-01無,只有日受限
0 0 13 * 52024-09-13、2024-12-13、2025-06-13、2026-02-13、2026-03-13、2026-11-13、2027-08-13有,第 8 次返回空

前兩行只限定一個欄位,交集聯集重合、鏈條規矩;日和週同時收窄時,第一次命中直接推到 2024-09-13,而線上每個週五照跑。

空洞二:日曆本身不給那一天

0 0 31 * * 從 2024-01-01 起鏈 12 次,只命中 7 個月:1、3、5、7、8、10、12 月,2、4、6、9、11 月被直接跳過。從 2 月往後問下一次,它答 2024-03-31,不報錯也不解釋;閏年 2024 同樣是 7 個含 31 號的月份。

更極端的是 2 月 30 日。0 0 30 2 * 欄位值與欄位數都合法,解析一路通過,nextRun 老老實實掃滿 366 × 24 × 60 = 527040 分鐘再返回空,實測單次約 58 毫秒,不死迴圈也不拋異常,介面只留一句「未找到下次執行時間(一年內)」。0 0 31 2 * 走同一條路。

這個上限還會誤傷活著的排程:0 0 13 * 5 跑到第 7 次 2027-08-13 之後,下一個「既是週五又是 13 號」落在 2028-10-13,距上一次超過 366 天,於是第 8 次同樣顯示未找到——稀疏不等於不存在,但頁面上這兩種情況文案一模一樣。另一條靜默路徑是反向區間 8-1:欄位被解析成空集合且不報錯,此後無論怎麼問都只返回空。

報錯文案:英文名一律不是名字

輸入頁面拋出的原文
SUN/sun/Sat/FRI無效的值: SUN(大小寫都一樣,沒有名稱表)
週欄位寫 8值 8 超出範圍 [0-7]
61 * * * *值 61 超出範圍 [0-59]
0 0 0 * *值 0 超出範圍 [1-31](日的下限是 1)
*/0 * * * *無效的步長: */0
四段或六段表達式必須為5個欄位(分 時 日 月 週)

週欄位裡 7 與 0 等價,parseCron 第 145 行把 7 歸成 0,所以 0 0 * * 7 與 0 0 * * 0 同解;可 FRI 這種真實 cron 認得的縮寫,在這裡只是 parseInt 出來的一個 NaN。欄位數校驗排在拆分之後,於是 0 0 1, * * * 這種「逗號後多打一個空格」被拆成六段,報的仍是那句五欄位提示。

預測還隨環境漂

nextRun 讀的是本地時鐘(getHours、getDate、getDay),第 229 行先把秒歸零,輸入裡帶的秒被丟掉;起點還是獨占的,after 正好壓在命中時刻上時不會把自身再報一遍。我把起點固定成上海時區的 2024-01-01 00:00,換算成毫秒就是 時間戳轉換 給的 1704038400000,免得每跑一次換個結果。

日光節約時間是最直觀的一刀:本機解析出的時區是 Asia/Shanghai,沒有日光節約時間,必須強制 TZ=America/New_York 才觸發得到。那一檔裡 0 2 * * * 從 2024-03-08 起命中 2024-03-08、2024-03-09、2024-03-11、2024-03-12——春季前撥把 3 月 10 日的 02:00 整個抹掉,預測從來不是純表達式決定的。

這一頁自己的體量

順帶量一下:範本 337 行、13855 位元組,7 個 id、14 個 button、1 個 input,邏輯全在前端。解析按鈕固定循環 10 趟去產生「未來 10 次執行時間」,每趟都自帶那 527040 分鐘上限,任何一趟返回空就當場收尾,所以稀疏的 0 0 13 * 5 只列出 7 條。

怎麼把兩套語義落回同一條

要「每月 13 號或每週五」,別指望一行表達式兩全,拆成 0 0 13 * * 與 0 0 * * 5 兩行,把聯集交給排程器;要「13 號恰好是週五」,就讓定時只管觸發、腳本自己判日期,此時那 2 次預測正好對得上。驗證的方法很笨但有效:把頁面前幾次命中的星期和日子逐個唸一遍。

常見疑問

Q:這一頁給的下次執行時間能直接照抄線上嗎?

只限定日或只限定週時可以,兩種讀法在那時重合;兩段同時收窄就不行,本頁算交集,線上算聯集。

Q:出現「未找到下次執行時間(一年內)」是不是任務永遠不跑?

不一定。永不存在日期 0 0 30 2 * 與合法的稀疏排程都會走到這句話,前者是日曆裡沒有那天,後者只是下一個命中點超出 366 天的遍歷上限。

Q:週日寫 7 還是寫 0?

在這頁等價,第 145 行把 7 歸 0;但 SUN、sat 一律按無效值拋錯,別把真實 cron 的縮寫習慣帶進來。

📎 相關文章

貼上 100 行分數,第 45 行被悄悄算成 1/4:約分這頁的負號、空行和大數各歸誰管 2026-11-03 把 3. 起步貼進 Markdown 轉 HTML,為什麼有序列表的編號只認第一個數 2026-11-03 文字切成 43 片再拼回去少了 42 個位元組,切口其實都落在換行上 2026-11-03 16 組開關實測 HTML 去標籤:報 19 個標籤,實際刪掉 148 個字元 2026-10-30 SVG 轉 PNG 後外部引用的圖丟了一半:同一份 594 B 檔案預覽有 3 張圖、成品只剩 1 張 2026-10-30