「休日除外」とは何をしたいユーティリティなのか
「この期間のデータを集計したいけど、休日は含めたくない」「SLA は“営業日ベース”なので、休日はカウントから外したい」。
こういうときに必要になるのが「休日除外」のユーティリティです。
やりたいことを一言でいうと、
「日付の集合から、休日にあたる日をきれいに取り除く」
という処理です。
ここでいう“休日”には、土日・祝日・会社独自の休業日などが含まれます。
まずは「休日とは何か」をコードで表現する
土日を休日とみなす最小バージョン
最初の一歩として、「土日=休日」というシンプルなルールから始めます。
import java.time.DayOfWeek;
import java.time.LocalDate;
public class HolidayRules {
public static boolean isWeekend(LocalDate date) {
DayOfWeek dow = date.getDayOfWeek();
return dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY;
}
public static boolean isHolidayBasic(LocalDate date) {
return isWeekend(date);
}
}
Javaここで大事なのは、「休日かどうかの判定を1か所に閉じ込める」という発想です。
あちこちで「if (dow == SATURDAY || dow == SUNDAY)」と書き散らさず、必ず isHolidayBasic を通すようにしておくと、後からルールを変えやすくなります。
祝日・会社休日を含めた“本気の休日判定”
実務では、「土日以外にも休み」が必ず出てきます。
代表例が「祝日」と「会社独自の休業日」です。
これをコードにベタ書きするのではなく、「休日マスタ」としてデータで持つのが定石です。
import java.time.LocalDate;
import java.util.Set;
public class HolidayRules {
private final Set<LocalDate> holidays;
public HolidayRules(Set<LocalDate> holidays) {
this.holidays = holidays;
}
public boolean isHoliday(LocalDate date) {
if (isWeekend(date)) {
return true;
}
if (holidays.contains(date)) {
return true;
}
return false;
}
private boolean isWeekend(LocalDate date) {
switch (date.getDayOfWeek()) {
case SATURDAY:
case SUNDAY:
return true;
default:
return false;
}
}
}
Javaここで深掘りしたいポイントは二つです。
休日判定は「土日か?」「休日マスタに含まれているか?」の二段構えで行うこと。
祝日や会社休日はコードではなくデータ(Set<LocalDate> など)として持つこと。
これにより、「来年の祝日を追加する」「会社独自の休みを増やす」といった変更を、コード変更なしで対応できるようになります。
「休日除外」の基本パターン:一覧から休日を取り除く
日付リストから休日をフィルタする
休日判定ができたら、次にやることはシンプルです。
「日付のリストから、休日にあたるものを取り除く」だけです。
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
public class HolidayExcluder {
private final HolidayRules rules;
public HolidayExcluder(HolidayRules rules) {
this.rules = rules;
}
public List<LocalDate> excludeHolidays(List<LocalDate> dates) {
return dates.stream()
.filter(d -> !rules.isHoliday(d))
.collect(Collectors.toList());
}
}
Java使い方の例です。
import java.time.LocalDate;
import java.util.List;
import java.util.Set;
public class HolidayExcluderExample {
public static void main(String[] args) {
Set<LocalDate> holidays = Set.of(
LocalDate.of(2025, 1, 1),
LocalDate.of(2025, 1, 13)
);
HolidayRules rules = new HolidayRules(holidays);
HolidayExcluder excluder = new HolidayExcluder(rules);
List<LocalDate> allDates = List.of(
LocalDate.of(2025, 1, 10),
LocalDate.of(2025, 1, 11),
LocalDate.of(2025, 1, 12),
LocalDate.of(2025, 1, 13)
);
List<LocalDate> businessDates = excluder.excludeHolidays(allDates);
businessDates.forEach(System.out::println);
}
}
Javaこの例では、金曜・土曜・日曜・祝日が混ざったリストから、
土日と祝日を除いた「平日のみ」が残ります。
ここで重要なのは、「休日除外のロジックを1か所にまとめる」ことです。
集計処理やレポート処理の中に直接「if (!isHoliday(d))」と書くのではなく、
必ず HolidayExcluder を通すようにしておくと、コードの意図がとても読みやすくなります。
日付範囲+休日除外=「営業日一覧」
範囲生成と組み合わせる
「開始日〜終了日のあいだで、休日を除いた日だけ欲しい」というのは、まさに「営業日一覧」です。
これは「日付範囲生成」と「休日除外」を組み合わせれば簡単に書けます。
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class BusinessDayRange {
private final HolidayRules rules;
public BusinessDayRange(HolidayRules rules) {
this.rules = rules;
}
public List<LocalDate> businessDaysBetween(LocalDate startInclusive, LocalDate endInclusive) {
if (endInclusive.isBefore(startInclusive)) {
throw new IllegalArgumentException("終了日は開始日以降である必要があります");
}
List<LocalDate> result = new ArrayList<>();
LocalDate d = startInclusive;
while (!d.isAfter(endInclusive)) {
if (!rules.isHoliday(d)) {
result.add(d);
}
d = d.plusDays(1);
}
return result;
}
}
Javaここでは「営業日一覧」という名前にしていますが、やっていることは「休日を除外した日付範囲の生成」です。
休日除外の考え方が、そのまま営業日一覧の土台になっているのが分かると思います。
「休日を除外した件数」「休日を除外した合計」などの集計
ストリームで“休日を飛ばして集計する”
休日除外は、日付だけでなく「データの集計」にもよく使います。
例えば「休日を除いた日だけの売上合計」「休日を除いた平均値」などです。
イメージしやすいように、日付と売上金額のペアを例にします。
import java.time.LocalDate;
public class DailySales {
private final LocalDate date;
private final int amount;
public DailySales(LocalDate date, int amount) {
this.date = date;
this.amount = amount;
}
public LocalDate getDate() {
return date;
}
public int getAmount() {
return amount;
}
}
Javaこれを「休日を除外して合計する」ユーティリティはこう書けます。
import java.util.List;
public class HolidayAwareAggregator {
private final HolidayRules rules;
public HolidayAwareAggregator(HolidayRules rules) {
this.rules = rules;
}
public int sumExcludingHolidays(List<DailySales> records) {
return records.stream()
.filter(r -> !rules.isHoliday(r.getDate()))
.mapToInt(DailySales::getAmount)
.sum();
}
}
Javaここで深掘りしたいのは、「集計ロジックの中に休日判定を直接書かない」ということです。HolidayRules を必ず経由することで、「休日の定義が変わっても集計ロジックは変えなくてよい」状態を作れます。
セキュリティ・運用の観点から見た「休日除外」
休日マスタは“業務ルール”であり、“監査対象”でもある
休日除外は、一見ただの便利ユーティリティですが、実はビジネスルールそのものです。
請求日、支払日、納期、SLA などに直結するため、ここが間違うとお金や契約に影響します。
だからこそ、次のような運用が重要になります。
休日マスタ(祝日・会社休日)はコードではなくデータとして管理する。
誰がいつどの休日を追加・変更したか、履歴を残す。
本番反映前に「休日マスタのレビュー」を行う運用を決める。
これはセキュリティというより「ガバナンス」に近い話ですが、
システムとしては「休日除外ユーティリティ」がその入口になります。
範囲が広すぎる処理をそのまま受けない
「2000年〜2100年の全日付から休日を除外して一覧にして」といった要求をそのまま受けると、
3万日以上に対して休日判定を行い、リストに詰めることになります。
実務では、例えば次のような制限をユーティリティ側に組み込むことが多いです。
休日除外を行う対象期間の最大長を決める(例:3年分まで)。
それを超える要求が来たら例外にする、あるいは警告ログを出す。
これは、誤設定や悪意ある入力による負荷増大(事実上のDoS)を防ぐための防御線です。
まとめ:「休日除外」で身につけてほしい感覚
休日除外ユーティリティは、「日付やデータの集合から、休日にあたるものをきれいに取り除く」ための道具です。
でも、その裏には次のような大事な考え方が隠れています。
休日判定ロジックを1か所に集約し、土日・祝日・会社休日をそこで判断すること。
休日はコードではなくマスタデータとして管理し、変更しやすく・追跡しやすくすること。
日付範囲生成や集計処理と組み合わせて、「休日を除外した営業日ベースの処理」を自然に書けるようにすること。
処理対象期間に上限を設け、誤設定や負荷増大を防ぐこと。
もしあなたのプロジェクトで、
「土日を if で飛ばしているコード」「祝日を個別に除外しているコード」があちこちに散らばっているなら、
それを一度、「休日ルール+休日除外ユーティリティ」に集約できないか眺めてみてください。
それだけで、コードの意図がはっきりし、
ビジネスルールの変更にも強い、“業務で戦える”日付・時間まわりの設計に一歩近づきます。
