crontab からの引っ越しガイド——5 つの定番を Kairos に翻訳する

Kairos

はじめに——引っ越さなくていい人

昨日の 1.0 宣言から来た方、はじめまして。この記事は crontab を持っている人向けの、いちばん実務寄りの入門である。

最初に断っておく。固定時刻の単純反復なら、cron で足りる。「毎時 0 分」「毎日 3 時」をKairos に書き換えるメリットはない。引っ越しを考えるのは、次のどれかに当たったときである——「月末」が書けない。「営業日」が書けない。サーバを移したら実行時刻がずれた。止まっていた間に何が飛んだか分からない。

以下、crontab の定番を 5 つ、順に翻訳する。

定番 1: 0 9 * * 1-5——平日 9 時

cron のこの行には、書かれていないことが二つある。どこの 9 時か(サーバの OS 設定任せ——移行事故の定番)と、祝日も動いてしまうこと(cron に祝日という概念はない)。Kairos ではどちらも定義が持つ:

premise JP {
  calendar-system: Gregorian; tz: "Asia/Tokyo"; wkst: Mon
  national = [2026-09-21, 2026-09-22, 2026-09-23, 2026-10-12, 2026-11-03, 2026-11-23] covering: 2026-08-01..2027-01-31
  satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
  bizDay = everyDay \ (satSun | national)
}

@JP
bizDay |> at(T09:00)

シルバーウィークを挟む 2 週間(9/18〜9/28)で実測すると:

2026-09-18T09:00
2026-09-24T09:00
2026-09-25T09:00
2026-09-28T09:00

9/21〜23(祝日)と土日が正しく休んでいる。「東京の 9 時」は定義のテキストに書いてあるので、サーバをどこへ移しても変わらない。前文(premise)の書き方は連載のpremise 入門に詳しい。以降の例はすべてこの前文を使い回す。

定番 2: 55 23 28-31 * *——月末 23:55

cron に「月末」は無いので、28〜31 日に毎晩起こして「明日は 1 日か?」を判定するスクリプトを先頭に置くのが定番ハックである。Kairos では判定ごと式に消える:

monthEnd |> at(T23:55)
#=> 2026-09-30T23:55  2026-10-31T23:55  2026-11-30T23:55  2026-12-31T23:55

定番 3: 給料日 25 日・土日祝なら前営業日——cron では書けない側

ここからが引っ越しの本命である。cron はこの要件の後半が書けない:

everyDay |> within(month) |> nth(25) |> roll(Preceding, on: bizDay)
#=> 2026-09-25  2026-10-23  2026-11-25  2026-12-25

10 月だけ 23 日なのは、10/25 が日曜だから。roll(寄せる)と shift(数える)の使い分けは前々回の基礎回で書いた。

定番 4: 月末最終営業日

bizDay |> within(month) |> last
#=> 2026-09-30  2026-10-30  2026-11-30  2026-12-31

10 月末(10/31 土)が金曜の 10/30 へ退いている。「営業日の列を作り、月ごとに最後の点を取る」——読んだままの構造である。

定番 5: 月末 3 営業日前

この連載の題になった式。祝日を挟んでも営業日の目盛りで正しく数える:

monthEnd |> roll(Preceding, on: bizDay) |> shift(-3, unit: bizDay)

運用の型——crontab に無かった三つの口

翻訳した定義は 1 ファイルに保存して、CLI で聞ける。

次はいつ動く?

$ kairos next -n 3 --from 2026-09-14 payday.kairos
2026-09-25
2026-10-23
2026-11-25

止まっていた間に何が飛んだ? サーバが 10/20〜10/31 に落ちていたとする:

$ kairos list --from 2026-10-20 --to 2026-11-01 payday.kairos
2026-10-23

10/23 の給料日振込バッチを撃ち漏らしていたことが、列挙で分かる(cron にこの口は無い——撃ち漏らしは列挙できる)。なお --from/--to は半開区間(--to の日は含まない)。この記法を選ぶ理由は第 13 弾で書いた。

じゃあ、どう動かす?——crontab は 1 行だけ残す

Kairos が答えるのは「いつ動くべきか」の集合までで、起動・リトライ・記録は実行系(systemd・ジョブランナー等)の仕事である——この分業は仕様に明文がある(spec §7.8)。ただ、それだけ言われても困るので、接続の型を 2 つ書いておく。どちらも今の CLI でそのまま動く(--json を付けると epochミリ秒込みの機械可読形で出る)。

型 1: cron を時計として残す。 crontab の行は 1 本だけ残し、判定を Kairos に移す。毎朝 9 時に「今日は点があるか」を聞いて、あれば起動する:

0 9 * * *  cd /srv/batch && ./run-if-today.sh payday.kairos ./payday.sh
#!/bin/sh
# run-if-today.sh <定義.kairos> <ジョブ> —— 今日 [今日, 明日) に点があればジョブを exec する
today=$(date +%F); tomorrow=$(date -d "$today + 1 day" +%F)
n=$(kairos list --from "$today" --to "$tomorrow" --json "$1" | jq '.results[0].dates | length')
[ "$n" -gt 0 ] && exec "$2"

祝日も tz も判定は定義側にあるので、crontab には曜日も日付も書かない。人間向けの出力には被覆サマリの# 行が混ざるため、判定は --json で数える(上の payday.kairos なら 9/14 は 0 件・9/25 は 1 件)。

型 2: 次の 1 点を予約する。 時刻も定義側に置き(nth(25) |> roll(Preceding, on: bizDay) |> at(T09:00))、next --json で次の発火を取る。壁時計と epoch ミリ秒の両方で返るので、OS の一回限りのタイマーに渡し、ジョブの最後で次を登録し直す:

t=$(kairos next --json payday.kairos | jq -r '.results[0].dates[0]')   # 2026-09-25T09:00
systemd-run --user --on-calendar="${t/T/ }:00" ./payday.sh
# Linux の at、Windows の schtasks /sc once でも同じ形

「点の列を定期的に取り直す」のが仕様の言う運用(§7.8)で、この 20〜40 行の型は別の回でまとめて書く。

データの話を最後に

例では祝日 6 個を定義に直書きした。実運用では祝日表は外部から供給する口(external)があり、式は静的・データは実行時という分業になる。どちらの場合もcovering:(この表はいつまで確認済みか)が必須で、表が尽きた先の結果には注記が付く——祝日テーブルは黙って腐るへの言語側の答えである。

まとめ——翻訳表

  • 0 9 * * 1-5bizDay |> at(T09:00)(tz と祝日が定義に入る)
  • 55 23 28-31 * *+判定 → monthEnd |> at(T23:55)
  • 給料日・前営業日(cron 不可) → nth(25) |> roll(Preceding, on: bizDay)
  • 月末最終営業日(cron 不可) → bizDay |> within(month) |> last
  • 月末 3 営業日前(cron 不可) → monthEnd |> roll(…) |> shift(-3, unit: bizDay)

▶ Playground で 3 式を実行する——祝日を足したり、窓を動かしたりして試せる。ブラウザ内で完結し、何も送信しない。


Kairos はスケジュール定義言語です。仕様と参照実装はGitHub で公開しています。

コメント

タイトルとURLをコピーしました