概要
Java には Calendar や LocalDate はあっても、「日本の祝日」を返す標準 API はありません。営業日計算や帳票の日付処理で祝日判定が必要になった途端、祝日データを自前で持つ設計を迫られます。ここで悩ましいのがデータの持ち方です。固定日の祝日だけならハードコードでも動きますが、成人の日のようなハッピーマンデー、日曜と重なったときの振替休日、天文計算で決まる春分・秋分が絡むと、単純な定数リストでは翌年に破綻します。この記事では、固定祝日とハッピーマンデーを LocalDate のリストとして組み立てるコードを軸に、振替休日の算出ルール、年次更新を回しやすくするデータ構成、営業日ロジックへのつなぎ方を整理します。
使いどころ
経理システムの支払予定日計算で使う祝日マスタを、年初に一括生成して DB に登録する
帳票の出力予定日が祝日に当たったとき、前営業日へ自動スライドさせる判定に使う
シフト管理画面のカレンダー表示で、祝日セルの背景色を変えるためのデータソースにする
コード例
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
public class JapanHolidays {
// 固定日の祝日
public static List<LocalDate> getFixedHolidays(int year) {
return List.of(
LocalDate.of(year, Month.JANUARY, 1), // 元日
LocalDate.of(year, Month.FEBRUARY, 11), // 建国記念の日
LocalDate.of(year, Month.FEBRUARY, 23), // 天皇誕生日
LocalDate.of(year, Month.APRIL, 29), // 昭和の日
LocalDate.of(year, Month.MAY, 3), // 憲法記念日
LocalDate.of(year, Month.MAY, 4), // みどりの日
LocalDate.of(year, Month.MAY, 5), // こどもの日
LocalDate.of(year, Month.AUGUST, 11), // 山の日
LocalDate.of(year, Month.NOVEMBER, 3), // 文化の日
LocalDate.of(year, Month.NOVEMBER, 23) // 勤労感謝の日
);
}
// 第 n 月曜日を返す(Happy Monday 制度)
public static LocalDate nthMonday(int year, Month month, int n) {
var first = LocalDate.of(year, month, 1);
var offset = (DayOfWeek.MONDAY.getValue()
- first.getDayOfWeek().getValue() + 7) % 7;
return first.plusDays(offset + 7L * (n - 1));
}
// 固定日 + ハッピーマンデー + 振替休日を結合
public static List<LocalDate> getHolidays(int year) {
var holidays = new ArrayList<>(getFixedHolidays(year));
holidays.add(nthMonday(year, Month.JANUARY, 2)); // 成人の日
holidays.add(nthMonday(year, Month.JULY, 3)); // 海の日
holidays.add(nthMonday(year, Month.SEPTEMBER, 3)); // 敬老の日
holidays.add(nthMonday(year, Month.OCTOBER, 2)); // スポーツの日
// 振替休日: 日曜に当たった祝日の直後の平日を追加する
var substitutes = new ArrayList<LocalDate>();
for (var holiday : holidays) {
if (holiday.getDayOfWeek() == DayOfWeek.SUNDAY) {
var next = holiday.plusDays(1);
while (holidays.contains(next)) {
next = next.plusDays(1); // 連休中は後ろへずらす
}
substitutes.add(next);
}
}
holidays.addAll(substitutes);
// 春分・秋分は天文計算のため、近似式か外部データで補完する
holidays.sort(LocalDate::compareTo);
return holidays;
}
public static void main(String[] args) {
for (var h : getHolidays(2025)) {
System.out.println(h + " (" + h.getDayOfWeek() + ")");
}
// 2025-01-01 (WEDNESDAY), 2025-01-13 (MONDAY), ... と並ぶ
// 振替休日の確認: 2026年は憲法記念日(5/3)が日曜。
// 5/4 みどりの日・5/5 こどもの日を飛ばして 5/6(水) が振替休日
var h2026 = getHolidays(2026);
System.out.println("2026-05-06 は休日か: "
+ h2026.contains(LocalDate.of(2026, 5, 6)));
// → true
}
}Version Coverage
Set.of() でイミュータブルな祝日セットを簡潔に初期化できる。var と toList() で記述量が減る。
// Java 17: Set.of() + var + Set.copyOf で簡潔に
var base = Set.of(
LocalDate.of(2024, 1, 1),
LocalDate.of(2024, 1, 8)
);
// 振替休日を追加してイミュータブルな Set を返す
var result = new TreeSet<>(base);
// ... 振替休日の追加処理 ...
return Set.copyOf(result);Library Comparison
注意点
春分の日・秋分の日は天文計算に基づくため、数年先の日付は暫定値になる。
祝日法の改正で祝日が追加・変更される可能性がある。
振替休日は「祝日が日曜のとき、その後の最初の祝日でない日が休日になる」ルール。連休中は翌月曜とは限らない。
「山の日」(2016年制定)など比較的新しい祝日が漏れているコードを保守案件でよく見かける。法定祝日の追加・変更は内閣府のウェブサイトで確認し、年次でリストを見直す運用ルールをチームで決めておくこと。
FAQ
内閣府の公開 CSV は例年2月頃に翌年分が確定します。年次バッチで取り込むか、運用カレンダーに更新タスクを登録して担当者を決めておくのが現実的です。
「祝日が日曜なら直後の平日を休日にする」という祝日法第3条のルールなので実装できます。2つの祝日に挟まれた国民の休日も忘れずに扱う必要があります。
更新が年1回で参照するサービスが1つなら定数で十分です。複数システムで共有する場合や期中の変更に即応したい場合は DB やマスタ API に寄せます。