03 / ガイド
驚きを起こさない Cron 式の書き方
5 つのフィールドとその範囲、名前と数字、日・曜日フィールドの OR トラップ、日曜は 0 か 7 か、夏時間のギャップと重複、そしてデプロイ前に次回実行プレビューで検証する方法。
5 フィールドを左から右へ読む
古典的な cron 式はスペース区切りの 5 フィールドです。分(0–59)、時(0–23)、日(1–31)、月(1–12)、曜日(0–7)。各フィールドは単一の値、カンマ区切りのリスト、ハイフンの範囲、スラッシュ後のステップ、またはすべての値を意味するアスタリスクを受け付けます。
cron のバグの大半は珍しい構文ではなく、普通のフィールドの読み違いです。範囲の境界でリセットされるステップや、23 まで数える系で 12 時間制のつもりで書かれた時フィールドなどです。
- リストリストは離散的な値を選びます。分フィールドの 0,30 は毎時 0 分と 30 分に発火します。
- 範囲とステップ範囲はスパンを選びます。時フィールドの 9-17 は就業時間をカバーし、ステップはそれを間引きます。9-17/2 はスパン内で 2 時間おきに発火します。
- アスタリスクは全マッチアスタリスクは「このフィールドを無視」ではありません。すべての値に能動的にマッチし、2 つの日付フィールドが相互作用した瞬間に重要になります。
名前はドキュメント付きの数字
月と曜日フィールドは、数字のエイリアスとして 3 文字の英語名——JAN から DEC、SUN から SAT——を受け付けます。0 9 * JAN MON と 0 9 * 1 1 は同じスケジュールですが、名前版は次の読者に自己説明し、オフバイワンの編集にも耐えます。
- 大文字小文字を区別しない名前のパースは大文字小文字を区別しません。mon、Mon、MON はすべて通りますが、意図的に見える慣例は大文字です。
- 範囲内の名前名前は範囲やリストの中でも使えます。MON-FRI が標準的な平日スパンで、JAN,APR,JUL,OCT は四半期の月を選びます。
- 説明を読むCron 作成・説明ツールは式を正規化し、フィールドごとに説明します。「month: every value」「weekday: 1, 2, 3, 4, 5」のように、パーサーが名前を意図どおり読んだか確認できます。
2 つの日付フィールドは OR で結合する
これが cron で最も高くつく驚きです。日と曜日の両方が限定されている——どちらもアスタリスクではない——とき、スケジュールはどちらかがマッチすれば発火します。0 9 13 * FRI は「13 日の金曜日」ではなく、毎月 13 日と毎週金曜日を意味します。
説明ツールの出力はこの点を明示します。意味論を dom-dow-or として計算済みの実行と並べて報告するため、意図よりはるかに頻繁に発火する式は crontab に入る前に正体を現します。
- 片方のワイルドカードは単純どちらかの日付フィールドがアスタリスクなら、もう一方が単独で支配します。多くの人がどこでも成り立つと思っている直感的な挙動です。
- 両方の限定は OR両方の限定は OR を意味します。古典的な Vixie cron の意味論で、ほとんどの Unix スケジューラもこれに従い、日がマッチするか曜日がマッチすればジョブが走ります。
- AND には工夫が必要本物の「13 日の金曜日」を狙うには、cron に 13 日を選ばせ、コマンド自身に曜日を確認させるか、AND 意味論を持つスケジューラを使います。古典的 cron は後者ではありません。
日曜は 0、たいてい 7 でもある
曜日フィールドは日曜を 0、月曜を 1、土曜を 6 と数えます。多くの実装は月曜から数える人のために 7 も 2 つ目の日曜として受け付けます。Toolars のパーサーは内部的に 7 を 0 に正規化します。ただし片方しか受け付けない系もあり、月曜を 1 とする ISO 式の番号付けも別の場所には存在します。
- 0 と 77 が受け付けられる場所では 0 と 7 はどちらも日曜です。0 と書くのがスケジューラ間で移植性のある選択です。
- 静かなオフバイワン7 を拒否する系で曜日に 7 と書けばデプロイ時に明示的に失敗しますが、「月曜から数えて 7 日目」と読み違えると、間違った曜日に静かに失敗します。
- プラットフォームごとに再確認式が実装間を移るとき——システムの crontab、コンテナ基盤、CI スケジューラ——番号付けが無事に旅を終えたと想定せず、曜日の番号を再確認してください。
夏時間はスケジュールを歪める
cron はスケジューラのタイムゾーンの壁時計時刻で発火し、その壁時計は年に 2 度おかしくなります。春の切り替えのギャップでは 02:30 のようなローカル時刻が存在せず、秋の戻りではその時刻が 2 度存在します。その分に固定されたスケジュールは 3 月に 1 回を逃し、11 月には 2 回走ることがあります。
- 消える 1 時間春のギャップ:切り替えの夜の 02:30 のジョブにはマッチする壁時計時刻がなく、何も発火しません。エラーもリトライもなく、ただ欠けた実行があります。
- 倍になる 1 時間秋の重複:同じ壁時計時刻が 2 度来ます。ジョブが 1 回走るか 2 回走るかは実装次第です。検証していないなら 2 回と想定してください。
- UTC に夏時間はないきれいな逃げ道は UTC です。夏時間の切り替えがないため、30 2 * * * は一年中どの日も同じ瞬間を意味します。説明ツールは UTC、デバイスのローカルゾーン、America/New_York や Europe/London などの名前付きゾーンを提供します。
希望的観測ではなく次回実行で検証する
正しい式とそれらしく見える式の違いは、具体的な発火時刻のリストです。Cron 作成・説明ツールは選択したタイムゾーンで今からの次の 10 回の実行を計算し、最大 366 日先まで探索します。バックグラウンドワーカーで評価するため、重い式でもタブは止まりません。
- 実行を読む最初の数回の実行を意図と照合します。「平日の 09:00」なら月曜から金曜の 9 時が並ぶはずで、そこに土曜がいれば OR トラップが名乗り出ています。
- 切り替えをまたぐ夏時間の境界をわざとまたぎます。名前付きゾーンで 3 月や 11 月の日付を選び、時計が動いてもスケジュールが期待どおりか確認します。
- 明示的なエラー失敗は明示的です。不正なフィールド、フィールド数の誤り、探索ウィンドウ内に 10 回の実行を作れないスケジュールは、静かな空リストではなくそれぞれ名前付きのエラーになります。
スケジュールとパターンは同じ規律を共有します。
どちらも影響半径の大きい小さな文字列です。正規表現のための write-test-refactor ループを、タイムアウト付きのローカルワーカーで学びましょう。