日付範囲生成とは何をするユーティリティか
「今月の全日付を一覧にしたい」「レポート期間の開始日〜終了日を1日ずつ処理したい」「1週間分のデータを日ごとに集計したい」。
こういうときに必要になるのが「日付範囲生成」です。
日付範囲生成ユーティリティは、
「開始日」と「終了日」を渡すと、その間の LocalDate を順番に並べてくれる小さな道具です。
業務では、集計・レポート・バッチ処理・カレンダー画面など、あらゆるところで使われます。
Java で日付範囲を扱う基本クラス
LocalDate を日付の“基本単位”として使う
日付範囲生成では、LocalDate を使うのが基本です。LocalDate は「年月日だけ(時間なし)」を表すクラスで、
「2025-03-26」のような“日付そのもの”を扱うのに向いています。
import java.time.LocalDate;
public class LocalDateBasic {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalDate specific = LocalDate.of(2025, 3, 26);
System.out.println("今日 : " + today);
System.out.println("特定の日付 : " + specific);
}
}
Java時間やタイムゾーンを気にせず、「日単位でループしたい」「日ごとに処理したい」というときは、
まず LocalDate を使う、と覚えておくと良いです。
一番基本的な日付範囲生成
開始日から終了日までを1日ずつ生成する
まずは、最もシンプルな「開始日〜終了日を1日ずつ列挙する」ユーティリティです。
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class DateRange {
public static List<LocalDate> daysBetween(LocalDate startInclusive, LocalDate endInclusive) {
if (endInclusive.isBefore(startInclusive)) {
throw new IllegalArgumentException("終了日は開始日以降である必要があります");
}
List<LocalDate> result = new ArrayList<>();
LocalDate d = startInclusive;
while (!d.isAfter(endInclusive)) {
result.add(d);
d = d.plusDays(1);
}
return result;
}
}
Java使い方の例です。
import java.time.LocalDate;
import java.util.List;
public class DateRangeExample {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2025, 3, 1);
LocalDate end = LocalDate.of(2025, 3, 5);
List<LocalDate> days = DateRange.daysBetween(start, end);
for (LocalDate d : days) {
System.out.println(d);
}
}
}
Java出力はこうなります。
2025-03-01
2025-03-02
2025-03-03
2025-03-04
2025-03-05
ここで重要なのは、
「開始日を含む」「終了日も含む」「1日ずつ plusDays(1) で進める」
という3点です。
この“含む/含まない”のルールをユーティリティ側で固定しておくと、
呼び出し側で迷わなくなります。
「開始日を含む/終了日を含む」を意識する
inclusive / exclusive の考え方
日付範囲には、よく次の2パターンがあります。
開始日を含み、終了日も含む(閉区間)
開始日を含み、終了日は含まない(半開区間)
例えば、レポート期間を「2025-03-01〜2025-03-31」と表示しつつ、
内部的には「2025-04-01 の手前まで」として扱う、という設計もよくあります。
半開区間のユーティリティはこう書けます。
public static List<LocalDate> daysBetweenHalfOpen(LocalDate startInclusive, LocalDate endExclusive) {
if (!endExclusive.isAfter(startInclusive)) {
throw new IllegalArgumentException("終了日は開始日より後である必要があります");
}
List<LocalDate> result = new ArrayList<>();
LocalDate d = startInclusive;
while (d.isBefore(endExclusive)) {
result.add(d);
d = d.plusDays(1);
}
return result;
}
Javaここで深掘りしたいのは、
「範囲の定義をユーティリティで統一する」ことの大切さです。
プロジェクト内で「このユーティリティは開始含む・終了含む」「こっちは終了含まない」とバラバラだと、
バグの温床になります。
「日付範囲は基本的に半開区間で扱う」など、チームでルールを決めておくと安全です。
ストリームを使った日付範囲生成
Stream API でスマートに書く
Java 8 以降なら、Stream を使って日付範囲を生成することもできます。
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DateRangeStream {
public static List<LocalDate> daysBetween(LocalDate startInclusive, LocalDate endInclusive) {
if (endInclusive.isBefore(startInclusive)) {
throw new IllegalArgumentException("終了日は開始日以降である必要があります");
}
long days = java.time.temporal.ChronoUnit.DAYS.between(startInclusive, endInclusive) + 1;
return Stream.iterate(startInclusive, d -> d.plusDays(1))
.limit(days)
.collect(Collectors.toList());
}
}
Javaここでのポイントは、ChronoUnit.DAYS.between(start, end) で「日数差」を求め、Stream.iterate で plusDays(1) しながら limit していることです。
ループ版とやっていることは同じですが、
「日付の列を生成している」という意図がよりはっきり見えます。
業務でよく使う「月」「週」の範囲生成
ある月の全日付を生成する
「今月のカレンダーを表示したい」「月次レポートで日ごとの集計をしたい」といったときに使うパターンです。
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class MonthRange {
public static List<LocalDate> daysOfMonth(int year, int month) {
LocalDate first = LocalDate.of(year, month, 1);
LocalDate last = first.withDayOfMonth(first.lengthOfMonth());
List<LocalDate> result = new ArrayList<>();
LocalDate d = first;
while (!d.isAfter(last)) {
result.add(d);
d = d.plusDays(1);
}
return result;
}
}
Java使い方の例です。
List<LocalDate> days = MonthRange.daysOfMonth(2025, 3);
days.forEach(System.out::println);
Javaここで深掘りしたいのは、lengthOfMonth() を使って「その月の最終日」を安全に求めている点です。
2月は28日だったり29日だったりしますが、lengthOfMonth() を使えば、うるう年も含めて正しく扱えます。
ある週の全日付を生成する
「週次レポート」「週単位のカレンダー」などで使うパターンです。
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
public class WeekRange {
public static List<LocalDate> weekOf(LocalDate anyDate, DayOfWeek firstDayOfWeek) {
LocalDate start = anyDate;
while (start.getDayOfWeek() != firstDayOfWeek) {
start = start.minusDays(1);
}
List<LocalDate> result = new ArrayList<>();
LocalDate d = start;
for (int i = 0; i < 7; i++) {
result.add(d);
d = d.plusDays(1);
}
return result;
}
}
Java使い方の例です。
List<LocalDate> week = WeekRange.weekOf(LocalDate.of(2025, 3, 26), DayOfWeek.MONDAY);
week.forEach(System.out::println);
Javaここでのポイントは、
「週の開始曜日(例えば月曜)」を引数で受け取っていることです。
国やシステムによって「週の始まり」が違うので、
ユーティリティ側で固定せず、DayOfWeek で指定できるようにしておくと柔軟です。
セキュリティ・運用の観点から見た日付範囲生成
範囲が“広すぎないか”を必ずチェックする
日付範囲生成は便利ですが、
「開始日:2000-01-01」「終了日:2100-12-31」のような指定をそのまま受け入れると、
36500日以上のリストを作ることになり、メモリや処理時間を食い尽くします。
実務では、例えば次のような制限をユーティリティに組み込むことが多いです。
最大でも 1年分(365日)まで
最大でも 3ヶ月分まで
それ以上は例外にする、あるいは警告ログを出す
こうした制限は、サービス妨害(DoS)的な使われ方や、誤設定による障害を防ぐうえで重要です。
タイムゾーンをまたぐ処理との境界を意識する
日付範囲生成自体は LocalDate で十分ですが、
「JST の日付範囲を UTC の日時に変換して処理する」
といったケースでは、ZonedDateTime や Instant との変換が絡みます。
例えば、「JST の 2025-03-01〜2025-03-31」を UTC の Instant 範囲に変換する場合は、
開始日 00:00 と終了日の翌日 00:00 を JST として ZonedDateTime にし、
それを Instant に変換して半開区間 [start, end) として扱う、などです。
日付範囲生成ユーティリティと、タイムゾーン変換ユーティリティをきちんと分けておくと、
境界のバグを減らせます。
まとめ:日付範囲生成で身につけてほしい感覚
日付範囲生成は、「開始日から終了日までを、日単位できれいに並べる」だけのシンプルな処理です。
でも、その中に大事なポイントがいくつもあります。
LocalDate を日付の基本単位として使う。
開始日・終了日を含むかどうか(閉区間/半開区間)をユーティリティで統一する。plusDays(1) で1日ずつ進める、あるいは Stream.iterate で生成する。
月・週の範囲生成では lengthOfMonth() や DayOfWeek を活用する。
範囲が広すぎないかをチェックし、誤設定やDoS的な利用を防ぐ。
もしあなたのコードのどこかで、
「for (int i = 0; i < 31; i++) { … }」のように“なんとなく日数を決め打ちしている”箇所があれば、
そこを一度、日付範囲生成ユーティリティに置き換えられないか眺めてみてください。
それだけで、コードの意図がはっきりし、
バグも減り、業務システムとしての信頼性が一段上がります。
