cron式
Cron式パーサーは、スケジュールを日本語で解説し、次の10回の実行予定を一覧表示します。すべてブラウザ内で動作します。そして、ほぼ全員が引っかかるルールをはっきり述べます。日と曜日の両方を指定した場合、cronはそれらをORで結合します。つまり0 0 13 * 5は、13日または任意の金曜日に発火するのであって、13日の金曜日に発火するのではありません。
cron式はどう動きますか?
Cronはわずか5つの数字なのに、決まって読み間違えられます。そのほとんどは、3つのポイントで説明がつきます。
2つの日フィールドはANDではなくOR
それ以外のフィールドのペアはすべて共通部分を取ります。0 9 * 6 *は6月の9時であり、時と月が交差します。日フィールドは違います。日と曜日の両方を指定した場合、どちらか一方が一致すればジョブは実行されます。つまり0 0 13 * 5は、毎月13日と毎週金曜日の両方で発火します。月に約5回であり、年に1、2回ではありません。これはPOSIXにもVixie cronのマニュアルページにも書かれているのに、今なお人々を驚かせます。だからこそ、ここの解説では各フィールドを個別に述べるのではなく、この挙動をはっきりと明示しています。
2つのうち片方が*なら、もう片方だけで決まります。だから0 0 * * 5は、単純に毎週金曜日です。
次回実行時刻の計算は、必ず停止する探索
0 0 31 2 *は2月31日の真夜中を求めていますが、それは永遠に来ません。1分単位の走査では、1年の上限に達して諦めるまでに260万回の反復が必要です。上限のない走査なら、永遠に諦めません。このツールの探索は、排除できる最大の単位でスキップします。一致しない月は、その日数を順に見るのではなく、翌月1日へ進みます。そのため、実行不可能なスケジュールは数百ステップで不可能と証明されます。
あえて、すべてUTC
サマータイムのあるタイムゾーンでスケジュールされたcronジョブは、実際に年1回ある夜に2回実行され、別の夜にはまったく実行されません。時計が戻ると01:30は2回到来し、進むと02:30は到来しません。これの扱いはcron実装ごとに異なり、まったく対処していない実装もあります。UTCで計算し、その旨を明記するのが誠実です。黙ってブラウザのタイムゾーンを適用すると、もっともらしく見えて年2回は間違う時刻ができあがります。
フィールド数と方言
5フィールドは標準のUnix cronです。6フィールドはたいてい先頭に秒が付く形で、Quartz、Spring、node-cronがそうです。ただし、年を最後に置くスケジューラも少数あり、これは本当に曖昧です。このツールは秒ありの形を採用します。7フィールドはQuartzの、秒と年の両方がある形です。パーサーは月名と曜日名、FRI-MONや22-2のような折り返し範囲、ワイルドカードの?、日曜を表す0と7の両方、そして@daily系のショートハンドを受け付けます。
@rebootは解析対象ではなく、認識して説明するものです。スケジュールを持たないため、計算すべき次回実行が存在しません。
式
0 0 13 * 5
解説
At 00:00, on day 13 of the month OR on Friday — cron ORs these two fields rather than intersecting them, so this is not "Friday the 13th". UTC.
cron式が対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| * | ワイルドカード | すべての値 | 日フィールドでは「他の日フィールドに決定させる」という意味もあり、これによりORルールが実用的になります。 |
| */n | ステップ | — | フィールドの最小値からn番目の値。分フィールドでの*/15は0, 15, 30, 45——「現在から15分ごと」ではありません。 |
| a-b | 範囲 | — | 範囲に含まれます。ラップする範囲も受け入れられます:時間フィールドでの22-2は22, 23, 0, 1, 2で、FRI-MONは週末です。 |
| a,b,c | リスト | — | 単一の値、範囲、ステップを任意に組み合わせ、カンマで区切ります。 |
| n/m | nからステップ | — | 時間フィールドでの5/6は5, 11, 17, 23——5から最大値まで、ステップ6で。 |
| 名称 | JAN-DEC, SUN-SAT | 受け入れられる | 大文字・小文字の区別なし、範囲でも使用可能:JAN-MAR, MON-FRI。 |
| 0と7 | 日曜日 | 両方 | すべての実装で両方が受け入れられますが、ほとんどのドキュメントでは他の記述と同じ場所では言及されていません。 |
| ? | ワイルドカード | Quartz | *として扱われます。Quartzは日フィールドで「特定の値なし」を意味するために使用します。 |
| フィールド数 | 5, 6または7 | 5 | 5は標準のUnix形式です。6は秒を先頭とする読み方で、これはQuartz、Spring、node-cronの意味です。7は年を含むQuartz形式です。 |
| @dailyとその仲間 | 省略形 | 展開される | @yearly @annually @monthly @weekly @daily @midnight @hourly。@rebootはスケジュールではなく説明されるもので、解析されません。 |
| タイムゾーン | UTC | 常に | ローカル時刻のスケジュールは、年に一度の夜に実際に2回実行されたり、全く実行されなかったりします。UTCで計算し、そのように記述する方が、確実に間違ったローカル時刻を生成するよりも優れています。 |
よくある質問
0 0 13 * 5は13日の金曜日に実行されますか?
いいえ。毎月13日と、毎週金曜日に実行されます。cronは2つの日フィールドを交差させるのではなくORで結合します。これは他のどのフィールドのペアとも逆の挙動で、cronにおける最もよくある単一の誤解です。13日の金曜日を狙うには、日の指定に加えてジョブ自身の中でチェックが必要です。cronでは共通部分を表現できません。
*/15は実際はどういう意味ですか?
フィールドの最小値から始めて、そのフィールド内の15で割り切れるすべての値です。分なら0、15、30、45です。「デプロイした時刻から15分ごと」という意味ではありません。この違いは時間指定と組み合わせたときに効いてきます。0 */2 * * *は00:00、02:00、04:00…に実行されるのであって、前回の実行から2時間後に実行されるのではありません。
時刻がすべてUTCなのはなぜですか?
ローカル時刻のスケジュールは、1年に2回、本当に曖昧になるからです。時計が戻ると01:30が2回到来し、実装によってはジョブが2回走ります。進むと02:30は到来せず、実装によっては丸ごとスキップされます。挙動はcron実装の間で異なります。UTCを表示し、その旨を伝えるのが誠実です。黙ってブラウザのタイムゾーンを適用すると、もっともらしく見えて年2日は間違う時刻ができあがります。
フィールドは5つ、6つ、それとも7つ?
Unix cron、crontab、大半のCIスケジューラは5つです。Quartz、Springの@Scheduled、node-cronは6つで、追加フィールドは先頭に入り、秒を表します。Quartzで最後に年を付ける場合は7つです。6フィールドは本当に曖昧です。年を最後に置くスケジューラも少数あるためです。このパーサーは、はるかに一般的な秒ありの形として読みます。
式を入力しても実行予定が1つも表示されないのはなぜですか?
その式は永遠に発火しないからです。よくある原因は、存在しない日と月の組み合わせ(0 0 31 2 *は2月31日を要求しています)、またはすでに過去の年を指定していることです。この結論を出す前に、探索は5年先まで見ます。つまり一覧が空というのは、単に先が遠いのではなく、本当に実行不可能だという意味です。
入力した式はアップロードされますか?
いいえ。解析も次回実行時刻の探索も、ブラウザ内で動作します。cron式は秘密ではありませんが、あなたのインフラの稼働リズムを記述するものです。それが、何の役にも立たないサーバーへ届く理由はありません。