月一覧生成とは何をするユーティリティか
「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());
}
}
JavaChronoUnit.MONTHS.between(start, end) で「何か月差か」を求め、Stream.iterate で plusMonths(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月」などの文字列として表示されます。
フォーマットがバラバラだと、人間にも機械にも扱いづらくなります。
YearMonth と DateTimeFormatter を組み合わせて、
「月表示はこのフォーマットで統一する」という小さなユーティリティを用意しておくと、
システム全体の一貫性と可読性がぐっと上がります。
まとめ:月一覧生成で身につけてほしい感覚
月一覧生成は、「開始年月から終了年月までを、月単位で並べる」だけのシンプルな処理です。
でも、その中に大事なポイントがいくつもあります。
月単位のデータには YearMonth を使う。plusMonths(1) / minusMonths(1) で月を進めたり戻したりする。
開始・終了の関係をチェックし、終了が開始より前なら例外にする。
年単位・直近◯か月など、よくある業務パターンをユーティリティ化しておく。
範囲が長すぎないように上限を決め、誤設定や負荷増大を防ぐ。
もしあなたのコードのどこかで、
「for (int m = 1; m <= 12; m++) { … }」のように“なんとなく月を回している”箇所があれば、
そこを一度、YearMonth ベースの月一覧生成ユーティリティに置き換えられないか眺めてみてください。
それだけで、コードの意図がはっきりし、
年月を扱う処理がぐっと“業務システムらしい”安定したものになります。
