1. 12 月になると、営業日マスタを手で埋め直す
WMS や受注システムには「営業日マスタ」「休配日マスタ」がある。中身は日付と営業日フラグの列で、年に一度、翌年分を誰かが埋める。元になるのは運送会社の年末年始の休配日告知と、内閣府の祝日の一覧。カレンダーを見ながら 365 行を打つか、前年の表をコピーして曜日をずらし、祝日とお盆と年末年始を直す。
痛いのは、作業そのものより更新漏れである。表が 12 月 31 日で尽きていると、1 月の受注の着日計算が翌営業日を見つけられずに止まるか、もっと悪いと、1 月 1 日を営業日として着日を返す。どちらが起きるかはシステム次第で、気づくのは年明けの現場からの電話だったりする。
この回は、その表を規則から生成する書き方の話である。営業日マスタをなくす話ではない。マスタは残し、埋める作業を「規則を 1 本書き、毎年の表を足し、再生成する」に替える。前回は営業日の列を「数える」道具にした。今回は同じ列を「マスタへ渡す」。
2. 規則を 1 本で書く
公開情報の典型で書く。休配日は日曜・祝日・年末年始(12/30〜1/3)・お盆(8/13〜8/15)。営業日はそれ以外。会社ごとに違うところは §6 で触れる。
# 営業日と休配日(日祝・年末年始・お盆を除く)
premise JP {
calendar-system: Gregorian
tz: "Asia/Tokyo"
wkst: Mon
}
@JP
holidays = [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] covering: 2026..2026
closed = [2026-01-01..2026-01-03, 2026-08-13..2026-08-15, 2026-12-30..2026-12-31] covering: 2026..2026
sunday = everyDay |> filter(d => weekday(d) == Sun)
noDelivery = sunday | holidays | closed
deliveryDay = everyDay \ noDelivery
deliveryDay
noDelivery
holidaysは 2026 年の祝日 18 日(振替休日 5/6・国民の休日 9/22 を含む)。covering: 2026..2026は「この表は 2026 年の分」という有効範囲の宣言。closedは会社の休業日。2026-01-01..2026-01-03は連続した日の略記。年ごとに告知で決まるものなので、規則にせず表に書く。sundayは規則。「日曜なら」は毎年同じなので、表は要らない。|が和、\が差。休配日は 3 つの和、営業日は毎日から休配日を引いたもの。- 最後の 2 行が「出してほしい列」。1 つのファイルに 2 つ並べられる。
「日曜は規則、祝日と休業日は表」という分け方は、前々回の「8 月は規則、年末年始は表」と同じである。ゴールデンウィークで動かしてみる:
$ kairos list --from 2026-04-27 --to 2026-05-11 --tz Asia/Tokyo delivery.kairos # 式 1(8 件) 2026-04-27 2026-04-28 2026-04-30 2026-05-01 2026-05-02 2026-05-07 2026-05-08 2026-05-09 # 式 2(6 件) 2026-04-29 2026-05-03 2026-05-04 2026-05-05 2026-05-06 2026-05-10 # 被覆サマリ # holidays covering 2026-01-01..2026-12-31 残走路 235 日 # closed covering 2026-01-01..2026-12-31 残走路 235 日
--from 2026-04-27 --to 2026-05-11 は「4/27 から 5/10 まで」。--to の日は含まない。この「右端を含まない」書き方は半開区間の回にある。式 1 が営業日、式 2 が休配日。土曜(5/2・5/9)は営業日、4/29 と 5/3〜5/6 が休配日に落ちている。5/6 は振替休日で、表に書いてあるから落ちる。
3. 表が尽きた先は、黙らない
年末に配る「年末年始の営業日のお知らせ」の帯を出す。12/26 から 1/7 まで:
$ kairos list --from 2026-12-26 --to 2027-01-08 --tz Asia/Tokyo delivery.kairos # 式 1(9 件) 2026-12-26 2026-12-28 2026-12-29 2027-01-01 2027-01-02 2027-01-04 2027-01-05 2027-01-06 2027-01-07 # ⚠ 範囲外 2027-01-01..2027-01-08(holidays covering 2026-01-01..2026-12-31) # ⚠ 範囲外 2027-01-01..2027-01-08(closed covering 2026-01-01..2026-12-31) # 式 2(4 件) 2026-12-27 2026-12-30 2026-12-31 2027-01-03 # ⚠ 範囲外 2027-01-01..2027-01-08(holidays covering 2026-01-01..2026-12-31) # ⚠ 範囲外 2027-01-01..2027-01-08(closed covering 2026-01-01..2026-12-31) # 被覆サマリ # holidays covering 2026-01-01..2026-12-31 残走路 -7 日 # closed covering 2026-01-01..2026-12-31 残走路 -7 日
12/30・12/31 は休業で落ちている。ところが 1/1 と 1/2 が営業日として出ている。祝日表も休業日の表も 2026 年で尽きているので、1 月は「日曜だけ」を引いた結果になっている(1/3 は日曜なので落ちている)。これが §1 の「1 月 1 日を営業日として着日を返す」の正体である。
違いは、黙っていないこと。点の下の ⚠ 範囲外 の 2 行と、被覆サマリの「残走路 −7 日」がそれを言っている。残走路は「表の終わりまであと何日あるか」で、マイナスは聞いた範囲が表を 7 日はみ出したという意味。点は出るが、表を知らずに出ている。この帯を配る前に、表を延ばす(祝日表は黙って腐る)。

図の下段の「12/29 出荷」は、同じ営業日の列で着日(出荷日+N 営業日)を数えたもので、書き方はレシピにある。表が尽きたままだと近県便が 1/1 着と出る。延ばせば 1/4 着になる。
4. 翌年の行を足して、再生成する
表を延ばす=2027 年の祝日 17 日と休業日を足し、covering: を 2026..2027 にする。式は触らない:
holidays = [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,
2027-01-01, 2027-01-11, 2027-02-11, 2027-02-23, 2027-03-21,
2027-03-22, 2027-04-29, 2027-05-03, 2027-05-04, 2027-05-05,
2027-07-19, 2027-08-11, 2027-09-20, 2027-09-23, 2027-10-11,
2027-11-03, 2027-11-23] covering: 2026..2027
closed = [2026-01-01..2026-01-03, 2026-08-13..2026-08-15, 2026-12-30..2026-12-31,
2027-01-01..2027-01-03, 2027-08-13..2027-08-15, 2027-12-30..2027-12-31] covering: 2026..2027
同じ帯をもう一度:
$ kairos list --from 2026-12-26 --to 2027-01-08 --tz Asia/Tokyo delivery.kairos # 式 1(7 件) 2026-12-26 2026-12-28 2026-12-29 2027-01-04 2027-01-05 2027-01-06 2027-01-07 # 式 2(6 件) 2026-12-27 2026-12-30 2026-12-31 2027-01-01 2027-01-02 2027-01-03 # 被覆サマリ # holidays covering 2026-01-01..2027-12-31 残走路 358 日 # closed covering 2026-01-01..2027-12-31 残走路 358 日
⚠ が消え、1/4(月)が年始の初営業日になった。表の名前に年を入れていない(holidays2026 ではなく holidays)ので、延ばしても式を直す場所がない。
マスタへ渡すときは --tsv で、1 点 1 行のタブ区切りに書き出す。註釈や被覆サマリは標準エラーに出るので、ファイルには混ざらない:
$ kairos list --tsv --from 2026-01-01 --to 2027-01-01 --tz Asia/Tokyo delivery.kairos > delivery.tsv # 式 1(289 件) # 式 2(76 件) # 被覆サマリ # holidays covering 2026-01-01..2026-12-31 残走路 0 日 # closed covering 2026-01-01..2026-12-31 残走路 0 日 $ cut -f 1,2,4,7 delivery.tsv | head -4 expr source date note 1 deliveryDay 2026-01-05 1 deliveryDay 2026-01-06 1 deliveryDay 2026-01-07
2026 年は営業日 289 日・休配日 76 日で、足すと 365。expr 列の 1 が営業日、2 が休配日なので、1 つのファイルから 2 つのマスタが切り出せる。note 列は、その点が「表の外」の区間に入るときだけ註釈文が入る。空なら表の内側の計算である。列はほかに uid・time・epoch_ms・label があり、Excel への取り込み方はExcel 対照レシピにある。
年次更新は、延ばした表で同じコマンドを 2 年ぶん走らせ、旧マスタと差分を取る:
$ kairos list --tsv --from 2026-01-01 --to 2028-01-01 --tz Asia/Tokyo delivery.kairos > delivery-2027.tsv # 式 1(581 件) # 式 2(149 件) # 被覆サマリ # holidays covering 2026-01-01..2027-12-31 残走路 0 日 # closed covering 2026-01-01..2027-12-31 残走路 0 日 $ diff delivery.tsv delivery-2027.tsv | cut -f 1,2,4 | head -3 290a291,582 > 1 deliveryDay 2027-01-04 > 1 deliveryDay 2027-01-05
差分は追加 365 行・削除 0 行で、追加はすべて 2027 年の行(営業日 292・休配日 73)。2026 年の 365 行は 1 行も変わっていない。これが検算である。表を足して式を触っていないのだから当然に見えるが、「当然」を機械に言わせるのが年次更新の肝で、前年の表をコピーして直す運用には、この検算がない。
旧マスタが Excel や DB にあるなら、日付と営業日フラグの 2 列に書き出し、生成した TSV の date 列と突き合わせる。Excel なら COUNTIF、PowerShell なら Compare-Object。差分が「足した年の行だけ」なら、更新は済んでいる。
5. 置き換えではない——上流の権威は上流に
- Kairos は WMS・TMS・受注システムを置き換えない。「どの日が営業日か」を定義から出し、マスタへ渡すところまで。受注・在庫・配車は、これまでのシステムに。
- 休配日の正は運送会社の公式告知にある。年末年始・お盆の休配は毎年告知され、年によって日付も長さも変わる。ここでの 12/30〜1/3・8/13〜8/15 は典型例で、告知が出たら
closedの行を告知どおりに直す。式が休配日を決めることはない。 - 祝日の正は内閣府「国民の祝日について」。毎年 2 月に翌年分が公示される。式は表を読むだけで、祝日を決めない。
- 着日の正は運送会社のサービス(地域別の所要日数・離島・繁忙期や天候の遅れ)。規則から出るのは「規則どおりなら」の予定日で、遅延は式の外。
6. 落とし穴
表と出力の落とし穴:
- 会社差。日祝も配達する会社がある。土曜も休配なら
sundayの行をsatSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)に替え、noDeliveryのsundayをsatSunにする。 - 年次更新の時期。祝日は 2 月に公示されるので、そこで翌年分を足す。忘れても、秋以降に翌年へかかる範囲(着日・年末年始の帯)を出すたびに
⚠と残走路のマイナスが出る。黙って 1/1 を営業日にはしない。 --tzを書かない。機械のタイムゾーンで印字され、日付に時刻が付くことがある。定義の tz と揃える。
着日まで書くときの落とし穴:
- 受注日が休配日のとき。着日は
shift(+N, unit: deliveryDay)で数えるが、入力が営業日でないとエラーで止まる。roll(Following, on: deliveryDay)で翌営業日へ寄せてから数える(roll と shift の回)。 - 評価範囲の端。
shiftで範囲の外へ動いた点は出ない(window-clipの警告)。着日を出すときは、範囲を N 営業日ぶん広げて聞く。
受注の締め時刻(15 時以降は翌営業日出荷)・定期便の集荷(火・金、休配日は翌営業日)・月末の棚卸(月末最終営業日)も、同じ営業日の列から書ける。書き方と実走結果は物流レシピ: 休配日と着日にまとめた。
7. 動かす
再生成はコマンド 1 本なので、毎朝「今日は営業日か」を聞く型もそのまま使える。月末最終営業日の自動実行まで通すの型 1、Windows ならWindows Update の回の §6 のガイドが同じ定義ファイルで動く。マスタの再生成は年 1 回、日々の判定は同じ定義——書く場所が 1 つになる。
まとめ
- 営業日マスタは残す。埋める作業を「規則を 1 本書き、毎年の表を足し、再生成する」に替える
- 規則は
deliveryDay = everyDay \ (sunday | holidays | closed)。日曜は規則、祝日と休業日は表+covering: - 表が尽きた先も点は出るが、
⚠ 範囲外と残走路のマイナスで黙らない。1/1 を営業日にして年を越す前に表を延ばす - 再生成は
--tsv。旧マスタとの差分が「追加 365 行・削除 0」なら検算は済み - 休配日の正は運送会社の告知、祝日の正は内閣府。式は表を読むだけで、決めない
▶ Playground で定義を実行する——2027 年まで延ばした表で、12/26 から 1/7 までの営業日 7 点が出る。コメントのとおり 2027 年の行を消して covering: を 2026..2026 に戻すと、§3 の 9 点と ⚠ に戻る。ブラウザ内で完結し、何も送信しない。
Kairos はスケジュール定義言語です——決めたら、いつでも。仕様と参照実装はGitHub で公開しています。

コメント