営業日 vs 暦日: 祝日を含めた締め切りの計算

営業日を正しく数え、足す方法、つまり O(1) の平日公式、祝日カレンダーの問題、そして素朴なコードを壊す包含/排他の端点と時間帯のエッジケースを整理します。

営業日で表された締め切りは、同じ数の暦日とは別の量であり、両者の間の差が、請求の 紛争、見逃した提出、怒った顧客の出どころです。請求書の「Net 30」は 30 暦日を意味 します。「3 営業日以内に発送」は週末を除きます。法的通知の「5 営業日以内に応答」は 週末 祝日を除き、どの祝日かは管轄によります。計算は些細に見えて、ほとんど 決してそうではありません。営業日の定義が地域的で、可変で、エッジケースに満ちている からです。

なぜこの区別が重要か

2 つの単位は、1 週間より長いどの期間でも数日まで分かれ、契約・SLA・法令が意図的に 一方を選びます。

  • 支払い条件。 「Net 30」は 30 暦日で、「30 営業日」はおよそ 42 暦日です。 一方を他方として扱うと、期日を 1 週間半ずらし、延滞料や違反条項を引き起こし えます。
  • SLA。 「翌営業日」応答のサポート SLA は、金曜の夜のチケットが土曜ではなく 月曜が期限という意味で、月曜が祝日なら火曜が期限です。
  • 発送 ETA。 配送業者は輸送を営業日で見積もります。木曜に注文した 3 営業日の 発送は、日曜ではなく翌火曜に届きます。
  • 法的・提出の締め切り。 裁判所や税務当局は期間を営業日で定義し、しばしば週末 や祝日に当たる締め切りが翌営業日へ繰り越されるルールを足します。これを間違える のは丸め誤差ではなく、見逃した提出です。

単位を間違えると答えが予測可能な量だけ間違い、これはランダムな誤りより悪いです。 お金や締め切りがかかるまで誰も気づかないからです。

定義

暦日 はすべてを数えます。平日、週末、祝日です。2 つの日付の間の暦日の差は、 単に日付の番号の引き算です。

営業日 (稼働日) は週末と祝日を除きます。2 つの仮定がここに隠れ、どちらも どこかで間違っています。

  • 週末は普遍的ではありません。 世界の大半は土曜と日曜に休みますが、中東の一部 は金曜〜土曜の週末を使い、いくつかの管轄は木曜〜金曜や単一の休日を使ってきました。 「週末」は定数ではなく設定値です。
  • 祝日は地域的です。 ある国の営業日が別の国では祝日で、一国の中でも祝日が 国家的、地域的、または特定の取引所や産業に固有でありえます。

だから営業日は「設定された週末の日でも、関連するカレンダーの祝日でもない日」です。 以下のすべてが、そのカレンダーを正しく持つことにかかっています。

2 つの日付の間の営業日を数える

素朴な方法は 1 日ずつ回り、その日が平日で祝日でないときにカウンタを上げます。 正しく読みやすく、日数に O(n) です。数週間には問題なく、数年の期間には無駄です。

2 つの日付の間の平日の数には O(1) の閉形式の近道があります。鍵となる観察は、7 日の すべての完全なブロックがちょうど 5 つの平日を寄与する、ということです。だから期間を 完全な週と余りに分けます。

# 半開区間 [start, end) の平日を数える
# (start を含み、end を除く)。Mon=0 .. Sun=6。

total_days   = end - start            # 日単位
full_weeks   = total_days // 7
remainder    = total_days % 7         # 0..6 の残りの日

weekdays = full_weeks * 5

# 残りの日だけを歩く。start の曜日から始める:
for i in 0 .. remainder-1:
    if (weekday(start) + i) % 7 < 5:   # 0..4 が月..金
        weekdays += 1

# 今、[start, end) 内の平日に当たる祝日を引く:
business_days = weekdays - count_weekday_holidays(start, end)

余りのループは期間の長さによらず最大 6 回回るので、全体が O(1) に祝日チェックの コストを足したものです。祝日のデータがソート済みのリストや日付でキー付けした集合 なら、範囲内の祝日を数えるのは全走査ではなく、限られた参照です。

端点に注意してください。 上記の公式は半開区間 [start, end) を数えます。start は数え、end は数えません。両端を含む ([start, end]) は 1 日多く、両端を除くは 1 日少ないです。ほとんどすべての日付計算のオフバイワンのバグは、同じシステムの 2 つ の部分の間の端点の規約の不一致です。1 つの規約を選び、コメントに書き、どこでも適用 してください。迷ったら、半開区間はきれいに合成されます。[a, c) 上のカウントは [a, b)[b, c) を足したものと等しく、閉区間はそれをただではくれません。

祝日の引き算には固有の罠があります。平日に当たる祝日だけを引いてください。土曜に 当たる祝日はそもそも平日として数えられていないので、それを引くと過少計算になります。

日付に N 営業日を足す

日付の を数えることと、日付を 前進 させることは別の演算で、上記の公式は 「N 営業日を足す」へ直接は逆転しません。前進は歩いて飛ばす方式できれいにできます。

# `start` から N 営業日後の日付を返す。
# N > 0 で前へ進む。`start` 自身は数えない。

date = start
added = 0
while added < N:
    date = date + 1 day
    if is_weekday(date) and not is_holiday(date):
        added += 1
return date

これは営業日に O(N) で、実務では問題ありません。誰も 1 万営業日を足しません。閉形式 の版も存在しますが (完全な週を計算し、それから余りを歩く)、歩いて飛ばす形のほうが 間違えにくく、カスタムの週末や祝日の集合へ拡張しやすいです。

身につけるべきはこれです。「+5 営業日」は「+7 暦日」ではありません。 偶然を 除いては。月曜に 5 営業日を足すと次の月曜に着きます。その間に祝日がないときだけ 7 暦日です。水曜に 5 営業日を足すと翌水曜に着き、これも 7 暦日ですが、祝日があれば 8 日です。週の途中で始め、範囲に祝日があると、暦の差は 9 日以上になりえます。関係は 固定の倍数ではありません。

祝日の問題

週末は固定の規則です。祝日はデータで、データは腐ります。

  • 祝日は地域ごとです。 正しい計算は 関連する 管轄の祝日の集合を必要とし、 「関連する」は国、州や県、または特定の証券取引所や銀行システムでありえます。米国 連邦のカレンダーはニューヨーク証券取引所の取引カレンダーと一致せず、それはドイツの ある州 (Land) と一致しません。
  • 観測のずれ。 固定日の祝日が週末に当たると、多くのシステムが隣接する平日に それを観測します。土曜の祝日は前の金曜に、日曜の祝日は後の月曜に。観測日が営業日の 除外であって名目上の日付ではなく、どちらにずれるかの規則自体が管轄ごとです。
  • 移動する祝日。 一部の祝日は別の出来事に相対的に、または太陰暦や太陰太陽暦で 定義され、毎年異なるグレゴリオ暦の日付に当たります。その日付は単純な固定の月・日の 規則では計算できず、それを発行する機関から来なければなりません。

これが、2026 年にハードコードした祝日のリストが 2028 年に間違っている理由です。新しい 祝日が宣言され、一度きりの祝日 (選挙、王室の行事、国家の服喪) が現れ、移動するものが ずれていきます。営業日計算機は、ソースに埋め込んだ定数ではなく、維持されるカレンダー、 つまり規則を追うライブラリやあなたが更新するデータソースを必要とします。私たちの 祝日カレンダーツール は、まさにそのソースになるために存在 します。推測する代わりに調べられる国ごとの集合です。

噛みつくエッジケース

  • 半日。 一部のカレンダーは部分的な営業日を含みます。早く閉まる取引所、大きな 祝日の前の半日です。単位が丸ごとの営業日なら、半日が 1 つとして数えるか、0 として か、それとも端数としてかを明示的に決め、文書化してください。
  • 時間帯。 「営業日の終了」は時間帯なしには無意味です。誰の都市で午後 5:00 の 締め切りですか。ロサンゼルスで間に合った提出が、フランクフルトでは 1 日遅れになり えます。時間帯を無視する日付計算は、日付変更線の反対側の誰かに丸 1 日ずれた締め 切りを作り、これは Unix タイムスタンプと 2038 年問題 で論じたのと同じ種類のバグです。
  • 締め切り時刻。 多くの「営業日」の規則に当日の締め切り時刻があります。午後 2:00 前に入れた注文は今日発送、午後 2:00 後は翌営業日として数えます。締め切り時刻は 営業日の計算が始まる前に事実上の開始日をずらすので、それを先に適用してください。
  • ロール規約。 計算された締め切りが非営業日に当たると、契約がそれを翌営業日へ 前にロールするか、前の営業日へ後ろにロールするか、そのまま置くかを決めます。これは 数えること自体とは別の規則で、明示されなければなりません。

計算は入力が固定されれば単純です。週末の定義、祝日のカレンダー、端点の規約、時間帯、 ロールの規則です。誤りはほぼ全面的に、そのうちの 1 つを暗黙のままにすることに宿り ます。

カレンダーのライブラリを組み込まずに答えが必要なら、私たちの 営業日計算機 が 2 つの日付の間の稼働日を数え、維持される国ごと の祝日の集合に対して営業日を足したり引いたりし、端点の規約が処理されるので、祝日の ロジックを作り直さずに締め切りを確認できます。