◀ 14.

【Java】日付の加算、減算

▶
この記事の要点
  • Java 8 以降は LocalDate / LocalDateTime の plusXxx / minusXxx で加算・減算するのが基本
  • 例: LocalDate.now().plusDays(1)(明日)、minusMonths(2)(2 か月前)
  • 古いコードでは Calendar#add(Calendar.DATE, -1) を使う。Date との変換が必要
  • 文字列との変換は新 API なら DateTimeFormatter、旧 API なら SimpleDateFormat
  • 月末の加算は日付が丸められる(1/31 の 1 か月後は 2 月末)。日数の差は ChronoUnit.DAYS.between

結論: Java 8 以降なら LocalDate を使う

本稿は Java の日付を加算、減算する方法について記載します。Java 8 で導入された java.time パッケージの LocalDate(日付)・LocalDateTime(日時)を使うと、1 行で書けます。

import java.time.LocalDate;

LocalDate today = LocalDate.now();
LocalDate tomorrow  = today.plusDays(1);    // 1 日後
LocalDate yesterday = today.minusDays(1);   // 1 日前
LocalDate nextWeek  = today.plusWeeks(1);   // 1 週間後
LocalDate twoMonthsAgo = today.minusMonths(2); // 2 か月前
LocalDate nextYear  = today.plusYears(1);   // 1 年後

LocalDate は不変(イミュータブル)なので、plusDays は元のオブジェクトを変えずに新しいオブジェクトを返します。戻り値を受け取らないと何も変わらない点に注意してください。

主なメソッド一覧

単位加算減算対応クラス
年plusYears(n)minusYears(n)LocalDate / LocalDateTime
月plusMonths(n)minusMonths(n)LocalDate / LocalDateTime
週plusWeeks(n)minusWeeks(n)LocalDate / LocalDateTime
日plusDays(n)minusDays(n)LocalDate / LocalDateTime
時・分・秒plusHours(n) などminusMinutes(n) などLocalDateTime / LocalTime
任意の単位plus(n, ChronoUnit.DAYS)minus(Period.ofMonths(1))共通

負の値を渡すこともできます(plusDays(-1) は minusDays(1) と同じ)。

文字列の日付を加算・減算する(新 API)

「20180101」のような文字列を受け取り、計算して文字列に戻す場合は DateTimeFormatter を使います。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate date = LocalDate.parse("20180101", fmt);
String strDate = date.minusDays(1).format(fmt);   // "20171231"

"yyyy-MM-dd" 形式(ISO 形式)ならフォーマッタ無しで LocalDate.parse("2018-01-01") と書けます。DateTimeFormatter はスレッドセーフなので、定数として使い回せます。

従来の方法: Calendar クラス(Java 7 以前のコード)

既存システムでは Date と Calendar を使った書き方もよく見かけます。処理の流れは次の通りです。

  1. SimpleDateFormat クラスで解析する日付の形式を指定する(例: yyyy/MM/dd)
  2. 文字列の日付を解析して Date クラスに変換する
  3. 日付計算は Calendar クラスで行うので、Calendar に変換する
  4. add メソッドで加算・減算する(負の値で減算)
  5. SimpleDateFormat で計算後の日付を文字列に変換する
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

...

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date date = sdf.parse("20180101");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DATE, -1);
String strDate = sdf.format(calendar.getTime());   // "20171231"

sdf.parse は検査例外 ParseException を投げるため、try-catch か throws 宣言が必要です。Calendar.DATE と Calendar.DAY_OF_MONTH は同じ意味で、月は Calendar.MONTH、年は Calendar.YEAR を指定します。

比較項目Calendar / Date(旧)LocalDate(新)
可変性可変(add で自分自身が変わる)不変(新しいオブジェクトを返す)
スレッドセーフSimpleDateFormat は非スレッドセーフDateTimeFormatter はスレッドセーフ
月の番号0 始まり(1 月が 0)1 始まり
コード量多い少ない

旧 API と新 API は date.toInstant() や Date.from(...) で相互変換できるので、既存コードを少しずつ置き換えられます。

落とし穴

  • 月末の丸め: LocalDate.of(2026, 1, 31).plusMonths(1) は 2026-02-28 になる。月末日を求めたいなら with(TemporalAdjusters.lastDayOfMonth()) を使う
  • SimpleDateFormat の寛容モード: 既定では「20181332」のような存在しない日付も繰り越して解釈してしまう。sdf.setLenient(false) で厳密にする
  • SimpleDateFormat を static で共有: 複数スレッドから同時に使うと結果が壊れる。メソッド内で生成するか DateTimeFormatter に移行する
  • パターンの大文字小文字: MM は月、mm は分。yyyyMMdd を yyyymmdd と書くと誤った結果になる
  • 時差を含む計算: 夏時間のある地域で時刻をまたぐ計算をするなら ZonedDateTime を使う

確認方法: 日数の差を求める

計算結果の検証や「何日後か」を求めるには ChronoUnit が便利です。

import java.time.temporal.ChronoUnit;

long days = ChronoUnit.DAYS.between(
        LocalDate.of(2026, 1, 1), LocalDate.of(2026, 3, 1));   // 59

関連

Post Share
子ページ

子ページはありません

同階層のページ
  1. プラットホーム
  2. 環境構築
  3. 文法
  4. API
  5. Servlet(サーブレット)
  6. JSP
  7. Applet(アプレット)
  8. デザインパターン
  9. フレームワーク
  10. ライブラリ
  11. Androidアプリケーション
  12. Project Jigsaw
  13. エラー一覧
  14. 日付の加算、減算
  15. 文字列の数字チェック
  16. 改行コードの削除
  17. 先頭と末端の文字の削除
  18. warファイルの中身を確認する方法
  19. nullもしくは空文字の判定
  20. beanの中身を確認する方法
  21. org.apache.log4j.Logger のログ出力で printStackTrace() のエラー内容を出力する方法
  22. Javaのバージョン確認方法