Javaプログラミングにおいて、日付や時刻の操作は避けて通れない重要な要素です。

長らくJavaの標準ライブラリとして日付計算を支えてきたのがjava.util.Calendarクラスですが、Java 8で導入されたjava.timeパッケージの登場により、その役割は大きく変わりました。

現在、新規開発ではモダンなAPIの使用が推奨されていますが、既存システムの保守やレガシーコードの改修においては、依然としてCalendarクラスの知識が必要不可欠です。

本記事では、Calendarクラスの基本操作から、現代的なjava.timeへの移行方法までを詳しく解説します。

Calendarクラスとは

java.util.Calendarクラスは、特定の時点と、YEAR、MONTH、DAY_OF_MONTH、HOURといったカレンダー・フィールド間の変換を行うための抽象クラスです。

1997年にリリースされたJDK 1.1で導入され、それまでのjava.util.Dateクラスが持っていた「日付計算」の機能を肩代わりする形で普及しました。

Calendarクラスの最大の特徴は、ロケールやタイムゾーンに基づいた日付操作が可能である点にあります。

しかし、設計が古いために、スレッドセーフではないことや、月の指定が「0から始まる」といった直感的でない仕様を含んでおり、利用には注意が必要です。

Calendarクラスの基本的な使い方

Calendarクラスは抽象クラスであるため、new演算子を使って直接インスタンス化することはできません。

通常は、実行環境のタイムゾーンとロケールに基づいた適切なサブクラスを取得する静的メソッドを使用します。

インスタンスの生成

最も一般的な方法は、getInstance()メソッドを使用することです。

これにより、デフォルトのタイムゾーンとロケールを持つCalendarオブジェクトが生成されます。

Java
import java.util.Calendar;

public class Main {
    public static void main(String[] args) {
        // 現在の日時でCalendarインスタンスを取得
        Calendar calendar = Calendar.getInstance();
        
        // インスタンスの中身を表示(toStringの結果は非常に詳細)
        System.out.println(calendar.getTime());
    }
}
実行結果
Thu Mar 05 15:30:00 JST 2026

日時情報の取得

Calendarインスタンスから特定の項目(年、月、日、時、分など)を取り出すには、get()メソッドを使用します。

引数には、Calendarクラスで定義されている定数を指定します。

ここで最も注意すべき点は、「月」の値が0から11で表されることです。

1月を取得すると0が返り、12月を取得すると11が返ります。

Java
import java.util.Calendar;

public class CalendarGetSample {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();

        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1; // 0から始まるため1を足す
        int date = cal.get(Calendar.DATE);
        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // 1(日)〜7(土)

        System.out.println("年: " + year);
        System.out.println("月: " + month);
        System.out.println("日: " + date);
        System.out.println("曜日(数値): " + dayOfWeek);
    }
}

実行結果(実行日時によって異なります):

実行結果
年: 2026
月: 3
日: 5
曜日(数値): 5

日時情報の設定と更新

特定の日時を設定したい場合は、set()メソッドを使用します。

年・月・日をまとめて設定する方法や、特定のフィールドだけを個別に設定する方法があります。

Java
import java.util.Calendar;

public class CalendarSetSample {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();

        // 2026年12月25日に設定
        // 月は11を指定(12月を意味する)
        cal.set(2026, 11, 25);
        System.out.println("設定した日付: " + cal.getTime());

        // 特定のフィールドのみ変更(時間を10時に設定)
        cal.set(Calendar.HOUR_OF_DAY, 10);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        
        System.out.println("時間を変更後: " + cal.getTime());
    }
}
実行結果
設定した日付: Fri Dec 25 15:30:00 JST 2026
時間を変更後: Fri Dec 25 10:00:00 JST 2026

Calendarクラスでの計算と比較

Calendarクラスを使用する主なメリットの一つは、複雑な日付計算をメソッド一つで行える点です。

例えば、「翌月末」や「3ヶ月前」といった計算が容易に行えます。

日時の加算・減算

日時の計算にはadd()メソッドを使用します。

第一引数に操作したいフィールドを、第二引数に増減させたい値を指定します。

値をマイナスにすれば減算になります。

Java
import java.util.Calendar;

public class CalendarAddSample {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.println("現在の日時: " + cal.getTime());

        // 10日後を加算
        cal.add(Calendar.DATE, 10);
        System.out.println("10日後: " + cal.getTime());

        // 2ヶ月前を計算
        cal.add(Calendar.MONTH, -2);
        System.out.println("2ヶ月前: " + cal.getTime());
        
        // 繰り上がりも自動で行われる
        cal.set(2026, 0, 31); // 2026年1月31日
        cal.add(Calendar.DATE, 1);
        System.out.println("1月31日の1日後: " + cal.getTime());
    }
}
実行結果
現在の日時: Thu Mar 05 15:30:00 JST 2026
10日後: Sun Mar 15 15:30:00 JST 2026
2ヶ月前: Thu Jan 15 15:30:00 JST 2026
1月31日の1日後: Sun Feb 01 15:30:00 JST 2026

なお、roll()というメソッドも存在しますが、これは上位のフィールドを変化させずに計算する特殊なメソッドです。

例えば、12月31日に「日のroll」で+1しても、月は1月のままになるため、通常の業務ロジックではadd()を使用するのが一般的です。

日時の比較

2つの日付を比較する場合、before()after()equals()メソッドを使用します。

Java
import java.util.Calendar;

public class CalendarCompareSample {
    public static void main(String[] args) {
        Calendar cal1 = Calendar.getInstance();
        Calendar cal2 = Calendar.getInstance();

        cal1.set(2026, 0, 1); // 2026年1月1日
        cal2.set(2026, 0, 2); // 2026年1月2日

        System.out.println("cal1はcal2より前か: " + cal1.before(cal2));
        System.out.println("cal1はcal2より後か: " + cal1.after(cal2));
        
        // compareToを使用する場合
        // 戻り値が負なら以前、0なら同時、正なら以後
        int result = cal1.compareTo(cal2);
        System.out.println("compareToの結果: " + result);
    }
}
実行結果
cal1はcal2より前か: true
cal1はcal2より後か: false
compareToの結果: -1

CalendarクラスとSimpleDateFormatの連携

Calendarクラス単体では、日付を「2026/03/05」のような特定のフォーマットで文字列出力する機能が貧弱です。

そのため、java.text.SimpleDateFormatと組み合わせて使用するのが従来の定番でした。

Java
import java.util.Calendar;
import java.text.SimpleDateFormat;

public class FormatSample {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        
        // フォーマットパターンの指定
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        
        // CalendarからDateオブジェクトを取得してフォーマット
        String formattedDate = sdf.format(cal.getTime());
        
        System.out.println("フォーマット済み日時: " + formattedDate);
    }
}
実行結果
フォーマット済み日時: 2026/03/05 15:30:00

ただし、SimpleDateFormatスレッドセーフではないため、マルチスレッド環境(Webアプリケーションなど)で共有インスタンスとして扱うと、意図しない日付が表示されるなどの重大なバグの原因となります。

Calendarクラスが抱える課題と注意点

長年使われてきたCalendarクラスですが、現代のJava開発においては多くの課題が指摘されています。

これらを理解しておくことは、なぜ新しいAPIへの移行が必要なのかを理解する助けになります。

ミュータブル(可変)である

Calendarオブジェクトは、メソッドを呼び出すと自分自身の状態を書き換えてしまいます。

これにより、意図せず日付が変更されてしまうバグが発生しやすく、並列処理においては致命的な問題となります。

不自然なインデックス

前述の通り、月が0から始まる仕様や、曜日の数値が日曜日から1で始まる仕様はミスを誘発します。

一貫性の欠如

Dateクラス、Calendarクラス、SimpleDateFormatクラスが別々のパッケージに存在し、相互の連携が複雑です。

不適切な名前付け

例えば、getTime()というメソッドは時刻を返すのではなく、java.util.Dateオブジェクトを返します。

これらの課題を解決するために、Java 8から導入されたのがjava.timeパッケージ(Date and Time API)です。

最新のjava.timeパッケージへの移行方法

現代のJava開発におけるデファクトスタンダードは、java.timeパッケージです。

JSR-310として開発されたこのAPIは、イミュータブル(不変)でスレッドセーフな設計となっており、メソッドチェーンによる直感的な操作が可能です。

java.timeパッケージの基本

主要なクラスは以下の通りです。

クラス名用途
LocalDate日付のみ(2026-03-05)
LocalTime時刻のみ(15:30:00)
LocalDateTime日付と時刻(2026-03-05T15:30:00)
ZonedDateTimeタイムゾーン付きの日時
DateTimeFormatterフォーマット(スレッドセーフ)

主要クラスの対応表

Calendarクラスで行っていた操作が、java.timeではどのように変わるかを比較します。

操作内容Calendarクラスjava.time (LocalDateTime等)
現在日時の取得Calendar.getInstance()LocalDateTime.now()
特定日時の設定cal.set(2026, 2, 5)LocalDateTime.of(2026, 3, 5, ...)
年の取得cal.get(Calendar.YEAR)dt.getYear()
月の取得cal.get(Calendar.MONTH) + 1dt.getMonthValue()
日の加算cal.add(Calendar.DATE, 1)dt.plusDays(1)

具体的な書き換え例

次に、具体的なコードで移行のメリットを確認しましょう。

Calendarクラスの場合(従来)

Java
Calendar cal = Calendar.getInstance();
cal.set(2026, Calendar.MARCH, 5); // 月の指定が定数を使わないと分かりにくい
cal.add(Calendar.MONTH, 1);
cal.add(Calendar.DATE, 7);
Date result = cal.getTime();

java.timeの場合(推奨)

Java
import java.time.LocalDate;

public class ModernDateSample {
    public static void main(String[] args) {
        // 直感的に2026年3月5日を指定できる
        LocalDate date = LocalDate.of(2026, 3, 5);
        
        // メソッドチェーンで記述可能(元のdateオブジェクトは変更されない)
        LocalDate result = date.plusMonths(1).plusDays(7);
        
        System.out.println("計算前: " + date);
        System.out.println("計算後: " + result);
    }
}
実行結果
計算前: 2026-03-05
計算後: 2026-04-12

このように、java.timeを使用するとコードの可読性が大幅に向上し、不変性が保証されるため安全なプログラミングが可能になります。

Calendarとjava.timeの相互変換

既存のライブラリやフレームワークがjava.util.DateCalendarを要求する場合、新しいAPIとの間で変換を行う必要があります。

CalendarからZonedDateTimeへの変換

Java 8以降、CalendarクラスにはtoInstant()メソッドが追加されており、これを介して変換が可能です。

Java
import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;

public class ConversionSample {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        
        // Calendar -> ZonedDateTime
        ZonedDateTime zdt = ZonedDateTime.ofInstant(
            cal.toInstant(), 
            cal.getTimeZone().toZoneId()
        );
        
        System.out.println("ZonedDateTime: " + zdt);
    }
}

ZonedDateTimeからCalendarへの変換

逆に、新しいAPIからCalendarに戻すには、GregorianCalendar.from()を使用するのが最も簡単です。

Java
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class ReverseConversionSample {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now();
        
        // ZonedDateTime -> Calendar
        Calendar cal = GregorianCalendar.from(zdt);
        
        System.out.println("Calendarの日時: " + cal.getTime());
    }
}

まとめ

本記事では、JavaのCalendarクラスの基本的な使い方から、実務での注意点、そしてモダンなjava.timeへの移行方法について解説しました。

Calendarクラスは、Javaの歴史を支えてきた重要なAPIですが、その設計には現代のプログラミング基準で見ると課題が多く含まれています。

「月の指定が0から始まる」「スレッドセーフではない」といった特徴を正しく理解しておくことは、保守開発においてバグを防ぐために極めて重要です。

一方で、新規開発においてはCalendarクラスの使用は避け、java.timeパッケージ(LocalDate, LocalDateTime等)を積極的に採用すべきです。

これにより、より堅牢で、読みやすく、メンテナンス性の高いコードを実現できます。

もし現在Calendarクラスを多用しているプロジェクトに関わっているのであれば、今回紹介した変換方法を活用しながら、段階的に新しいAPIへとシフトしていくことを検討してみてください。

日付操作の正確性は、システムの信頼性に直結する要素です。

正しい知識を身につけ、適切なツールを選択していきましょう。