C# Tips | 日付・時間処理:祝日判定

C# C#
スポンサーリンク

はじめに 「祝日判定」は“営業日ロジックの一番やっかいな部分”

土日判定はコードで簡単に書けますが、「祝日」はそうはいきません。
日本の祝日は法律で決まっていて、改正されたり、オリンピックや即位などのイベントで特例が入ったりします。
だからこそ、祝日判定は「どうデータを持つか」「どこまで自前でやるか」から設計する必要があるんです。

ここでは、
祝日判定の考え方 → 自前マスタ方式 → 外部ライブラリ・API方式 → 営業日判定とのつなぎ込み
という流れで、初心者向けにかみ砕いて説明していきます。


祝日判定の前提:「コードだけで完結させない」発想を持つ

なぜ DateTime だけで祝日は分からないのか

DateTime には曜日を取る DayOfWeek はありますが、「祝日かどうか」を教えてくれるプロパティはありません。
理由はシンプルで、祝日は国ごとに違うし、年によって変わるからです。

つまり、祝日判定には必ず「どこかに祝日データを持つ」か「祝日を計算してくれる仕組みを使う」必要があります。
ここを理解しておくと、「DateTime だけでなんとかしよう」と無理をしなくて済みます。


パターン1:自前で「祝日マスタ」を持つ方式

祝日一覧をデータとして持ち、それを参照して判定する

一番ストレートで、基幹システムでもよく使われるのが「祝日マスタ方式」です。
考え方はこうです。

  1. 祝日一覧(年月日と名称)をどこかに保存しておく(DB、CSV、設定ファイルなど)
  2. 判定したい日付が、その一覧に含まれているかどうかを調べる

まずは、メモリ上に「祝日セット」を持つ簡易版から見てみましょう。

using System;
using System.Collections.Generic;

public class HolidayMaster
{
    private readonly HashSet<DateOnly> _holidays;

    public HolidayMaster(IEnumerable<DateOnly> holidays)
    {
        _holidays = new HashSet<DateOnly>(holidays);
    }

    public bool IsHoliday(DateTime date)
    {
        var d = DateOnly.FromDateTime(date);
        return _holidays.Contains(d);
    }
}
C#

使い方の例です。

var holidays = new[]
{
    new DateOnly(2026, 1, 1),  // 元日
    new DateOnly(2026, 1, 12), // 成人の日(例)
    new DateOnly(2026, 2, 11), // 建国記念の日(例)
};

var holidayMaster = new HolidayMaster(holidays);

DateTime d1 = new DateTime(2026, 1, 1);
DateTime d2 = new DateTime(2026, 1, 13);

Console.WriteLine(holidayMaster.IsHoliday(d1)); // true
Console.WriteLine(holidayMaster.IsHoliday(d2)); // false
C#

ここでのポイントは、「祝日判定のロジック」はめちゃくちゃシンプルで、
“祝日データをどう用意し、どう更新するか”が本質だということです。

祝日マスタ方式のメリット・デメリット

メリットは、次のようなイメージです。

  • 自社ルール(会社独自の休業日など)も一緒に管理できる
  • 将来祝日が変わっても、マスタを更新するだけで済む
  • ロジックは単純なのでバグりにくい

デメリットは、

  • マスタを誰かがメンテし続ける必要がある
  • マスタの初期データをどこかから持ってくる必要がある

という点です。

「基幹システム」「長く動かす業務システム」では、この方式がかなり王道です。


パターン2:外部ライブラリや API を使う方式

「祝日計算ライブラリ」を使う

日本の祝日だけに限っても、「計算で求める」ライブラリがいくつか存在します。
NuGet で「Japan Holiday」「HolidayJp」などを検索すると、
「1955〜2150年までの日本の祝日を計算で出せる」といったライブラリが見つかります。

こうしたライブラリを使うと、コード側はかなりシンプルになります。

イメージとしては、こんな感じです。

public interface IHolidayProvider
{
    bool IsHoliday(DateTime date);
}

public class LibraryHolidayProvider : IHolidayProvider
{
    public bool IsHoliday(DateTime date)
    {
        // ここで外部ライブラリのAPIを呼ぶイメージ
        // 例: return HolidayJp.IsHoliday(date);
        throw new NotImplementedException();
    }
}
C#

アプリ側は IHolidayProvider 経由で祝日判定を呼ぶだけにしておけば、
「自前マスタ」から「ライブラリ」への切り替えも簡単にできます。

祝日 API を叩く方式

別のパターンとして、「祝日 API」を提供しているサービスを叩く方法もあります。
HTTP で「この年の祝日一覧」を取得し、それをキャッシュして使うイメージです。

ただし、API 方式は、

  • ネットワーク障害時にどうするか
  • API 側の仕様変更にどう追従するか
  • レート制限や利用規約をどう守るか

といった運用上の課題が増えるので、
「社内システムで長く安定して動かしたい」場合は、
自前マスタ方式やライブラリ方式のほうが扱いやすいことが多いです。


設計のキモ:「祝日判定」をインターフェースで抽象化する

直接 new しないで、インターフェース越しに扱う

実務で一番効いてくるのは、
祝日判定をインターフェースで抽象化しておくことです。

例えば、こんなインターフェースを用意します。

public interface IHolidayProvider
{
    bool IsHoliday(DateTime date);
}
C#

そして、営業日判定ユーティリティは、
このインターフェースだけを知っていればよいようにします。

public static class BusinessDayUtil
{
    private static IHolidayProvider? _holidayProvider;

    public static void SetHolidayProvider(IHolidayProvider provider)
    {
        _holidayProvider = provider;
    }

    public static bool IsWeekend(DateTime date)
    {
        DayOfWeek w = date.DayOfWeek;
        return w == DayOfWeek.Saturday || w == DayOfWeek.Sunday;
    }

    public static bool IsHoliday(DateTime date)
    {
        if (_holidayProvider == null)
        {
            return false;
        }

        return _holidayProvider.IsHoliday(date);
    }

    public static bool IsBusinessDay(DateTime date)
    {
        return !IsWeekend(date) && !IsHoliday(date);
    }
}
C#

こうしておけば、

  • 今は「固定祝日マスタ」で実装
  • 将来「外部ライブラリ」に差し替え
  • テストでは「祝日なし」「毎日祝日」などのダミー実装を使う

といったことが、IHolidayProvider の実装を差し替えるだけでできるようになります。


祝日判定を使った実用例:営業日判定・営業日加算

営業日判定に組み込む

祝日判定単体で使うことはあまりなく、
多くの場合「営業日判定」の一部として使われます。

先ほどの BusinessDayUtil のように、

  • 土日かどうか → IsWeekend
  • 祝日かどうか → IsHoliday
  • 営業日かどうか → IsBusinessDay

という分担にしておくと、
ロジックがとても読みやすくなります。

DateTime d = new DateTime(2026, 1, 1);

if (BusinessDayUtil.IsBusinessDay(d))
{
    Console.WriteLine("営業日です");
}
else
{
    Console.WriteLine("休業日です");
}
C#

営業日加算に組み込む

「3営業日後」「5営業日後」といった計算も、
祝日判定を組み込んだ IsBusinessDay を使えば簡単に書けます。

public static DateTime AddBusinessDays(DateTime start, int businessDays)
{
    if (businessDays < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(businessDays));
    }

    DateTime current = start;
    int remaining = businessDays;

    while (remaining > 0)
    {
        current = current.AddDays(1);

        if (BusinessDayUtil.IsBusinessDay(current))
        {
            remaining--;
        }
    }

    return current;
}
C#

祝日判定をきちんとユーティリティに閉じ込めておくことで、
「営業日を使うロジック」がすべてシンプルに書けるようになります。


タイムゾーンと「どの国の祝日か」を忘れない

祝日は“国とタイムゾーン”に強く依存する

祝日は国ごとに違いますし、
同じ日付でもタイムゾーンによって「その国では前日/翌日」ということもあります。

日本の祝日を扱うなら、

  • どのタイムゾーンで日付を切るか(日本時間か、UTCか)
  • サーバー内部では UTC で持ち、判定時に日本時間に変換するのか

といった方針を最初に決めておくことが大事です。

例えば、「UTC から日本時間に変換してから祝日判定する」イメージはこうです。

public static bool IsJapaneseHolidayFromUtc(DateTime utcDateTime, IHolidayProvider provider)
{
    TimeZoneInfo jst = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
    DateTime jstTime = TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, jst);

    return provider.IsHoliday(jstTime);
}
C#

「どの国の祝日か」「どのタイムゾーンか」をユーティリティ名やコメントに明示しておくと、
後から読んだ人が迷わずに済みます。


まとめ 「祝日判定ユーティリティ」は“変わりうる現実をコードから切り離す”

祝日判定は、
ロジックそのものよりも「データが変わる」「ルールが変わる」ことが本質です。

押さえておきたいポイントを整理すると、こうなります。

祝日は DateTime だけでは分からないので、「祝日データ」か「祝日計算ロジック」が必須になる。
自前マスタ方式は、基幹システムでよく使われる王道で、ロジックは単純だがデータのメンテが必要。
外部ライブラリや API を使う方式もあるが、依存先の変更や障害をどう扱うかを考える必要がある。
IHolidayProvider のようなインターフェースで祝日判定を抽象化しておくと、実装の差し替えやテストが圧倒的に楽になる。
祝日判定は営業日判定・営業日加算とセットで使われることが多く、「どの国・どのタイムゾーンの祝日か」を最初に決めておくことが重要。

ここまで押さえておけば、
「なんとなく祝日っぽい日を if で書いている」状態から一歩進んで、
“現実の変化に耐えられる、実務で使える祝日判定ユーティリティ”を
自分の C# コードの中にきちんと組み込めるようになります。

C#C#
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました