祝日テーブルの 2 行は、実は法律の計算結果である——振替休日と国民の休日を式で導出する

Kairos

2026 年の祝日は 18 日ある。官報に載るのは 16 日ぶんだ

祝日テーブルを自前で持っている人は、毎年こういう作業をしている。内閣府の一覧から祝日を転記して——5 月 6 日と 9 月 22 日を、手で足す

この 2 日は法定祝日ではない。祝日法が定める計算の結果だ:

  • 振替休日: 祝日が日曜に当たるとき、「その日後においてその日に最も近い『国民の祝日でない日』」が休日になる(2026 年は 5/3 憲法記念日が日曜→ 5/4 も 5/5 も祝日なので、 滑って 5/6 が振替になる)
  • 国民の休日: 前日と翌日の両方が祝日である平日は、休日になる(2026 年は敬老の日 9/21 と秋分の日 9/23 に挟まれた 9/22

規則が分かっているのに、テーブルには結果だけを書く。だから毎年、誰かが計算して、転記して、ときどき忘れる。

規則をそのまま式にする

Kairos は列の代数なので、条文の構造をだいたいそのまま書ける。法定祝日 16 日を statutory として:

nonHoliday  = everyDay \ statutory
substitutes = statutory |> filter(d => weekday(d) == Sun) |> roll(Following, on: nonHoliday)
sandwiched  = ((statutory |> shift(+1, unit: day)) & (statutory |> shift(-1, unit: day))) \ statutory
holidays    = statutory | substitutes | sandwiched

3 行それぞれが条文に対応する。

substitutes(振替休日)——日曜の祝日を選び、roll(Following, on: nonHoliday) で「その後における最初の非祝日」へ倒す。条文の「最も近い国民の祝日でない日」が roll の軸そのもので、5/3 → 5/4(祝日)→ 5/5(祝日)→ 5/6 と滑る連鎖を、式は何も特別扱いしていない。軸が「非祝日」だから勝手に滑る。

sandwiched(国民の休日)——「前日も翌日も祝日」を、祝日列を ±1 日ずらした二つの列の積で書く。statutory |> shift(+1) は「祝日の翌日」の列、shift(-1) は「祝日の前日」の列。両方に属する日=挟まれた日。最後に \ statutory で祝日自身を除けば、9/21〜9/23 の連なりから 9/22 だけが残る。

動かす

16 日を入れて評価すると、18 点が出る:

2026-01-01
2026-01-12
2026-02-11
2026-02-23
2026-03-20
2026-04-29
2026-05-03
2026-05-04
2026-05-05
2026-05-06   ← 振替休日(導出)
2026-07-20
2026-08-11
2026-09-21
2026-09-22   ← 国民の休日(導出)
2026-09-23
2026-10-12
2026-11-03
2026-11-23
# ⚠ 範囲外 2026-01-01..2026-01-02(statutory covering 2026-01-01..2026-12-31)
# ⚠ 範囲外 2026-12-31..2027-01-01(statutory covering 2026-01-01..2026-12-31)
# 被覆サマリ
#   statutory covering 2026-01-01..2026-12-31 残走路 0 日

(← は筆者注。末尾の ⚠ と被覆サマリは前回書いた「データの鮮度」の器がここでも生きているだけ——2026 年分しか主張していないデータで年境界を評価しているので、端に註釈が付き、残走路は 0 日=「そろそろ 2027 年分を」と言っている。)

▶ Playground で開く

導出分だけ取り出すなら holidays \ statutory——5/6 と 9/22 の 2 点だけが出る:

2026-05-06
2026-09-22
# ⚠ 範囲外 2026-01-01..2026-01-02(statutory covering 2026-01-01..2026-12-31)
# ⚠ 範囲外 2026-12-31..2027-01-01(statutory covering 2026-01-01..2026-12-31)
# 被覆サマリ
#   statutory covering 2026-01-01..2026-12-31 残走路 0 日

▶ Playground で開く

前回の話とつながる

前回、祝日テーブルは黙って腐るという話を書いた。腐るのはデータだからで、データの覆域(covering)と残走路で鮮度を監視する、というのが前回の答えだった。

今回の話はその前段にある。「祝日テーブル」と呼んでいたものの中に、データと法則が混ざっていた。法定祝日 16 日は官報由来のデータで、これは覆域管理の対象。だが振替と国民の休日は法則で、一度式に書けば毎年ついてくる。分けると、覚えて更新すべきものが 16 行に減り、「5/6 を足し忘れる」という事故のクラスが構造ごと消える。

cron にはこの区別を書く場所がない。データも法則も、結局は誰かの頭の中と転記作業に畳み込まれる。式で書けるということは、法則を法則のままレビューできるということだ——この 3 行が祝日法の条文と一致しているかは、コードレビューで(あるいはこの記事のコメント欄で)確認できる。テーブルの 18 個の日付が正しいかを目視するのとは、確認している内容が違う。

(コード例は Playground でそのまま動く。定義の全文は 公開リポジトリの例 ——言語仕様の代表例 §7.5 でもある。現在 RC・1.0 準備中。)

コメント

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