ページ

2013/01/19

[PHP]DateTimeオブジェクトをforeach文で回せるDatePeriodクラス

[競技プログラミング][PHP][AtCoder]超大型連休 | DevAchieveDateTimeオブジェクトを
for 文で日付を回そうと思ったのだけど、以下のようにとんでもないことになってしまった。
<?php
for(
    $date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo')),
    $end = DateTime::createFromFormat('Y/m/d', '2012/12/31', new DateTimeZone('Asia/Tokyo'));
    $date->diff($end)->format('%R') === '+';
    $date->add(new DateInterval('P1D'))
){
    // 2012/01/01 から 2012/12/31 まで1日毎に日付を回すfot文
}
これはいくらなんでも微妙すぎだと思っていたんだけど、その後DatePeriodクラスというものを知った。
期間を設定してやれば foreach 文で日付を回せるらしい。百聞は一見に如かず。
<?php
$start = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo'));
$end = clone $start; // clone でオブジェクトを複製
$end->add(new DateInterval('P1Y'));
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
foreach($period as $date){
    // 2012/01/01 から 2012/12/31 まで1日毎に日付を回すfoteach文
}
とってもスッキリして読みやすくなりました。
しかし地味にバグがあって、終了日の時刻が"00:00:00"だと終了日がイテレーションに含まれないらしいです。
バグレポートはされていてオプションで対応するみたいですが、まだ使えないみたいです。
参考: DatePeriod::INCLUDE_END_DATE - ゆっくり*ゆっくり

そのため上記の例では1日毎に日付を回すので+1の 2013/01/01 ということで 1年足しました。
さりげなく DateTime::createFromFormat では作らずに clone でオブジェクトを複製しました。
PHP: オブジェクトのクローン作成 - Manual
細かい説明は抜きに下のコードをhttp://codepad.viper-7.com/で試してみてください。
<?php
$date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo'));
$copy = $date;
$copy->add(new DateInterval('P1Y'));
echo $date->format('Y/m/d'); // 2013/01/01
echo $copy->format('Y/m/d'); // 2013/01/01

$date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo'));
$copy = clone $date;
$copy->add(new DateInterval('P1Y'));
echo $date->format('Y/m/d'); // 2012/01/01
echo $copy->format('Y/m/d'); // 2013/01/01
clone を使わないと $copy の変更で $date も変更されてしまっていたでしょう?
つまりそういうことです。

ということで、ちょっと脱線してしまったけど DatePeriod クラスの使い方はこれでおしまい。
綺麗にforeach 文が書けてチョーイイネ!サイコー!