「今日は Windows Update の日です」——毎月手で送っていたメールを、定義 1 つにする

Kairos

1. 毎月、手で送っていたメール

前職で、毎月 1 通、B2B の顧客のシステム管理者宛に、同じ件名のメールを手で送っていた。件名は「今日は Windows Update の日です」。送るのは水曜の朝である。

段取りはこうだった。Google カレンダーに「毎月第 2 水曜の 7 時」の予定を入れておく。それが鳴ったら、壁のカレンダーでもう一度数え直してから送る。

数え直しの手順は最初からあったわけではない。運用を始めて何か月かした頃、顧客からクレームが来た。間違えた日に送ってしまったことに後になって気が付いた。それ以来カレンダーが鳴っても鵜呑みにせず、壁のカレンダーで数え直すことにした。月に 1 度、釈然としない思いを抱えながら同じ確認を繰り返す。第 2 火曜——ただし、アメリカの。

2. 「第 2 火曜」は、日本の暦では何曜の何時か

Microsoft の月例セキュリティ更新は、毎月第 2 火曜の 10:00 太平洋時間に公開される(Microsoft Learn: Update release cycle for Windows clients。「通常は」の但し書きつき)。この一点を日本の暦に置くと、日付も時刻も揺れる。2026 年の 12 回を日本時間で並べる(参照実装の実測。定義は後述):

2026-01-14T03:00
2026-02-11T03:00
2026-03-11T02:00
2026-04-15T02:00
2026-05-13T02:00
2026-06-10T02:00
2026-07-15T02:00
2026-08-12T02:00
2026-09-09T02:00
2026-10-14T02:00
2026-11-11T03:00
2026-12-09T03:00
  • 時刻: 1・2・11・12 月は 3 時、3〜10 月は 2 時。太平洋時間に夏時間があるからで、日本側は何もしていない。
  • 日付: つねに水曜だが、4 月と 7 月は第 3 水曜である。月が水曜始まりだと、第 2 火曜(14 日)の 翌日は 3 つめの水曜になる。「日本では第 2 水曜」と覚えると、年に 2 回、丸 1 週間ずれる。

「毎月第 2 水曜」というカレンダーの予約は、月が水曜始まりの月には 1 週間早く鳴る。2026 年なら 4 月と7 月がそれで、予約は 4/8 と 7/8 に鳴るが、更新が出るのは 4/15 と 7/15 である。壁のカレンダーで数え直していたのは、つまりこの差を毎月、手で埋めていたことになる。cron やタスク スケジューラに「第 2 水曜の 3 時」と書いても同じことが起きる(時刻まで含めると、2026 年は 12 か月中 10 か月が違う)。その計算は言語サイトのレシピに、ロンドンやシドニーの時刻と誤り例まで含めて置いてある。この記事はその先——「今日は」と書けるメールを、いつ出すかの話である。

図 1: 2026 年 4 月の暦。4 月 1 日が水曜なので「毎月第 2 水曜」の予約は 4/8 に鳴るが、1 週間早い。公開は太平洋時間の第 2 火曜 4/14 10:00 で、東京では翌日 4/15(第 3 水曜)の 02:00。通知は 4/15 の 09:00。
図 1: 2026 年 4 月の暦。4 月 1 日が水曜なので「毎月第 2 水曜」の予約は 4/8 に鳴るが、1 週間早い。公開は太平洋時間の第 2 火曜 4/14 10:00 で、東京では翌日 4/15(第 3 水曜)の 02:00。通知は 4/15 の 09:00。

3. 「今日」はどこの今日か

上の列を見ると、公開は日本では水曜の未明である。だから「今日は Windows Update の日です」と書けるのは水曜の朝で、そのとき更新はもう出ている。前日の火曜の夕方に送るなら「今夜(未明に)公開されます」。同じ一点を、ロンドンの人は火曜の 18 時に受け取るので、向こうの「今日」は火曜である。

「今日」は tz に依存する。メールの文面が「今日」を含む以上、送る側は「どこの今日か」を決めて持っていなければならない(「どこの 9 時」かを、コードはどう覚えているか)。

4. Kairos で書く——公開の点は太平洋時間で 1 つ、通知は東京の暦で

定義は 2 段になる。公開の点は太平洋時間の前文(premise)の中に置き、通知の時刻は東京の前文からそれを参照して貼る:

premise PT {
  calendar-system: Gregorian; tz: "America/Los_Angeles"; wkst: Sun
  release = everyDay |> filter(d => weekday(d) == Tue) |> within(month) |> nth(2) |> at(T10:00)
}
premise JP { calendar-system: Gregorian; tz: "Asia/Tokyo"; wkst: Mon }

@JP
PT.release |> snapTo(day) |> at(T09:00)

PT.release が公開の瞬間(太平洋時間の第 2 火曜 10 時)。snapTo(day) はその瞬間が属する東京の日へ落とす(floor)。それが水曜である。そこに at(T09:00) で朝 9 時を貼る:

$ kairos list --from 2026-01-01 --to 2027-01-01 --tz Asia/Tokyo notify.kairos
2026-01-14T09:00
2026-02-11T09:00
2026-03-11T09:00
2026-04-15T09:00
2026-05-13T09:00
2026-06-10T09:00
2026-07-15T09:00
2026-08-12T09:00
2026-09-09T09:00
2026-10-14T09:00
2026-11-11T09:00
2026-12-09T09:00

4/15 と 7/15 が正しく第 3 水曜に来ている。定義のどこにも「水曜」も「第 3」も書いていない。太平洋時間の第 2 火曜を東京の日に落としたら、結果としてそうなった——それだけである。

前日の夕方に「今夜」と送る形は、日を 1 つ戻して時刻を貼り替えるだけ:

PT.release |> snapTo(day) |> shift(-1, unit: day) |> at(T17:00)
#=> 2026-01-13T17:00  2026-02-10T17:00  2026-03-10T17:00  2026-04-14T17:00  …

5. 落とし穴——定義を置く場所

最初はこう書きたくなる——release を前文の外に置く形である。しかし、結果が変わる:

@PT
release = everyDay |> filter(d => weekday(d) == Tue) |> within(month) |> nth(2) |> at(T10:00)

@JP
release |> snapTo(day) |> at(T09:00)
#=> 2026-01-13T09:00  2026-02-10T09:00  2026-03-10T09:00  2026-04-14T09:00  …

火曜になった。ファイル直下の 名前 = 式 は糖衣(マクロ)で、使う側の前文で読み直される (仕様 premise 層・ADR-28「= はどこでも同じ束縛」)。@JP から見た release は「日本の第 2 火曜の10 時」であり、太平洋時間はどこにも残らない。太平洋時間の点を持ち運びたいなら、定義をpremise PT { … } の中に置いて PT.release と参照する——本社の暦と、支社の暦で書いた「前文の中の束縛」は、まさにこのためにある。

6. 動かす——前回の 6 行をそのまま

月末最終営業日の自動実行まで通すの型 1 がそのまま使える。crontab に 1 行、判定は同じスクリプト、ジョブがメール送信になるだけである:

0 9 * * *  cd /srv/notify && ./run-if-today.sh notify.kairos ./send-update-mail.sh

リハーサルも同じ。カレンダーが鳴ってしまう 4 月の第 2 水曜(4/8)・第 2 火曜(4/14)・正解の 4/15 で叩く:

$ TODAY=2026-04-08 ./run-if-today.sh notify.kairos ./send-update-mail.sh
$ TODAY=2026-04-14 ./run-if-today.sh notify.kairos ./send-update-mail.sh
$ TODAY=2026-04-15 ./run-if-today.sh notify.kairos ./send-update-mail.sh
send-update-mail: 2026-09-18 10:47:39 送信

4/15 だけ送られる。壁のカレンダーで数え直していた分が、定義の側に移った。

Windows しかない環境でも、同じ型で動く。PowerShell 版の判定スクリプトとタスク スケジューラ(schtasks)への登録、--json をConvertFrom-Json で受ける方法、Outlook に .ics で入れる方法はWindows 管理者のための Kairosにまとめた。cron の行が PowerShell の 1 行に変わるだけで、定義ファイルは同じものを使う。

7. 射程——式が出すのは「公開予定の点」まで

  • 10:00 は公開の時刻であって、各機械への配信・導入の時刻ではない。配信の順序や遅延は Windows Update・WSUS・Intune など配信側の挙動で、権威は Microsoft の告知と各サービスの仕様にある。
  • Microsoft 自身が「通常は」と書いているとおり、臨時(OOB)の更新や延期は式の外である。 「今日は Windows Update の日です」のメールも、公開の予定を知らせるものである。 出たかどうかの確認は、告知を見る。
  • 対象は Windows クライアントの月例更新。Windows Server や Microsoft 365 Apps は別の周期を持つ (多くは同じ第 2 火曜だが、権威はそれぞれのドキュメント)。

まとめ

  • 「第 2 火曜 10 時(太平洋時間)」は、日本では水曜の 2 時か 3 時——4 月と 7 月は第 3 水曜(「第 2 水曜」の予約は 1 週間早く鳴る)
  • 「今日」は tz に依存する。通知の定義は「どこの今日か」を持っていなければならない
  • 公開の点は premise PT の中に置き、PT.release |> snapTo(day) |> at(T09:00) で東京の朝 9 時に
  • ファイル直下の = は使う側の前文で読み直される。太平洋時間を運ぶなら前文の中に置く
  • 動かすのは月末最終営業日の回の 6 行——crontab 1 行と run-if-today.sh

▶ Playground で定義を実行する——2026 年の 12 回が水曜 9 時で出る。コメントアウトしてある「今夜」版に切り替えたり、at(T09:00) を変えたりして試せる。ブラウザ内で完結し、何も送信しない。


Kairos はスケジュール定義言語です。仕様と参照実装はGitHub で公開しています。

コメント

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