Java Tips | 日付・時間:日付減算

Java Java
スポンサーリンク

日付減算の本質は「過去にさかのぼるカレンダー計算を任せること」

日付減算は、「今日から 7 日前」「3 ヶ月前」「1 年前の同じ日」といった
“カレンダー上で過去にさかのぼる”計算です。

ここでも一番やってはいけないのは、
year--day -= 7 のように、自分で年・月・日をいじることです。
月の長さ、うるう年、月末などを全部自前で面倒見るのはほぼ無理ゲーです。

正解は、minusXxx(または plusXxx にマイナス値)を素直に使うこと。
「過去方向のカレンダー計算も Java に任せる」という発想を持つのがスタートラインです。


LocalDate の基本的な日付減算

日数を引く:minusDays

「N 日前」は minusDays で書きます。

import java.time.LocalDate;

public class MinusDaysBasic {

    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2025, 3, 26);
        LocalDate before7 = today.minusDays(7);

        System.out.println("today   : " + today);    // 2025-03-26
        System.out.println("before7 : " + before7);  // 2025-03-19
    }
}
Java

ここで押さえてほしいポイントは二つです。

一つ目は、「月またぎ・年またぎも勝手にやってくれる」こと。
3 月 1 日から 1 日引けば 2 月 28 日(うるう年なら 2 月 29 日)になります。

二つ目は、「元の値は変わらない(不変)」こと。
today はそのまま、before7 だけが新しい日付です。
“うっかり上書き”系のバグを防げます。

月を引く:minusMonths と「月末問題(逆方向)」

import java.time.LocalDate;

public class MinusMonthsBasic {

    public static void main(String[] args) {
        LocalDate d1 = LocalDate.of(2025, 3, 15);
        LocalDate d2 = d1.minusMonths(1);

        System.out.println(d1); // 2025-03-15
        System.out.println(d2); // 2025-02-15
    }
}
Java

ここで重要なのが、減算でも「存在しない日付は末日に丸められる」というルールです。

LocalDate endMar = LocalDate.of(2025, 3, 31);
LocalDate minus1 = endMar.minusMonths(1);

System.out.println(minus1); // 2025-02-28
Java

3 月 31 日から 1 ヶ月引くと、2 月 31 日は存在しないので
「その月で可能な最大の日(末日)」に寄せられます。

年に対しても同じです。

LocalDate leap = LocalDate.of(2024, 2, 29);
LocalDate prevYear = leap.minusYears(1);

System.out.println(prevYear); // 2023-02-28
Java

「minusYears / minusMonths でも、存在しない日付は末日に丸められる」
この挙動を知っておくと、結果に戸惑わなくなります。


Period を使った「年+月+日」の減算

Period をマイナス方向に使う

Period は「何年何ヶ月何日」という“期間”を表すクラスです。
加算だけでなく、減算にもそのまま使えます。

import java.time.LocalDate;
import java.time.Period;

public class PeriodMinusExample {

    public static void main(String[] args) {
        LocalDate base = LocalDate.of(2025, 3, 26);

        Period period = Period.of(1, 2, 10); // 1年2ヶ月10日
        LocalDate result = base.minus(period);

        System.out.println("base   : " + base);    // 2025-03-26
        System.out.println("before : " + result);  // 2024-01-16
    }
}
Java

業務的には、

「契約開始日の 1 年 2 ヶ月 10 日前から受付開始」
「満了日の 1 ヶ月前に通知」

のようなルールを Period で表現しておき、
target.minus(period) で「いつから/いつまで」を計算するイメージです。


業務シナリオで考える日付減算

例1:支払期限から 7 日前にリマインド

「支払期限の 7 日前にメールを送る」というよくある要件です。

import java.time.LocalDate;

public class ReminderExample {

    public static void main(String[] args) {
        LocalDate dueDate = LocalDate.of(2025, 4, 25);
        LocalDate remindDate = dueDate.minusDays(7);

        System.out.println("支払期限 : " + dueDate);    // 2025-04-25
        System.out.println("通知日   : " + remindDate); // 2025-04-18
    }
}
Java

ここで大事なのは、
「“基準日”を決めて、そこから minusDays / minusMonths でさかのぼる」
というパターンを体に染み込ませることです。

例2:契約満了日の 1 ヶ月前に通知

import java.time.LocalDate;

public class ContractNoticeExample {

    public static void main(String[] args) {
        LocalDate expire = LocalDate.of(2025, 3, 31);
        LocalDate notice = expire.minusMonths(1);

        System.out.println("満了日 : " + expire); // 2025-03-31
        System.out.println("通知日 : " + notice); // 2025-02-28
    }
}
Java

ここでも月末問題が出ます。
3 月 31 日の 1 ヶ月前は 2 月 28 日(うるう年なら 2 月 29 日)になります。

「必ず月末に通知したい」という仕様ならこれで OK。
「必ず 1 ヶ月前の同じ日(31 日前)」という意味なら、
minusDays(31) のように日数で扱うべきです。

「1 ヶ月前」と「30 日前/31 日前」は違う
という感覚を、減算でもしっかり意識してください。


LocalDateTime / ZonedDateTime での日付減算

LocalDateTime の minusXxx

LocalDateTime でも、日付部分の減算は同じ感覚で書けます。

import java.time.LocalDateTime;

public class LocalDateTimeMinusExample {

    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2025, 3, 26, 9, 0);
        LocalDateTime before = dt.minusDays(3);

        System.out.println(dt);      // 2025-03-26T09:00
        System.out.println(before);  // 2025-03-23T09:00
    }
}
Java

日付が動くときに、時刻も一緒に動く——それだけです。

ZonedDateTime の minusXxx(タイムゾーンも考慮)

ZonedDateTime では、タイムゾーンやサマータイムも含めて
“現地時間で N 日前”を計算してくれます。

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ZonedDateTimeMinusExample {

    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.of(
                2025, 3, 26,
                9, 0, 0, 0,
                ZoneId.of("Asia/Tokyo")
        );

        ZonedDateTime before = zdt.minusDays(1);

        System.out.println(zdt);
        System.out.println(before);
    }
}
Java

サマータイムのある地域では、
「1 日前」が 24 時間前とは限らないこともありますが、
ZonedDateTime はそのあたりも含めて正しく処理してくれます。


minusXxx と plusXxx(負の値) の関係

minusDays(7) と plusDays(-7) は同じ

実は、次の二つは同じ意味です。

date.minusDays(7);
date.plusDays(-7);
Java

ただし、読みやすさの観点からは minusDays(7) を使う方が圧倒的におすすめです。
「7 日前」と書いてある方が、コードを読む人の脳に優しいからです。


まとめ:日付減算で絶対に覚えてほしいこと

日付減算は、「過去方向のカレンダー計算」を安全に書くための技術です。

自前で year--day -= 30 を書かない。
LocalDate / LocalDateTime / ZonedDateTimeminusDays / minusMonths / minusYears を使う。
存在しない日付は、加算と同じく「その月の末日に丸められる」。
「1 ヶ月前」と「30 日前/31 日前」は違う——ビジネスルールに合わせて選ぶ。
複合期間は Period を使い、minus(period) でさかのぼる。

あなたのコードのどこかに、
「締切の 7 日前を day - 7 で計算している」ような箇所があれば、
そこを一度 minusDaysminusMonths に置き換えられないか眺めてみてください。

その小さな一歩が、
“時間とカレンダーに強いエンジニア”への、確かな前進になります。

タイトルとURLをコピーしました