1. 2026 年 2 月 28 日は、土曜日だが出勤日である
中国本土の暦には、日本にない種類の日がある。土曜日や日曜日なのに出勤日、という日である。連休を長くつなぐために、前後の週末を平日に振り替える。これを「調休(ちょうきゅう・振替出勤)」と呼ぶ。
2026 年なら、たとえば春節。2 月 15 日(日)から 23 日(月)まで 9 連休になる代わりに、2 月 14 日(土)と 2 月 28 日(土)が出勤日になる。つまり 2 月の最終営業日は、土曜日の 28 日である。
「月末最終営業日にバッチを回す」「25 日払い、休みなら前営業日」——この連載で何度も書いてきたスケジュールは、土曜が営業日になる暦の上ではどう書けばよいか。答えは短い。規則では書かず、データとして写す。
2. 先に、この記事がやらないこと
調休の日程は、毎年、中国の国务院办公厅(国務院弁公庁)が「通知」として公布する。2026 年分は2025 年 11 月 4 日付の「国务院办公厅关于2026年部分节假日安排的通知」(国办发明电〔2025〕7号)である。どの日が休みで、どの日が出勤かを決める権威はこの通知にあり、この記事はそれを置き換えるものでも、評価するものでもない。やるのは一つだけ——通知をデータとして写し、「月末最終営業日」のようなスケジュールがその上で正しく出ることを確かめる。
暦の権威は上流にある。式の側がやってよいのは、上流のデータを鵜呑みにせず期限つきで持つこと——それだけである(第 5 弾祝日テーブルは黙って腐る)。
3. 2026 年の通知には何が書いてあるか
通知の本文から、日付だけを抜く(曜日は本文どおり):
- 元旦: 1 月 1 日(木)〜3 日(土)が休み。1 月 4 日(日)は出勤
- 春節: 2 月 15 日(日)〜23 日(月)が休み(9 日間)。2 月 14 日(土)・2 月 28 日(土)は出勤
- 清明節: 4 月 4 日(土)〜6 日(月)が休み
- 労働節: 5 月 1 日(金)〜5 日(火)が休み。5 月 9 日(土)は出勤
- 端午節: 6 月 19 日(金)〜21 日(日)が休み
- 中秋節: 9 月 25 日(金)〜27 日(日)が休み
- 国慶節: 10 月 1 日(木)〜7 日(水)が休み。9 月 20 日(日)・10 月 10 日(土)は出勤
休みの日が 33 日、週末なのに出勤の日が 6 日。この 6 日が、日本の暦の常識(土日は休み)をそのまま持ち込むと壊れる場所である。
4. なぜ規則で書けないか
日本の祝日は、多くが規則で導ける。「1 月の第 2 月曜」「祝日が日曜なら翌日が振替」——法律に規則が書いてあるから、式で書ける(本社の暦と、支社の暦で振替休日を導いた)。
調休は違う。どの週末を出勤日にするかは、年ごとに通知で決まる。「連休の前の土曜」という傾向はあっても、規則ではない(2026 年の春節は連休前日の土曜とその 2 週間後の土曜、国慶節は11 日前の日曜と 3 日後の土曜)。規則がないものを規則で書こうとすると、来年の通知が出た瞬間に式を直すことになる。
だから、式には「休みの表と出勤の表を差し引く」とだけ書き、表の中身はデータにする。式は静的・データは実行時、の型である(第 14 弾式は静的・データは実行時)。
5. 定義——休みの表と、出勤の表
2026 年の通知を、そのまま 2 つの表に写す:
premise CN {
calendar-system: Gregorian
tz: "Asia/Shanghai"
source: "gov.cn/zhengce/content/202511/content_7047090.htm"
asof: 2025-11-04
satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
holidays = [2026-01-01..2026-01-03, 2026-02-15..2026-02-23, 2026-04-04..2026-04-06,
2026-05-01..2026-05-05, 2026-06-19..2026-06-21, 2026-09-25..2026-09-27,
2026-10-01..2026-10-07] covering: 2026..2026
workdays = [2026-01-04, 2026-02-14, 2026-02-28, 2026-05-09, 2026-09-20, 2026-10-10] covering: 2026..2026
nonWorking = (satSun | holidays) \ workdays
}
premise Shanghai { calendar-system: Gregorian; calendar: CN; tz: "Asia/Shanghai"; wkst: Mon }
@Shanghai
everyDay |> filter(on: bizDay)
初めて出る記法を順に。
[2026-01-01..2026-01-03, 2026-02-15..2026-02-23]は日付の表。a..bは a から b までの 毎日(両端とも含む)の省略形で、通知の「1 月 1 日至 3 日」をそのまま書ける。covering: 2026..2026は「この表は 2026 年について完全である」という主張。表に無い日は 休みではない、と言ってよいのは 2026 年の中だけ。2027 年については、まだ何も言っていない。source:は出所、asof:はいつ時点の版か(通知の日付)。表を持つ以上、出所と版は書く。satSun | holidaysの|は和(どちらかに入る日)、\は差(左にあって右にない日)。 「週末または休日、ただし出勤日を除く」を、そのまま集合の式で書いている。nonWorkingは、カレンダーとして使える premise が持つべき予約語で、「非稼働日の集合」 という意味が言語で決まっている。Shanghaiの側でcalendar: CNと指すと、bizDay(毎日からCN.nonWorkingを引いたもの)が自動で使えるようになる。営業日の定義を自分で書く必要はない。
最後の行は「上海の暦で、営業日だけ」。まず春節の前後で確かめる:
$ kairos list --from 2026-02-09 --to 2026-03-03 --tz Asia/Shanghai cn.kairos 2026-02-09 2026-02-10 2026-02-11 2026-02-12 2026-02-13 2026-02-14 2026-02-24 2026-02-25 2026-02-26 2026-02-27 2026-02-28 2026-03-02
2 月 14 日(土)と 28 日(土)が営業日として出ていて、15 日から 23 日は出ない。土曜が営業日で日曜が休みという、日本の常識にない列が、表を 2 つ写しただけで出た。
6. 月末最終営業日と、25 日払い
営業日の列ができれば、あとはこの連載でおなじみの式がそのまま乗る。各月の最終営業日:
@Shanghai everyDay |> filter(on: bizDay) |> within(month) |> last
$ kairos list --from 2026-01-01 --to 2027-01-01 --tz Asia/Shanghai cn-monthend.kairos 2026-01-30 2026-02-28 2026-03-31 2026-04-30 2026-05-29 2026-06-30 2026-07-31 2026-08-31 2026-09-30 2026-10-30 2026-11-30 2026-12-31
2 月だけ土曜日の 28 日である。式のどこにも「土曜」も「調休」も書いていない。表がそう言っているから、そうなった。
25 日払い、休みなら前営業日(roll(Preceding, on: CN) は「CN の暦で、休みなら手前の営業日へ丸める」):
@Shanghai everyDay |> within(month) |> nth(25) |> roll(Preceding, on: CN)
$ kairos list --from 2026-01-01 --to 2027-01-01 --tz Asia/Shanghai cn-payday.kairos 2026-01-23 2026-02-25 2026-03-25 2026-04-24 2026-05-25 2026-06-25 2026-07-24 2026-08-25 2026-09-24 2026-10-23 2026-11-25 2026-12-25 # ⚠ 範囲外 2026-01-01..2026-01-04(CN.holidays covering 2026-01-01..2026-12-31, asof 2025-11-04) # ⚠ 範囲外 2026-01-01..2026-01-04(CN.workdays covering 2026-01-01..2026-12-31, asof 2025-11-04)
1 月 25 日は日曜なので 23 日(金)へ。4 月 25 日(土)は 24 日(金)へ。10 月 25 日(日)は 23 日へ。
末尾に注記が 2 行ついた。12 点の値は正しいが、言語が一言添えている。1 月 1〜3 日は休みで、もしこの区間に点が落ちれば「前営業日」は 2025 年 12 月 31 日——表が覆っていない年——になる。この定義では 25 日が 1 月 1〜3 日に落ちることはないので実害はないが、言語は「この区間は表の外に依存する」ことを黙らない。黙って 12 月 31 日を返すことも、黙って捨てることもしない(黙りの既定)。注記が出たら、読んで、要らなければ表の覆域を前年末まで広げればよい。
7. 2027 年の通知は、まだ出ていない
いちばん大事なのはここである。2027 年の調休は、この記事を書いている時点ではまだ公布されていない(例年、前年の 11 月ごろに出る)。表の covering: は 2026 年までしか主張していない。では年末に「次はいつか」と聞くとどうなるか:
$ kairos next -n 3 --from 2026-12-20 --tz Asia/Shanghai cn-monthend.kairos 2026-12-31 2027-01-29 2027-02-26 # ⚠ 範囲外 2027-01-01..2027-02-27(CN.holidays covering 2026-01-01..2026-12-31, asof 2025-11-04) # ⚠ 範囲外 2027-01-01..2027-02-27(CN.workdays covering 2026-01-01..2026-12-31, asof 2025-11-04) # 被覆サマリ # CN.holidays covering 2026-01-01..2026-12-31 asof 2025-11-04 残走路 -57 日 # CN.workdays covering 2026-01-01..2026-12-31 asof 2025-11-04 残走路 -57 日
2027 年 1 月 29 日、2 月 26 日は出る——土日を除いただけの値である。そしてその値は「表の外」だと明記され、残走路(表が尽きるまでの日数)がマイナス 57 日と出る。2027 年 1 月末に春節がかかっていれば(2027 年の春節は 2 月 6 日)、この値は通知が出た時点で変わりうる。
これが、規則で書かずデータで写すことの代償であり、同時に利点である。表が切れることは隠せない。通知が出たら、holidays と workdays に 2027 年分を足し、covering: を2026..2027 に、asof: を通知の日付に直す。式は 1 文字も変わらない。
8. 表を外から差す
表を式の中に直書きしたが、運用では通知を写したファイルを差し替えるだけにしたい。premise の表を external と宣言すると、中身を実行時に外から渡せる:
premise CN {
calendar-system: Gregorian
tz: "Asia/Shanghai"
source: "gov.cn/zhengce/content/202511/content_7047090.htm"
satSun = everyDay |> filter(d => weekday(d) == Sat or weekday(d) == Sun)
holidays = external(kind: dates)
workdays = external(kind: dates)
nonWorking = (satSun | holidays) \ workdays
}
渡す側は、通知を写した JSON(日付・覆域・版):
{
"holidays": { "dates": ["2026-01-01", "2026-01-02", "2026-01-03", "2026-02-15", … , "2026-10-07"],
"covering": "2026-01-01..2026-12-31", "asof": "2025-11-04" },
"workdays": { "dates": ["2026-01-04", "2026-02-14", "2026-02-28", "2026-05-09", "2026-09-20", "2026-10-10"],
"covering": "2026-01-01..2026-12-31", "asof": "2025-11-04" }
}
$ kairos list --from 2026-02-09 --to 2026-03-03 --tz Asia/Shanghai --supply cn-2026.json cn.kairos 2026-02-09 (…§5 と同じ 12 行…) 2026-03-02
直書き版と同じ列が出る(点列の一致は実測済み)。JSON を渡し忘れると、式は黙って「祝日なし」に落ちたりせず、供給エラーで止まる——「解決子がない——external CN.holidays は解決できない」。覆域と版は JSON の側が必ず運ぶ決まりなので、「いつの通知か」が式の外へ出ても失われない。
9. 射程——式が言えるのは「通知どおり」まで
- 権威は通知。臨時の調整や、地方・業種ごとの運用(金融市場の休場日、企業独自の年休)は、 それぞれの権威のデータを同じ器で持つ。この記事の表は国務院弁公庁の 2026 年通知 1 本を写した だけである。
- 通知が改訂されれば、表を直す。式は変わらない。どの版を写したかは
asof:が持つ。 - 「休みの表と出勤の表を差し引く」形は、中国に限らない。週末が金・土の国、半日出勤の土曜、 企業の一斉休業——「週末=休み」の常識が崩れる暦は、すべて同じ 2 表で書ける。
まとめ
- 中国本土の暦は土日が出勤日になることがある(2026 年は 6 日・2 月 28 日は土曜の最終営業日)
- 調休は規則ではなく毎年の通知で決まる——だから式に規則を書かず、休みの表と出勤の表を写す
nonWorking = (satSun | holidays) \ workdaysの 1 行で、月末最終営業日も 25 日払いもそのまま乗るcovering:は「この表は 2026 年について完全」という主張。2027 年に踏み込むと範囲外と 残走路のマイナスが出る——表が切れることは隠せない- 表は
externalで外から差せる。渡し忘れは供給エラーで止まる
▶ Playground で定義を実行する——2026 年の各月の最終営業日 12 点が出る(2 月は 2/28)。コメントにある「営業日の列」「25 日払い」の行と入れ替えたり、covering: を 2026..2027 に広げて注記の消え方を見たりして試せる。ブラウザ内で完結し、何も送信しない。
Kairos はスケジュール定義言語です。仕様と参照実装はGitHub で公開しています。

コメント