Javaでカレンダーを作るときに押さえるのは、「1日が何曜日か」と「その月が何日まであるか」の2つだけです。
LocalDateならgetDayOfWeek()とlengthOfMonth()で両方を取得でき、for文1つで曜日のそろったカレンダーを表示できます。
年月を入力して表示する基本コード、授業や課題で指定されやすいCalendarクラス版、振替休日・国民の休日まで判定する祝日対応版を、実際に動かした出力結果付きで紹介します。
この記事でわかること
- 年月を入力してカレンダーを表示するコード(
LocalDate版) - 1日の曜日に合わせて日付をずらすfor文の考え方
- 課題でよく使う
Calendarクラス版の書き方と注意点 - 春分の日・振替休日・国民の休日まで含めた祝日の判定方法
- 内閣府の祝日CSVを読み込んで祝日を管理する方法
PARTS LIST /目次
Javaでカレンダーを表示する仕組み
コードを書く前に、カレンダー表示の流れを整理します。
やることは「1日の曜日の分だけ空白を出す」「1日から月末までfor文で数字を出す」「土曜日まで出したら改行する」の3つです。
getDayOfWeek() と lengthOfMonth()(day + offset) % 7 == 0完成イメージと必要な機能
今回作るカレンダーの仕様は次の3つです。
ポイント
- 入力した年・月のカレンダーを表示する
- 日曜始まりで曜日の見出しを付ける
- 1日の曜日に合わせて日付の位置をずらす
2026年9月を指定すると、次のように表示されるのがゴールです。
2026年9月のカレンダー
日 月 火 水 木 金 土
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
CalendarクラスとLocalDateどちらで作る?
Javaの標準ライブラリで日付を扱うクラスは、古いCalendarクラスと、Java 8で追加されたLocalDateの2つが代表的です。
結論として、指定がなければLocalDateを選んでください。まずは両方で今日の日付を取得して、書き方の違いを比べます。
Calendarクラスで日付を取得する
Calendarクラスはjava.utilパッケージにある日付クラスで、get()に定数を渡して年・月・日を取り出します。
import java.util.Calendar;
public class CalendarExample {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance(); // 現在日時のカレンダーを生成
int year = calendar.get(Calendar.YEAR); // 年を取得
int month = calendar.get(Calendar.MONTH) + 1; // 月は0始まりなので+1
int day = calendar.get(Calendar.DAY_OF_MONTH); // 日を取得
System.out.println("今日の日付: " + year + "年" + month + "月" + day + "日");
}
}
2026年9月24日に実行した結果です。
今日の日付: 2026年9月24日
Calendar.MONTHは0始まり(1月=0)なので、表示するときは+1が必要です。
LocalDateで日付を取得する
LocalDateは、Java 8で追加された日付専用のクラスです。
年・月・日を1始まりで扱い、一度作ると値が変わらないイミュータブル(不変)な設計です。
import java.time.LocalDate;
public class LocalDateExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now(); // 今日の日付を取得
System.out.println("今日の日付: " + today);
System.out.println(today.getYear() + "年" + today.getMonthValue() + "月" + today.getDayOfMonth() + "日");
}
}
今日の日付: 2026-09-24
2026年9月24日
LocalDateはコードが短く、月の0始まりも気にしなくて済みます。
CalendarクラスとLocalDateの違い
両者の違いを表にまとめます。
Calendarクラスは月が0始まりでバグを生みやすく、値を書き換えられるため意図しない変更も起こります。2026年9月時点の最新LTSはJava 25で、LocalDateはJava 8以降のどのバージョンでも標準で使えます。
既存コードがCalendarを使っている場合は、LocalDate.ofInstant(cal.toInstant(), cal.getTimeZone().toZoneId())(Java 9以降)でLocalDateに変換できます。Calendarのタイムゾーンを引き継ぐ書き方で、ZoneId.systemDefault()を渡すと、Calendarが別のタイムゾーンのときに日付がずれます。新しく書く部分だけLocalDateにして、境目で変換するのが現実的な移行方法です。
後半の祝日カレンダーではLocalDateをMapのキーに使います。LocalDateは値ベースでequals・hashCodeが実装されているため、同じ年月日なら別インスタンスでも正しく一致します。
古いDateクラスは値を書き換えられるため、HashMapのキーに登録した後で値が変わると、そのキーで検索できなくなります。
LocalDateでカレンダーを表示する(年月入力版)
ここからが本題です。キーボードから年と月を入力し、その月のカレンダーを表示するプログラムを作ります。
サンプルコード(CalendarApp.java)
そのままコピーしてCalendarApp.javaとして保存すれば動きます。Java 8以降で動作し、Java 23で実行を確認しました。
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.Scanner;
public class CalendarApp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 年と月を入力してもらう
System.out.print("年を入力してください: ");
int year = scanner.nextInt();
System.out.print("月を入力してください: ");
int month = scanner.nextInt();
scanner.close();
// 1〜12以外の月はLocalDate.ofで例外になるため先にチェックする
if (month < 1 || month > 12) {
System.out.println("月は1〜12で入力してください");
return;
}
printCalendar(year, month);
}
public static void printCalendar(int year, int month) {
LocalDate firstDay = LocalDate.of(year, month, 1); // 月の1日
int lastDay = firstDay.lengthOfMonth(); // 月の日数(28〜31)
DayOfWeek startDay = firstDay.getDayOfWeek(); // 1日の曜日
System.out.println(year + "年" + month + "月のカレンダー");
System.out.println(" 日 月 火 水 木 金 土");
// getValue()は月曜=1〜日曜=7。% 7で日曜=0〜土曜=6に変換する
int offset = startDay.getValue() % 7;
for (int i = 0; i < offset; i++) {
System.out.print(" "); // 1日の曜日まで空白で埋める
}
for (int day = 1; day <= lastDay; day++) {
System.out.printf("%3d", day);
if ((day + offset) % 7 == 0) { // 土曜日まで出力したら改行
System.out.println();
}
}
System.out.println();
}
}
ポイント
- ユーザーが年と月を入力
LocalDateを使って月初めの日付を取得- その月の最終日を計算
- 曜日に合わせて空白を出力
- 日付を整列して表示
for文で曜日をずらす仕組み
カレンダー表示でつまずきやすいのは、1日を正しい曜日の列に置く処理です。
getDayOfWeek().getValue()は月曜=1〜日曜=7を返します。これを% 7すると日曜=0、月曜=1…土曜=6になり、日曜始まりの列番号として使えます。
2026年9月1日は火曜日なのでoffsetは2です。先に空白を2マス分出してから「1」を出力すると、火曜の列にそろいます。
改行の判定は(day + offset) % 7 == 0です。空白の数と日付を足した合計が7の倍数になった位置が土曜日の列なので、そこで改行します。
lengthOfMonth() = 30 まで出して終了getValue() = 2、offset = 2 % 7 = 2。結果が0にならない日は改行せず、同じ行に続けて出力します。0になる5日・12日・19日・26日が土曜の列です。実行結果
2026と9を入力したときの実際の出力です。
年を入力してください: 2026
月を入力してください: 9
2026年9月のカレンダー
日 月 火 水 木 金 土
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
うるう年もlengthOfMonth()が自動で判定します。2024年2月を指定すると、29日まで表示されます。
2024年2月のカレンダー
日 月 火 水 木 金 土
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29
月に13を入力すると「月は1〜12で入力してください」と表示して終了します。
コンソールではなくウィンドウにカレンダーを出したいときは、Swingで画面を作ります。SwingとJavaFXのどちらで作るかはJava GUI開発入門で比べています。
よくあるエラーと対処法
カレンダー作りでつまずきやすいのは次の3点です。
Calendar.MONTHが0始まり:Calendarクラスでは1月が0、12月が11です。表示時に+1を忘れると月がずれます。LocalDateなら1始まりなのでこの問題は起きません。DayOfWeek.getValue()の戻り値:LocalDateのgetDayOfWeek().getValue()は月曜=1、日曜=7を返します。日曜始まりのカレンダーにするには% 7で変換が必要です。- 存在しない日付の指定:
LocalDate.of(2024, 13, 1)のように範囲外の月を指定するとDateTimeExceptionが発生します。サンプルコードでは、LocalDate.of()を呼ぶ前に月の範囲をチェックしています。
Calendarクラスでカレンダーを作る場合
授業や課題でCalendarクラスを使うよう指定されることもあります。考え方はLocalDate版と同じで、「1日の曜日」と「月の日数」の取り方だけが変わります。
import java.util.Calendar;
public class CalendarClassApp {
public static void main(String[] args) {
int year = 2026;
int month = 9; // 表示したい月(1〜12)
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(year, month - 1, 1); // MONTHは0始まりなので-1する
int lastDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); // 月の日数
int offset = cal.get(Calendar.DAY_OF_WEEK) - 1; // 日曜=1〜土曜=7 を 0〜6 に
System.out.println(year + "年" + month + "月のカレンダー");
System.out.println(" 日 月 火 水 木 金 土");
for (int i = 0; i < offset; i++) {
System.out.print(" ");
}
for (int day = 1; day <= lastDay; day++) {
System.out.printf("%3d", day);
if ((day + offset) % 7 == 0) {
System.out.println();
}
}
System.out.println();
}
}
実行すると、LocalDate版と同じ2026年9月のカレンダーが表示されます。
2026年9月のカレンダー
日 月 火 水 木 金 土
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
LocalDate版との違いは次の3点です。
Calendar.DAY_OF_WEEKは日曜=1なので、1を引くだけで日曜始まりの列番号になります。一方、MONTHは0始まりのためmonth - 1を忘れると翌月のカレンダーが出てしまいます。
祝日に対応したカレンダーを作る
祝日を色分けできると、カレンダーは一気に実用的になります。
日本の祝日は、日付が決まっている固定祝日、第N月曜日になるハッピーマンデー、年ごとに日付が変わる春分の日・秋分の日、さらに振替休日と国民の休日の組み合わせでできています。
祝日データを用意する3つの方法
Javaの標準ライブラリには日本の祝日を返す機能がありません。祝日データは自分で用意します。
ここではまず、自前の計算で全祝日を判定するコードを作ります。そのあとで内閣府CSVを読み込む方法も紹介します。
全祝日を計算するサンプルコード
固定祝日とハッピーマンデーに加え、春分の日・秋分の日、振替休日、国民の休日まで計算します。祝日と日曜日は赤色で表示し、カレンダーの下にその月の祝日名を並べます。
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class HolidayCalendar {
public static void main(String[] args) {
printCalendar(2026, 9);
}
/** 祝日を赤色にしたカレンダーと、その月の祝日一覧を表示する */
public static void printCalendar(int year, int month) {
Map<LocalDate, String> holidays = getHolidays(year);
LocalDate firstDay = LocalDate.of(year, month, 1);
int offset = firstDay.getDayOfWeek().getValue() % 7;
System.out.println(year + "年" + month + "月のカレンダー");
System.out.println(" 日 月 火 水 木 金 土");
for (int i = 0; i < offset; i++) {
System.out.print(" ");
}
for (int day = 1; day <= firstDay.lengthOfMonth(); day++) {
LocalDate date = firstDay.withDayOfMonth(day);
if (holidays.containsKey(date) || date.getDayOfWeek() == DayOfWeek.SUNDAY) {
System.out.printf("\u001B[31m%3d\u001B[0m", day); // 祝日・日曜は赤色
} else {
System.out.printf("%3d", day);
}
if ((day + offset) % 7 == 0) {
System.out.println();
}
}
System.out.println();
// その月の祝日を一覧表示(TreeMapなので日付順に並ぶ)
for (Map.Entry<LocalDate, String> e : holidays.entrySet()) {
if (e.getKey().getMonthValue() == month) {
System.out.println(e.getKey().getDayOfMonth() + "日: " + e.getValue());
}
}
}
/** 指定した年の祝日・休日を日付順のMapで返す(2020・2021年の五輪特例には非対応) */
public static Map<LocalDate, String> getHolidays(int year) {
Map<LocalDate, String> h = new TreeMap<>();
// 固定祝日
h.put(LocalDate.of(year, 1, 1), "元日");
h.put(LocalDate.of(year, 2, 11), "建国記念の日");
h.put(LocalDate.of(year, 2, 23), "天皇誕生日");
h.put(LocalDate.of(year, 4, 29), "昭和の日");
h.put(LocalDate.of(year, 5, 3), "憲法記念日");
h.put(LocalDate.of(year, 5, 4), "みどりの日");
h.put(LocalDate.of(year, 5, 5), "こどもの日");
h.put(LocalDate.of(year, 8, 11), "山の日");
h.put(LocalDate.of(year, 11, 3), "文化の日");
h.put(LocalDate.of(year, 11, 23), "勤労感謝の日");
// ハッピーマンデー(第N月曜日)
h.put(nthMonday(year, 1, 2), "成人の日");
h.put(nthMonday(year, 7, 3), "海の日");
h.put(nthMonday(year, 9, 3), "敬老の日");
h.put(nthMonday(year, 10, 2), "スポーツの日");
// 春分の日・秋分の日(1980〜2099年に使える近似式)
h.put(LocalDate.of(year, 3, equinoxDay(year, 20.8431)), "春分の日");
h.put(LocalDate.of(year, 9, equinoxDay(year, 23.2488)), "秋分の日");
addSubstituteHolidays(h);
addCitizensHolidays(h);
return h;
}
/** 指定月の第N月曜日を返す */
static LocalDate nthMonday(int year, int month, int n) {
return LocalDate.of(year, month, 1)
.with(TemporalAdjusters.dayOfWeekInMonth(n, DayOfWeek.MONDAY));
}
/** 春分・秋分の日付を近似式で求める(基準値: 春分20.8431 / 秋分23.2488) */
static int equinoxDay(int year, double base) {
return (int) (base + 0.242194 * (year - 1980) - (year - 1980) / 4);
}
/** 振替休日: 祝日が日曜なら、その後の最も近い「祝日でない日」を休日にする */
static void addSubstituteHolidays(Map<LocalDate, String> h) {
for (LocalDate date : new ArrayList<>(h.keySet())) { // 走査中の追加を避けるためコピー
if (date.getDayOfWeek() == DayOfWeek.SUNDAY) {
LocalDate next = date.plusDays(1);
while (h.containsKey(next)) {
next = next.plusDays(1);
}
h.put(next, "振替休日");
}
}
}
/** 国民の休日: 前日と翌日が祝日の平日を休日にする(例: 2026年9月22日) */
static void addCitizensHolidays(Map<LocalDate, String> h) {
List<LocalDate> found = new ArrayList<>();
for (LocalDate date : h.keySet()) {
LocalDate mid = date.plusDays(1);
if (!h.containsKey(mid) && h.containsKey(mid.plusDays(1))
&& mid.getDayOfWeek() != DayOfWeek.SUNDAY) {
found.add(mid);
}
}
for (LocalDate d : found) {
h.put(d, "国民の休日");
}
}
}
2026年9月を表示した実際の出力です(ブログ上では色を再現できないため、ターミナルで赤色になる数字を太字にしています)。
2026年9月のカレンダー
日 月 火 水 木 金 土
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
21日: 敬老の日
22日: 国民の休日
23日: 秋分の日
2026年9月は、敬老の日(21日)と秋分の日(23日)にはさまれた22日が「国民の休日」になり、19日の土曜日から23日まで5連休になります(日曜の20日から数えると4連休です)。
このコードの計算結果は、内閣府の祝日CSVの2022〜2027年分と照合し、全日付が一致することを確認しています。
祝日カレンダーのコード解説
getHolidays()は、指定した年の祝日をMap<LocalDate, String>で返すメソッドです。TreeMapを使っているので、祝日一覧が日付順に並びます。
- 固定祝日: 元日・天皇誕生日・山の日など10日は
LocalDate.of()でそのまま登録します。 - ハッピーマンデー: 成人の日・海の日・敬老の日・スポーツの日は
TemporalAdjusters.dayOfWeekInMonth(n, DayOfWeek.MONDAY)で「第n月曜日」を求めます。 - 春分の日・秋分の日: 天文計算で決まるため、1980〜2099年の範囲で使える近似式で日付を出します。
- 振替休日: 祝日が日曜日なら、その後で最も近い「祝日でない日」を休日にします。
- 国民の休日: 前日と翌日がどちらも祝日の日を休日にします。
振替休日の処理では、keySet()をArrayListにコピーしてから走査しています。元のMapを直接回しながらput()するとConcurrentModificationExceptionが発生するためです。
赤色表示には\u001B[31mというANSIエスケープシーケンス(ターミナルで文字に色を付ける特殊コード)を使い、\u001B[0mで元の色に戻します。Windowsのコマンドプロンプトなど色に対応しない環境では、記号がそのまま表示されることがあります。
計算方式の注意点
- 春分の日・秋分の日は、国立天文台の計算をもとに前年2月1日の官報で正式に決まります。近似式は将来の日付を保証しません
- 2020年・2021年は東京オリンピック・パラリンピックに合わせて海の日・スポーツの日・山の日が移動したため、このコードの結果とは一致しません
- 天皇誕生日を2月23日としているため、2019年以前の年には使えません
内閣府の祝日CSVを読み込む方法
正確さを優先するなら、内閣府が公開している「国民の祝日」についてのページからCSVを取得する方法が確実です。
2026年9月時点のCSV(syukujitsu.csv)には1955年から2027年までの祝日・休日が収録されています。内閣府は更新時期を「随時」としか示していませんが、現行のCSVは2026年2月2日に更新されたもので、2028年分も春分・秋分の日が官報で公表される2027年2月以降に掲載されると見込まれます。
中身は「2026/9/21,敬老の日」のような2列の形式で、文字コードはShift_JISです。振替休日と国民の休日は、名称が「休日」と記載されています。
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class CsvHolidayLoader {
public static void main(String[] args) throws IOException {
Map<LocalDate, String> holidays = load("syukujitsu.csv");
System.out.println("読み込んだ件数: " + holidays.size());
// 2026年9月の祝日を表示
for (Map.Entry<LocalDate, String> e : holidays.entrySet()) {
LocalDate d = e.getKey();
if (d.getYear() == 2026 && d.getMonthValue() == 9) {
System.out.println(d + " " + e.getValue());
}
}
}
/** 内閣府の祝日CSV(Shift_JIS)を読み込み、日付→祝日名のMapを返す */
public static Map<LocalDate, String> load(String path) throws IOException {
// 日付は「2026/9/21」のようにゼロ埋めなしなので y/M/d で解釈する
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("y/M/d");
List<String> lines = Files.readAllLines(Paths.get(path), Charset.forName("Shift_JIS"));
Map<LocalDate, String> holidays = new TreeMap<>();
for (String line : lines.subList(1, lines.size())) { // 1行目は見出しなので飛ばす
String[] cols = line.split(",");
if (cols.length < 2) {
continue;
}
holidays.put(LocalDate.parse(cols[0].trim(), fmt), cols[1].trim());
}
return holidays;
}
}
syukujitsu.csvをCsvHolidayLoader.javaと同じフォルダに置いて実行した結果です。
読み込んだ件数: 1067
2026-09-21 敬老の日
2026-09-22 休日
2026-09-23 秋分の日
文字コードを指定しないと正しく読み込めません。このコードのFiles.readAllLines(path)は既定でUTF-8として読むため、Shift_JISのCSVではMalformedInputExceptionが発生します(FileReaderなど既定の文字コードで読む方法では文字化けになります)。Charset.forName("Shift_JIS")の指定を忘れないでください。
読み込んだMapは、前のサンプルのgetHolidays()の代わりにそのまま使えます。
よくある質問
Q1. CalendarとLocalDateどちらを使うべき?
A1. 新規コードではLocalDateがおすすめです。月が1始まりでバグを起こしにくく、イミュータブルでスレッドセーフという利点があります。Calendarはレガシーな既存コードとの互換性が必要な場合のみ使うのが現実的です。
Q2. 月初めの曜日はどうやって取得しますか?
A2. LocalDate.of(year, month, 1)で月初めの日付を作り、getDayOfWeek()を呼び出します。getValue()は月曜=1〜日曜=7を返すため、日曜始まりのカレンダーでは% 7で0〜6に変換します。
Q3. 祝日はJavaでどう管理すればよいですか?
A3. 学習用なら、固定祝日をMap<LocalDate, String>に登録し、ハッピーマンデーや春分・秋分の日を計算で求める方法で十分です。業務システムでは、内閣府が公開している祝日CSV(Shift_JIS)を毎年取り込む方式が確実です。
Q4. LocalDateはMapのキーに使えますか?
A4. はい、安全に使えます。LocalDateは値ベースでequals・hashCodeが実装されているため、同じ年月日を表すインスタンスはMapのキーとして正しく一致します。可変な古いDateクラスとは対照的です。
Q5. 振替休日はどうやって実装しますか?
A5. 祝日法のルールは「祝日が日曜日に当たるときは、その後で最も近い祝日でない日を休日とする」です。祝日Mapを走査し、日曜日の祝日があれば、祝日でない日まで1日ずつ後ろにずらして振替休日を登録します。
Q6. 月曜始まりのカレンダーにするには?
A6. 空白の数をgetDayOfWeek().getValue() - 1に変え、見出しを「月 火 水 木 金 土 日」に並べ替えます。改行の判定式(day + offset) % 7 == 0はそのままで、日曜日の後ろで改行されます。
Q7. Swingなど画面(GUI)にカレンダーを表示できますか?
A7. できます。1日の曜日と月の日数を求める部分はこの記事のコードをそのまま使い、System.outで出力している部分をJTableやGridLayoutに並べたJLabelへの表示に置き換えます。
まとめ:Javaでカレンダーを作る方法
Javaのカレンダー表示は、「1日の曜日」と「月の日数」を取得できれば、for文1つで作れます。
【要点まとめ】
- 新規コードは
LocalDate、課題で指定があればCalendarクラスを使う - 1日の曜日は
getDayOfWeek().getValue() % 7で日曜始まりの列番号にする - 月の日数は
lengthOfMonth()で取得でき、うるう年も自動で判定される (day + offset) % 7 == 0で土曜日の後ろに改行を入れるCalendarクラスは月が0始まりなのでmonth - 1を忘れない- 祝日は固定祝日・ハッピーマンデー・春分/秋分・振替休日・国民の休日の5種類で判定する
- 正確さが必要なら内閣府の祝日CSV(Shift_JIS)を読み込む
カレンダーが動いたら、次はテトリスや数当てゲームなど、ループと条件分岐をもっと使うプログラムに挑戦してみてください。
| REV. | DATE | 内容 |
|---|---|---|
| 5 | 2026-10-15 | 図解を描き直し(図面のデザインにそろえた) |
| 4 | 2026-10-04 | 記載内容の事実確認と修正 |
| 3 | 2026-09-02 | 2026年9月時点の情報で全体を見直し(古い記述を3件更新) |
| 2 | 2026-04-30 | 2026年4月時点の情報で全体を見直し |
| 1 | 2025-04-14 | 初版 |







