Home > Enterprise >  DateTime perhaps overwritten while during nested for loop
DateTime perhaps overwritten while during nested for loop

Time:01-19

I hope someone can help with this, i think perhaps the issue is i am overwriting the DateTime value in my second for loop, because its not outputting correct values, but not entirely sure.

<?php

$begin_from = new DateTime( "2023-01-01" );
$end_from   = new DateTime( "2023-12-31" );

$begin_to = new DateTime( "2023-01-31" );
$end_to   = new DateTime( "2023-12-31" );

for($i = $begin_from; $i <= $end_from; $i->modify(' 1 month')){
    for($k = $begin_to; $k <= $end_to; $k->modify('first day of')->modify(' 1 month')->modify('last day of')){
    echo $i->format("Y-m-d"),'..',$k->format("Y-m-d");
    echo "\n";
    }
}

From the code above its outputting this:

2023-01-01..2023-01-31
2023-01-01..2023-02-28
2023-01-01..2023-03-31
2023-01-01..2023-04-30
2023-01-01..2023-05-31
2023-01-01..2023-06-30
2023-01-01..2023-07-31
2023-01-01..2023-08-31
2023-01-01..2023-09-30
2023-01-01..2023-10-31
2023-01-01..2023-11-30
2023-01-01..2023-12-31

But, if you run these for loops separately you will get the correct values like below.

2023-01-01..2023-01-31
2023-02-01..2023-02-28
2023-03-01..2023-03-31
2023-04-01..2023-04-30
2023-05-01..2023-05-31
2023-06-01..2023-06-30
2023-07-01..2023-07-31
2023-08-01..2023-08-31
2023-09-01..2023-09-30
2023-10-01..2023-10-31
2023-11-01..2023-11-30
2023-12-01..2023-12-31

Can anyone tell what i am doing wrong here?

CodePudding user response:

I'm not sure why your code produces that output, but it can be simplified using a while loop. You just need one date to manually modify within each loop, and one target date (DateTimeImmutable is recommended for datetimes that are meant to remain unchanged)

$i_date = new DateTime( "2023-01-01" );
$end_date = new DateTimeImmutable( "2023-12-31" );


while($i_date <= $end_date){
    // echo first of month..
    echo $i_date->format("Y-m-d..");
    // goto last of month
    $i_date->modify('last day of');
    // echo last of month \n
    echo $i_date->format("Y-m-d") . "\n";

    // goto first of next month for the next iteration
    $i_date->modify('first day of')->modify(' 1 month');
}

The above will produce the following in PHP v7 :

2023-01-01..2023-01-31
2023-02-01..2023-02-28
2023-03-01..2023-03-31
2023-04-01..2023-04-30
2023-05-01..2023-05-31
2023-06-01..2023-06-30
2023-07-01..2023-07-31
2023-08-01..2023-08-31
2023-09-01..2023-09-30
2023-10-01..2023-10-31
2023-11-01..2023-11-30
2023-12-01..2023-12-31

PHP v5.6.40 requires you set the timezone or the default timezone like date_default_timezone_set('UTC'); before creating the DateTimes, but then you get the same output as above.

Run it live here.

CodePudding user response:

If your main goal is to output the start and end of each month within a given date range, inclusive of the end month, you can simplify this down to:

$from = new DateTime("2023-01-01");
$to   = new DateTime("2023-12-31");

while ($from <= $to) {
    echo $from->format("Y-m-01") . ".." . $from->format("Y-m-t") . "\n";
    $from->add(new DateInterval("P1M"));
}

If you need to respect the $to date, that is; if you need last date range to end at the $to date, you can adjust the above code slightly using the min() function:

$from = new DateTime("2023-01-01");
$to   = new DateTime("2023-12-31");

while ($from <= $to) {
    echo $from->format("Y-m-01") . ".." . min($to->format("Y-m-d"), $from->format("Y-m-t")) . "\n";
    $from->add(new DateInterval("P1M"));
}

CodePudding user response:

To use datetime objects in a loop like @ArleighHix's answer, I'd only modify the start date and use a calls of min() and max() in conjunction with 01 and t to ensure that date boundaries are not exceeded.

max() and min() are safe in this case because Y-m-d is a "big-endian" date format -- this means that the string can be evaluated as a simple string.

Code: (Demo)

$start_date = new DateTime("2023-01-03");
$end_date = new DateTime("2023-12-15");

while ($start_date <= $end_date) {
    printf(
        "%s..%s\n",
        max($start_date->format("Y-m-01"), $start_date->format("Y-m-d")),
        min($end_date->format("Y-m-d"), $start_date->format("Y-m-t"))
    );
    $start_date->modify(' 1 month first day of');
}

If your actual project requirement is to have non-full date ranges each month, then a refactored approach would be necessary. Please provide a more challenging example by editing your question if this is a real concern/possibility.

  • Related