Java Tips | 日付・時間:休日除外

Java Java
スポンサーリンク

「休日除外」とは何をしたいユーティリティなのか

「この期間のデータを集計したいけど、休日は含めたくない」「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 で飛ばしているコード」「祝日を個別に除外しているコード」があちこちに散らばっているなら、
それを一度、「休日ルール+休日除外ユーティリティ」に集約できないか眺めてみてください。

それだけで、コードの意図がはっきりし、
ビジネスルールの変更にも強い、“業務で戦える”日付・時間まわりの設計に一歩近づきます。

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