1. 「旬」と「10 日ごと」は違う
上旬・中旬・下旬。旬払いの給料、旬報、旬ごとの締め。初日は毎月 1 日・11 日・21 日で、cron でも0 0 1,11,21 * * と書ける(曜日が絡まないので、「月 2 回」の回の OR の罠も出ない)。
問題は、これを「10 日ごと」と言い換えたときに起きる。上旬と中旬は 10 日ずつだが、下旬は 8〜11 日ある。旬は月に従う区切りで、等幅ではない。この違いが、式にそのまま出る。
2. 旬の初日は、月の中の日番号で書く
premise JP {
calendar-system: Gregorian
tz: "Asia/Tokyo"
wkst: Mon
}
@JP
everyDay |> filter(d => dayNo(d) == 1 or dayNo(d) == 11 or dayNo(d) == 21)
dayNo(d)は「日 d が月の何日か」(1〜31)。標準の前文に入っている値関数で、within(month) |> nth(n)が 「月の第 n 日を取り出す」なら、dayNoは「点が第何日かを読む」。向きが逆なだけで、同じものを指している。orで 3 つ並べて 1 行。「月の中の 1 日・11 日・21 日」と、要件を言った順に読める。
$ kairos list --from 2026-01-01 --to 2026-04-01 jun.kairos 2026-01-01 2026-01-11 2026-01-21 2026-02-01 2026-02-11 2026-02-21 2026-03-01 2026-03-11 2026-03-21
2026 年で 36 点。同じ列は (everyDay |> within(month) |> nth(1)) | (everyDay |> within(month) |> nth(11)) | (everyDay |> within(month) |> nth(21))でも出る。「毎月 30 日払い」の回で nth(30) は 2 月に空振りしたが、21 日はどの月にもあるので、こちらは空振りしない。
3. 「10 日ごと」に窓を敷くと、2 月 1 日が消える
等幅の窓を敷く語が grid である。10 日幅の窓を 1 月 1 日から敷いて、各窓の初日を取ってみる:
premise Dekad = Gregorian with { decade = chronos grid 10d anchor: 2026-01-01 }
premise JPD { calendar-system: Dekad; tz: "Asia/Tokyo"; wkst: Mon }
@JPD
everyDay |> within(decade) |> first
$ kairos list --from 2026-01-21 --to 2026-04-15 dekad.kairos 2026-01-21 2026-01-31 2026-02-10 2026-02-20 2026-03-02 2026-03-12 2026-03-22 2026-04-01 2026-04-11
2 月 1 日が無い。代わりに 1 月 31 日がある。1 月は 31 日あるので、1 月 21 日から 10 日の窓の次は 1 月 31 日から始まる。2 月の窓頭は旬の初日より 1 日早く、2 月は 28 日しかないので 3 月の窓頭は 1 日遅く(3 月 2 日・12 日・22 日)、3 月が 31 日なので 4 月 1 日に戻る。ずれは一方向に溜まらず、月の長さに合わせて行ったり来たりする。5 月はそろい、6 月はまた 5 月 31 日から始まる。

「月は 30 日」と決めて窓を敷いても同じことが起きる。chronos grid 30d anchor: 2026-01-01 の窓頭は1 月 1 日・1 月 31 日・3 月 2 日・4 月 1 日・5 月 1 日・5 月 31 日・6 月 30 日。等幅の窓は月の境を知らない。暦の月を grid で作ってはいけない、というのは Kairos の仕様の「落とし穴」に書いてある最初の 1 行で、月に従う区切りは within(month) か dayNo で書く。
4. 旬の最終日と、休みのとき
旬の最終日は 10 日・20 日・月末。下旬だけ長さが違うのが旬の本質で、月末は last で取る:
(everyDay |> filter(d => dayNo(d) == 10 or dayNo(d) == 20)) | (everyDay |> within(month) |> last)
$ kairos list --from 2026-01-01 --to 2026-04-01 jun-last.kairos 2026-01-10 2026-01-20 2026-01-31 2026-02-10 2026-02-20 2026-02-28 2026-03-10 2026-03-20 2026-03-31
初日が土日や祝日なら前営業日に払う、という規則は、祝日表と営業日の列を前文に置いて(締め切りまであと何営業日の§2 と同じ器)、roll を 1 回足す:
premise JP {
calendar-system: Gregorian; tz: "Asia/Tokyo"; wkst: Mon
national = [2026-01-01, 2026-01-12, 2026-02-11, 2026-02-23, 2026-03-20, 2026-04-29, 2026-05-03..2026-05-06,
2026-07-20, 2026-08-11, 2026-09-21..2026-09-23, 2026-10-12, 2026-11-03, 2026-11-23] covering: 2026..2026
satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
bizDay = everyDay \ (satSun | national)
}
@JP
everyDay |> filter(d => dayNo(d) == 1 or dayNo(d) == 11 or dayNo(d) == 21) |> roll(Preceding, on: bizDay)
$ kairos list --from 2026-01-01 --to 2026-05-01 jun-biz.kairos 2026-01-09 2026-01-21 2026-01-30 2026-02-10 2026-02-20 2026-02-27 2026-03-11 2026-03-19 2026-04-01 2026-04-10 2026-04-21 # ⚠ 範囲外 2026-01-01..2026-01-02(JP.national covering 2026-01-01..2026-12-31) # 被覆サマリ # JP.national covering 2026-01-01..2026-12-31 残走路 245 日
1 月 11 日(日)は 9 日(金)へ、2 月 1 日(日)は 1 月 30 日へ、2 月 11 日(建国記念の日)は 10 日へ、3 月 21 日(土)は20 日が春分の日なので 19 日(木)へ。1 月 1 日は 12 月 31 日へ動いて範囲の外に出る——末尾の「範囲外」の 1 行がそれを言っている(「月 2 回」の回の §3 と同じ註釈。範囲を 12 月から取れば 12 月 31 日が出る)。
5. 落とし穴
- 本当に「10 日ごと」でよい仕事もある。10 日おきの点検、10 日ごとの集計バッチ。それなら
grid 10dかstride(10)が正しく、2 月 1 日は出なくてよい。要件が「旬」なのか「10 日ごと」なのかを 言葉で確かめる。式はその違いを隠さない。 - 月末を
dayNo(d) == 31で書かない。4 月は 30 日で終わる。月末はwithin(month) |> last。
- 締めと支払いが別の旬にまたがる(20 日締め・翌月 10 日払い)なら、締め日の列に
shiftを重ねるのではなく、 支払日をdayNo(d) == 10で直接書く。前回の「読んだ順に書く」と同じ。 - 祝日表の期限。被覆サマリの「残走路 245 日」は、表が 12 月末で切れていることを言っている (祝日表は黙って腐る)。
6. 動かす
月末最終営業日の自動実行まで通すの型 1 で、毎朝 1 回:
0 7 * * * cd /srv/pay && ./run-if-today.sh jun-biz.kairos ./pay.sh
まとめ
- 旬は月に従う区切りで、等幅ではない(下旬は 8〜11 日)。「10 日ごと」と言い換えた瞬間に壊れる
- 旬の初日は
dayNo(d) == 1 or dayNo(d) == 11 or dayNo(d) == 21の 1 行。within(month) |> nth(n)の和でも同じ - 等幅の
grid 10dは 1 月 31 日から窓が始まり、2 月 1 日が消える。ずれは月の長さで行ったり来たりする。grid 30dで月を作るのも同じ誤り - 最終日は 10 日・20 日・月末(
last)。休みならroll(Preceding, on: bizDay) - 本当に「10 日ごと」なら
grid/strideが正しい。要件の言葉を確かめる
▶ Playground で定義を実行する——2026 年 1〜6 月で、旬の初日 18 点と、10 日ごとの窓頭 19 点が並んで出る。2 月 1 日が片方にしか無い。コメントにある nth の和・旬の最終日の行と入れ替えて試せる。ブラウザ内で完結し、何も送信しない。
Kairos はスケジュール定義言語です——決めたら、いつでも。仕様と参照実装はGitHub で公開しています。

コメント