1. 日付と時刻の加算・減算
プログラミングにおける日付と時刻の扱いは、単に「日付をきれいに画面に表示する」ことだけではありません。次のようなことがよく求められます。
- 重要なイベントまであと何日あるかを知る;
- ユーザーの年齢を計算する;
- 2つのイベントの間にどれだけ時間が経過したかを求める;
- 締め切りが来ていないかを確認する;
- 日付に特定の日数・週数・月数・時間数を加える。
たとえば、アプリで「締め切りまであと3日です」とユーザーに通知したい場合や、(日付が過去なら)誕生日おめでとうメッセージを送りたい場合があります。
そのために java.time には、こうした計算を簡単かつ安全に行える専用のクラスとメソッドが用意されています。
加算・減算の基本メソッド
日付と時刻の各クラス(LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant)には次のメソッドがあります。
- plusXxx() — 追加(⽇、⽉、年、時、分など)
- minusXxx() — 減算(同様)
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // 今日の日付
LocalDate tomorrow = today.plusDays(1); // 明日
LocalDate nextMonth = today.plusMonths(1); // 1か月後
LocalDate lastWeek = today.minusWeeks(1); // 1週間前
System.out.println("今日: " + today);
System.out.println("明日: " + tomorrow);
System.out.println("1か月後: " + nextMonth);
System.out.println("1週間前: " + lastWeek);
これらのメソッドはすべて元のオブジェクトを変更せず、新しいオブジェクトを返します。不変(イミュータブル)— これが大切です!
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime inTwoHours = now.plusHours(2);
LocalTime tenMinutesAgo = now.minusMinutes(10);
System.out.println("今: " + now);
System.out.println("2時間後: " + inTwoHours);
System.out.println("10分前: " + tenMinutesAgo);
メソッドの組み合わせ
LocalDate vacation = today.plusMonths(2).plusDays(10);
System.out.println("休暇: " + vacation);
2. 日付の差分取得: Period と Duration
2つの日付や時刻の間にどれだけ時間が経過したかを知りたいとき、2人のヒーロー — Period と Duration — の出番です。詳しく見ていきましょう。
Period と Duration の違いは?
- Period — 日付(年・月・日)向け。例: 「3年2か月5日」。
- Duration — 時刻(時・分・秒・ナノ秒)向け。例: 「5時間30分」。
| クラス | 用途 | 使用例 |
|---|---|---|
|
日付同士の差分 | 日付の間の年/月/日 |
|
瞬間(時刻)同士の差分 | イベント間の時間(時/分/秒) |
例: ユーザーの年齢を計算する
import java.time.LocalDate;
import java.time.Period;
LocalDate birthday = LocalDate.of(2000, 1, 15); // 誕生日
LocalDate today = LocalDate.now();
Period age = Period.between(birthday, today);
System.out.println("年齢: " + age.getYears() + " 年, " +
age.getMonths() + " か月, " +
age.getDays() + " 日");
たとえ話: Period は日付間の「人間的な」差分(「24年5か月12日」)のようなものです。
例: 締め切りまであとどれくらい?
import java.time.LocalDate;
LocalDate deadline = LocalDate.of(2025, 7, 1);
LocalDate today = LocalDate.now();
if (today.isBefore(deadline)) {
Period left = Period.between(today, deadline);
System.out.println("締め切りまで残り: " +
left.getMonths() + " か月と " +
left.getDays() + " 日");
} else {
System.out.println("締め切りはすでに過ぎました!");
}
Duration: 時間の差分
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println("所要時間: " + duration.toHours() + " 時間 " +
(duration.toMinutes() % 60) + " 分");
Duration は差分を秒・分・時・日などの正確な時間で測ります(ただし日数は常に24時間単位で、暦上の特殊事情は考慮しません)。
使い分け: Period と Duration のどちらを使う?
- 日付だけ(時刻なし)の場合 — Period を使う。
- 時刻がある場合(LocalDateTime や Instant など)— Duration を使う。
3. 日付と時刻の比較: isBefore, isAfter, equals
単に「その日付はもう来たのか、まだなのか」「その出来事は過去か未来か」を判断したいことがあります。これには日付・時刻の各クラスに次のメソッドがあります。
- isBefore(otherDate)
- isAfter(otherDate)
- isEqual(otherDate)(または equals)
import java.time.LocalDate;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
if (today.isBefore(deadline)) {
System.out.println("まだ時間があります!");
} else if (today.isEqual(deadline)) {
System.out.println("今日は締め切り日!");
} else {
System.out.println("締め切りはもう過ぎました :(");
}
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime lunch = LocalTime.of(13, 0);
if (now.isAfter(lunch)) {
System.out.println("昼食はもう済みました!");
} else {
System.out.println("昼食までまだ時間があります。");
}
重要: 同じ型のオブジェクト同士のみ比較できます(LocalDate と LocalDate、LocalDateTime と LocalDateTime など)。
4. 実践: 差分計算と期限チェックの課題
例: タスクの締め切りまであと何日?
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
long daysLeft = ChronoUnit.DAYS.between(today, deadline);
if (daysLeft > 0) {
System.out.println("締め切りまで残り " + daysLeft + " 日");
} else if (daysLeft == 0) {
System.out.println("今日は締め切り日!");
} else {
System.out.println("締め切りは " + Math.abs(daysLeft) + " 日前に過ぎました");
}
ChronoUnit.DAYS.between() は、2つの日付の間の正確な日数(年や月の区切りを考慮せず)を取得する便利な方法です。
例: 2つのイベントの間の秒数
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
long seconds = ChronoUnit.SECONDS.between(start, end);
System.out.println("イベント間の経過時間は " + seconds + " 秒");
例: タスクの経過年数・月数・日数
import java.time.LocalDate;
import java.time.Period;
LocalDate created = LocalDate.of(2025, 5, 20);
LocalDate today = LocalDate.now();
Period period = Period.between(created, today);
System.out.println("タスクの作成から " +
period.getDays() + " 日、" +
period.getMonths() + " か月、" +
period.getYears() + " 年が経過しました。");
例: 2つの Instant 間の所要時間
import java.time.Instant;
import java.time.Duration;
Instant start = Instant.now();
// ... 何か処理する ...
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("実行に要した時間: " + duration.toMillis() + " ミリ秒");
5. Period と Duration の使い方の注意点
Period の特徴
- Period は暦に基づいて差分を数えます。たとえば2月28日と3月1日の間は、月が違っていても1日です。
- 年・月・日を個別に取得できます: getYears()、getMonths()、getDays()。
- 日付間の総日数が必要なら、ChronoUnit.DAYS.between() を使いましょう。
Duration の特徴
- Duration は正確な時間(時・分・秒)で扱います。
- LocalTime、LocalDateTime、Instant と一緒に使えます。
- 時刻のない日付(LocalDate)には使えません — 例外になります。
例: Period と Duration の違い
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Duration;
import java.time.Period;
LocalDate date1 = LocalDate.of(2025, 2, 28);
LocalDate date2 = LocalDate.of(2025, 3, 1);
Period period = Period.between(date1, date2); // 2日(うるう年)
long days = java.time.temporal.ChronoUnit.DAYS.between(date1, date2); // 2日
System.out.println("Period: " + period.getDays() + " 日");
System.out.println("ChronoUnit: " + days + " 日");
LocalDateTime dt1 = LocalDateTime.of(2025, 6, 1, 23, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 6, 2, 1, 0);
Duration duration = Duration.between(dt1, dt2); // 2時間
System.out.println("Duration: " + duration.toHours() + " 時間");
6. Duration と Period を使って見やすい形式で差分を表示する
ユーザー向けに日付や時刻の差分を表示する場合、「123456秒」のような表示は見やすくありません。通常は「2日3時間15分」のように表現したいはずです。
例: 日付と時刻の差分の表示
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 14, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 3, 16, 30);
Duration duration = Duration.between(start, end);
long days = duration.toDays();
long hours = duration.minusDays(days).toHours();
long minutes = duration.minusDays(days).minusHours(hours).toMinutes();
System.out.println("差分: " + days + " 日 " +
hours + " 時間 " + minutes + " 分");
7. ZonedDateTime と Instant を扱う際の注意点
- 異なるゾーンの ZonedDateTime 同士の差分は、各ゾーンの「人間的な」時刻ではなく、絶対時間で計算されます。
- 夏時間/冬時間への切り替えは、日付間の時間差(時間数)に影響を与えることがあります。
例: ZonedDateTime の差分
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.Duration;
ZonedDateTime MinskTime = ZonedDateTime.of(2025, 6, 1, 12, 0, 0, 0, ZoneId.of("Europe/Minsk"));
ZonedDateTime nyTime = ZonedDateTime.of(2025, 6, 1, 5, 0, 0, 0, ZoneId.of("America/New_York"));
Duration diff = Duration.between(nyTime, MinskTime);
System.out.println("差: " + diff.toHours() + " 時間");
8. 日付・時刻計算でよくあるミス
誤り №1: Period と Duration を混同する。
Duration を LocalDate に対して使うと例外になります。日付には Period、時刻には Duration を使いましょう。
誤り №2: タイムゾーンを考慮しない。
異なるタイムゾーンの日時を比較すると、想定外の結果になることがあります。すべてを同一のタイムゾーンにそろえるか、Instant を使うのが望ましいです。
誤り №3: plus/minus がオブジェクトを変更すると思っている。
java.time のオブジェクトはすべて不変です! 結果を新しい変数に代入するのを忘れないでください(例: today = today.plusDays(1))。
誤り №4: Period の getDays() が総日数だと思っている。
実際には、年と月を引いた残りの日数に過ぎません。総日数は ChronoUnit.DAYS.between() を使って取得してください。
誤り №5: 異なる型のオブジェクトを比較する。
isBefore、isAfter、isEqual は同じ型のオブジェクト間でのみ機能します(例: LocalDate 同士)。
GO TO FULL VERSION