DatePeriodで日付を一定間隔で刻む ラベル: PHP,DateTime,DatePeriod

DatePeriodは、開始日から終了日(または回数)まで、決まった間隔で日時を繰り返し取り出すためのクラスです。日次バッチや週次レポートの日付リストを作るときに使います。

「今日から30日分の日付がほしい」というだけなら、for文を回してDateIntervalを足していけば書けます。書けるんですが、境界条件(最後の日を含むか含まないか)を毎回自分で気にする羽目になって、地味に事故りやすいところです。DatePeriodはそこを型として持たせてくれるので、素直に使ったほうが後で楽だと思います。

DatePeriodはどう書くか

基本形は開始日・間隔(DateInterval)・回数(または終了日)の3つを渡すだけです。

$start = new DateTimeImmutable('2026-09-01');
$interval = new DateInterval('P1D'); // 1日おき
$recurrences = 6; // 開始日を含めて7回分になる

$period = new DatePeriod($start, $interval, $recurrences);

foreach ($period as $date) {
    echo $date->format('Y-m-d'), "\n";
}
// 2026-09-01 〜 2026-09-07 の7日分

回数を渡す版では、$recurrencesは「開始日の後に何回繰り返すか」であって「全部で何件出るか」ではありません。上の例だと6を渡していますが、開始日も1件としてカウントされるので、結果は7件になります。ここを勘違いして1件多い/少ないデータを作ってしまうのがよくあるハマりどころです。

終了日を渡すとなぜ1件少なく感じるのか

終了日を渡す版はもっと直感に反します。終了日そのものは基本的に含まれません。

$start = new DateTimeImmutable('2026-09-01');
$end   = new DateTimeImmutable('2026-09-08'); // 9/8は含まれない
$interval = new DateInterval('P1D');

$period = new DatePeriod($start, $interval, $end);

foreach ($period as $date) {
    echo $date->format('Y-m-d'), "\n";
}
// 2026-09-01 〜 2026-09-07 の7日分(09-08は出てこない)

「9/1から9/8まで」のつもりでこう書くと、9/8が出てこなくて「1日足りない」とハマります。これはバグではなく仕様で、ISO 8601の区間表現(半開区間)に合わせた挙動です。終了日も含めたいなら、PHP 8.2以降で追加されたDatePeriod::INCLUDE_END_DATEオプションを使います。

$period = new DatePeriod(
    $start,
    $interval,
    $end,
    DatePeriod::INCLUDE_END_DATE
);
// これで09-08まで8日分出る

逆に開始日を除きたい場合はDatePeriod::EXCLUDE_START_DATEを使います。オプションはビット演算子で組み合わせられるので、両方除きたければ`EXCLUDE_START_DATE`だけ渡し、両方含めたければ`INCLUDE_END_DATE`だけ渡す、という要領です(開始日は指定しない限りデフォルトで含まれます)。

間隔は日単位じゃなくてもいい

DateIntervalの指定を変えれば、週次・月次・時間単位でも同じ書き方で回せます。

// 毎週月曜起点で4週分
$period = new DatePeriod($start, new DateInterval('P1W'), 3);

// 毎月1日起点で12ヶ月分
$period = new DatePeriod($start, new DateInterval('P1M'), 11);

// 30分おきに3時間分
$period = new DatePeriod($start, new DateInterval('PT30M'), 5);

月単位のP1Mは要注意です。開始日が31日だと、2月など短い月では末日に丸められて、そのあとの月の「31日」が続かなくなることがあります。月次バッチで日付がずれる不具合は、大体これが原因です。月初(1日)を起点にするか、月末を扱う場合はDateInterval側ではなく計算後にmodifyで補正するほうが安全だと思います。

まとめ

DatePeriodは日付の連番を自前のfor文で作るより安全ですが、回数指定と終了日指定で「境界に何を含むか」の感覚が違う点だけは覚えておく必要があります。回数指定は開始日を含めた件数、終了日指定は終了日を含まない半開区間がデフォルトです。INCLUDE_END_DATEとEXCLUDE_START_DATEでその境界を明示的に制御できるので、レポートの集計期間のように「何日から何日まで」を厳密に扱いたいときは、思い込みで書かずにオプションで意図を書き残しておくのがいいと思います。

よくある質問

Q. DatePeriodのforeachで取れるのはDateTimeとDateTimeImmutableのどちらですか。
A. コンストラクタに渡したstartオブジェクトの型に従います。DateTimeImmutableを渡せばDateTimeImmutable、DateTimeを渡せばDateTimeが返ってきます。

Q. DatePeriodの結果を配列にまとめて件数を確認したいときは。
A. iterator_to_array($period)で配列化できます。件数だけ知りたい場合は$period->getRecurrences()でも取得できますが、これはオプションの影響を受けた最終的な件数なので、そのまま件数チェックに使えます。

Q. INCLUDE_END_DATEはPHPの古いバージョンでも使えますか。
A. PHP 8.2で追加された定数なので、それより前のバージョンでは使えません。古い環境では終了日を1つ先にずらして渡すか、ループの中で自分で終了日を含めるかの対応が必要です。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です