月末・月末最終営業日に実行する——cron の限界と Kairos の式
「毎月末に実行したい」は cron 最頻出の挫折点のひとつ (Stack Overflow 閲覧 32.5 万)。 Kairos ではどちらも 1 行の式になる:
monthEnd # 月末日
bizDay |> within(month) |> last # 月末最終営業日
cron で何が起きるか
- 標準 cron に「月末」は無い。
28-31を並べて先頭でスクリプト判定するハックが定番で、 Kubernetes CronJob では同じ要望が issue で再提起され続けている。 - Quartz 方言の
L(最終日)・LW(最終平日)は便利だが処理系間で移植できず、祝日は扱えない (LWの W は土日回避のみ)。 - 「月末最終営業日」になると Google Calendar でも 編集不可警告つきの ICS 輸入でしか置けない。
根にあるのは機能の不足ではなく式が合成できないこと——「月末」を出せても、その結果を 「営業日で前へ丸める」次の規則に流せない。
Kairos で書く
営業日(祝日対応)の月末。@JP の標準前提(土日+日本の祝日データから導出された bizDay)で:
# eval: 2026-01-01..2026-07-01
@JP
bizDay |> within(month) |> last
#=> 2026-01-30 2026-02-27 2026-03-31 2026-04-30 2026-05-29 2026-06-30
1 月末(1/31 土)と 5 月末(5/30 土・5/31 日)が正しく金曜へ退いている。「営業日の列を作り、
月ごとに最後の点を取る」——読んだままの構造で、祝日はカレンダーデータ(covering: つき)から
来るので、データが尽きれば黙らず註釈が出る。
月末から数える変種も同じ語彙の合成:
monthEnd |> roll(Preceding, on: bizDay) |> shift(-3, unit: bizDay) # 月末の 3 営業日前
米国のカレンダーでも、式は同じ
上の式は日本の祝日データに 1 文字も依存していない。同じ式を米国連邦祝日(2026 年・振替後の observed 日付)に載せ替える:
# eval: 2026-01-01..2026-07-01 tz: America/New_York
premise US {
calendar-system: Gregorian
tz: "America/New_York"
wkst: Sun
}
@US
federal2026 = [2026-01-01, 2026-01-19, 2026-02-16, 2026-05-25, 2026-06-19,
2026-07-03, 2026-09-07, 2026-10-12, 2026-11-11, 2026-11-26,
2026-12-25] covering: 2026..2026
satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
bizDay = everyDay \ (satSun | federal2026)
bizDay |> within(month) |> last
#=> 2026-01-30 2026-02-27 2026-03-31 2026-04-30 2026-05-29 2026-06-30
変わったのは前提(premise)のデータと tz だけで、本体の式は 1 文字も変わらない。 「営業日の列を作り、月ごとに最後の点を取る」という構造は暦データから独立している——どの国の カレンダーでも、差し替えるのはデータだけである。 米国版を Playground で実行。
動かす——crontab は 1 行だけ残す
Kairos が答えるのは「いつ動くべきか」の集合までで、起動・リトライ・記録は実行系(systemd・ジョブランナー等)の
仕事である(言語仕様 §7.8)。接続の型は 2 つ。どちらも CLI(npm i -g kairos-lang)で
そのまま動く。
型 1: cron を時計として残し、判定だけ Kairos に移す。 crontab の行は 1 本だけ残し、毎朝 9 時に「今日は点があるか」
を聞いて、あれば起動する。ラベルと [--from, --to) の窓は機械の tz で読まれる(定義の premise tz と違うなら --tz
で揃える):
0 9 * * * cd /srv/batch && ./run-if-today.sh month-end.kairos ./close-books.sh
#!/bin/sh
# run-if-today.sh <定義.kairos> <ジョブ> —— 今日 [今日, 明日) に点があればジョブを exec する
today=$(date +%F); tomorrow=$(date -d "$today + 1 day" +%F) # GNU date。macOS は date -v+1d +%F
n=$(kairos list --from "$today" --to "$tomorrow" --json "$1" | jq '.results[0].dates | length')
[ "$n" -gt 0 ] && exec "$2"
人間向けの出力には被覆サマリの # 行が混ざるため、判定は --json で数える。
型 2: 次の 1 点を予約する。 時刻も定義側に置き(… |> last |> at(T23:55))、next --json で次の発火を取る。
壁時計(機械の tz)と epoch ミリ秒の両方で返るので、OS の一回限りのタイマーに渡し、ジョブの最後で次を登録し直す
(仕様の言う「定期的に再具現化する」運用):
t=$(kairos next --json month-end.kairos | jq -r '.results[0].dates[0]') # 例 2026-09-30T23:55
systemd-run --user --on-calendar="$(echo "$t" | tr T ' '):00" ./close-books.sh
# Linux の at(1)、Windows の schtasks /sc once でも同じ形
ブラウザで試す
前提込みの自己完結形(祝日テーブルを式の中に持つ)を Playground で実行 ——from/to を動かしたり、祝日を足したりして挙動を確かめられる。
関連
- 似た型: 毎月 31 日(無い月をどうするかの二義を式で書き分ける)・第 N 営業日・N 営業日ごと ——調査研究 11 の実測節
- 語彙:
within・last・roll・shift - 祝日データの持ち込みと鮮度の統治(
covering:/asof:): 言語仕様 §4.10