Java Tips | 日付・時間:月一覧生成

Java Java
スポンサーリンク

月一覧生成とは何をするユーティリティか

「2023年1月〜2025年12月までの月を全部出したい」「指定期間の月ごとのレポートを作りたい」「画面のプルダウンに“年月”を並べたい」。
こういうときに使うのが「月一覧生成」のユーティリティです。

日付範囲生成が「日単位」で並べるのに対して、月一覧生成は「月単位」で並べます。
業務では、月次レポート、請求処理、締め処理、年月選択UIなど、かなり多くの場面で登場します。


月を表す基本クラス YearMonth を押さえる

なぜ LocalDate ではなく YearMonth を使うのか

「月一覧」を扱うときに、LocalDate で「その月の1日」を使う方法もありますが、
Java にはそもそも「年+月」だけを表す YearMonth というクラスがあります。

import java.time.YearMonth;

public class YearMonthBasic {

    public static void main(String[] args) {
        YearMonth ym = YearMonth.of(2025, 3);
        System.out.println(ym);          // 2025-03
        System.out.println(ym.getYear()); // 2025
        System.out.println(ym.getMonthValue()); // 3
    }
}
Java

「日付は要らない、月単位で扱いたい」というときは、
YearMonth を使うとコードの意図がとても分かりやすくなります。


一番基本的な「月一覧生成」ユーティリティ

開始年月から終了年月までを1か月ずつ並べる

まずは、開始年月と終了年月を渡すと、その間の YearMonth を全部返すユーティリティです。

import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;

public class MonthRange {

    public static List<YearMonth> monthsBetween(YearMonth startInclusive, YearMonth endInclusive) {
        if (endInclusive.isBefore(startInclusive)) {
            throw new IllegalArgumentException("終了年月は開始年月以降である必要があります");
        }

        List<YearMonth> result = new ArrayList<>();
        YearMonth ym = startInclusive;
        while (!ym.isAfter(endInclusive)) {
            result.add(ym);
            ym = ym.plusMonths(1);
        }
        return result;
    }
}
Java

使い方の例です。

import java.time.YearMonth;
import java.util.List;

public class MonthRangeExample {

    public static void main(String[] args) {
        YearMonth start = YearMonth.of(2023, 1);
        YearMonth end   = YearMonth.of(2023, 6);

        List<YearMonth> months = MonthRange.monthsBetween(start, end);

        for (YearMonth ym : months) {
            System.out.println(ym);
        }
    }
}
Java

出力はこうなります。

2023-01
2023-02
2023-03
2023-04
2023-05
2023-06

ここで重要なのは、「開始も終了も含む」「plusMonths(1) で1か月ずつ進める」「終了が開始より前なら例外にする」という3点です。
このルールをユーティリティ側で固定しておくと、呼び出し側で迷わなくなります。


Stream を使った月一覧生成

Stream.iterate でスマートに書く

Java 8 以降なら、Stream を使って同じことをもう少し宣言的に書けます。

import java.time.YearMonth;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MonthRangeStream {

    public static List<YearMonth> monthsBetween(YearMonth startInclusive, YearMonth endInclusive) {
        if (endInclusive.isBefore(startInclusive)) {
            throw new IllegalArgumentException("終了年月は開始年月以降である必要があります");
        }

        long months = java.time.temporal.ChronoUnit.MONTHS.between(startInclusive, endInclusive) + 1;

        return Stream.iterate(startInclusive, ym -> ym.plusMonths(1))
                     .limit(months)
                     .collect(Collectors.toList());
    }
}
Java

ChronoUnit.MONTHS.between(start, end) で「何か月差か」を求め、
Stream.iterateplusMonths(1) しながら limit する、という流れです。

ループ版とやっていることは同じですが、
「月の列を生成している」という意図がよりはっきり見えます。


よくある業務パターン別の月一覧生成

ある年の全ての月を一覧にする

「2025年の月次レポートを全部出したい」「年度の月一覧を作りたい」といったときのパターンです。

import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;

public class YearMonths {

    public static List<YearMonth> monthsOfYear(int year) {
        List<YearMonth> result = new ArrayList<>();
        for (int month = 1; month <= 12; month++) {
            result.add(YearMonth.of(year, month));
        }
        return result;
    }
}
Java

使い方の例です。

List<YearMonth> months = YearMonths.monthsOfYear(2025);
months.forEach(System.out::println);
Java

これで「その年の1〜12月」がきれいに並びます。
年度(4月〜翌年3月)にしたい場合は、開始年月と終了年月を変えて monthsBetween を使えばOKです。

直近◯か月分の一覧を作る

「直近12か月の売上グラフ」「直近6か月のログ集計」など、よくある要件です。

import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RecentMonths {

    public static List<YearMonth> lastMonths(int count) {
        if (count <= 0) {
            throw new IllegalArgumentException("count は 1以上で指定してください");
        }

        YearMonth now = YearMonth.now();
        List<YearMonth> result = new ArrayList<>();
        YearMonth ym = now;
        for (int i = 0; i < count; i++) {
            result.add(ym);
            ym = ym.minusMonths(1);
        }
        Collections.reverse(result);
        return result;
    }
}
Java

使い方の例です。

List<YearMonth> last12 = RecentMonths.lastMonths(12);
last12.forEach(System.out::println);
Java

ここで深掘りしたいのは、「過去に向かって minusMonths し、最後に reverse する」というパターンです。
こうすると、「古い順」に並んだ直近◯か月を簡単に作れます。


月一覧と LocalDate / 期間処理のつなぎ方

YearMonth からその月の開始日・終了日を取る

月一覧だけでなく、「その月の全日付」「その月の期間」を扱いたいことも多いです。
YearMonth からは簡単に「1日」と「最終日」が取れます。

import java.time.LocalDate;
import java.time.YearMonth;

public class YearMonthToDates {

    public static LocalDate firstDay(YearMonth ym) {
        return ym.atDay(1);
    }

    public static LocalDate lastDay(YearMonth ym) {
        return ym.atEndOfMonth();
    }
}
Java

例えば、「月次レポートの期間」を LocalDate で持ちたい場合は、

YearMonth ym = YearMonth.of(2025, 3);
LocalDate from = YearMonthToDates.firstDay(ym);
LocalDate to   = YearMonthToDates.lastDay(ym);
Java

のようにして、「2025-03-01〜2025-03-31」を作れます。
日付範囲生成ユーティリティと組み合わせれば、「その月の全日付を列挙する」ことも簡単です。


セキュリティ・運用の観点から見た月一覧生成

範囲が“長すぎないか”を必ず意識する

月一覧生成も、日付範囲と同じく「範囲が広すぎると危険」です。
例えば「2000-01〜2100-12」までの月一覧を作ると、1200か月以上になります。

月単位なので日単位ほど極端ではありませんが、
それでも「ユーザー入力から自由に開始・終了を受け取る」場合は、
最大範囲を決めておくのが安全です。

例えば「最大でも120か月(10年)まで」「それ以上はエラーにする」など、
ユーティリティ側で制限をかけておくと、誤設定やDoS的な利用を防ぎやすくなります。

表示用フォーマットはユーティリティで統一する

月一覧は、画面やログで「YYYY/MM」「YYYY年MM月」などの文字列として表示されます。
フォーマットがバラバラだと、人間にも機械にも扱いづらくなります。

YearMonthDateTimeFormatter を組み合わせて、
「月表示はこのフォーマットで統一する」という小さなユーティリティを用意しておくと、
システム全体の一貫性と可読性がぐっと上がります。


まとめ:月一覧生成で身につけてほしい感覚

月一覧生成は、「開始年月から終了年月までを、月単位で並べる」だけのシンプルな処理です。
でも、その中に大事なポイントがいくつもあります。

月単位のデータには YearMonth を使う。
plusMonths(1) / minusMonths(1) で月を進めたり戻したりする。
開始・終了の関係をチェックし、終了が開始より前なら例外にする。
年単位・直近◯か月など、よくある業務パターンをユーティリティ化しておく。
範囲が長すぎないように上限を決め、誤設定や負荷増大を防ぐ。

もしあなたのコードのどこかで、
「for (int m = 1; m <= 12; m++) { … }」のように“なんとなく月を回している”箇所があれば、
そこを一度、YearMonth ベースの月一覧生成ユーティリティに置き換えられないか眺めてみてください。

それだけで、コードの意図がはっきりし、
年月を扱う処理がぐっと“業務システムらしい”安定したものになります。

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