php 在嵌套的for循环期间,DateTime可能被覆盖

bgtovc5b  于 2023-01-19  发布在  PHP
关注(0)|答案(4)|浏览(134)

我希望有人能帮上忙,我想问题可能是我在第二个for循环中覆盖了DateTime值,因为它没有输出正确的值,但不完全确定。

<?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";
    }
}

从上面的代码中输出:

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

但是,如果您单独运行这些for循环,您将得到如下所示的正确值。

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

有谁知道我哪里做错了吗?

jq6vz3qz

jq6vz3qz1#

我不知道为什么你的代码会产生这样的输出,但是可以使用while循环来简化它,你只需要在每个循环中手动修改一个日期,以及一个目标日期(对于不想改变的日期时间,建议使用DateTimeImmutable

$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');
}

以上代码在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要求您在创建DateTime之前设置时区或默认时区(如date_default_timezone_set('UTC');),但您会得到与上面相同的输出。
Run it live here.

qmb5sa22

qmb5sa222#

要在@ArleighHix的答案这样的循环中使用datetime对象,我只需修改开始日期,并结合01t使用min()max()的调用,以确保不超出日期边界。
max()min()在这种情况下是安全的,因为Y-m-d是一种“big-endian”日期格式--这意味着可以将字符串作为简单字符串进行计算。
代码:(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');
}

如果您的实际项目要求是每个月都有非完整的日期范围,那么重构方法将是必要的。如果这是一个真实的的问题/可能性,请编辑您的问题,提供一个更具挑战性的示例。

3z6pesqy

3z6pesqy3#

如果您的主要目标是输出给定日期范围内每个月的开始和结束日期(包括结束月份),则可以将此简化为:

$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"));
}

如果您需要遵守$from$to日期,即;如果您需要第一个日期范围从$from开始,最后一个日期范围从$to结束,您可以使用max()min()函数稍微调整上面的代码:

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

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

dohp0rv54#

可变对象真的很难推理,特别是,你需要知道$bar = $foo使$bar指向与$foo相同的对象,而不是指向具有相同值的新对象。
记住这一点,让我们“展开”循环(never write code like this!):

$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" );

$i = $begin_from;
outerforloop:
if ( $i <= $end_from ) {

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

    $i->modify('+1 month');
    goto outerforloop;
}

特别是,查看内部循环的开始和结束:

  • $k = $begin_to;将使$k指向与$begin_to相同的对象
  • 然后$k->modify(...)将修改该对象,这意味着$k * 和 * $begin_to都向前移动了一个月
  • 下一次循环时,我们检查$k <= $end_to,得到预期的结果
  • 然而,当我们回到 outer 循环时,我们再次运行$k = $begin_to;,期望它 * 重置值 *;但是$k$begin_to已经指向同一对象,该对象已经被修改;该赋值语句不执行任何操作
  • 所以现在当我们检查$k <= $end_to时,它已经是false了:我们根本就不会进入这个循环

要实际复制对象的 value,可以使用clone keyword,例如$k = clone $begin_to;
但是,这种特殊情况就是创建the DateTimeImmutable class的原因。使用DateTimeImmutable,您永远不会更改现有对象的值,而是始终将结果赋给某个位置。简而言之,将$i->modify(...)替换为$i = $i->modify(...),将$k->modify(...)替换为$k = $k->modify(...)

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

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

for($i = $begin_from; $i <= $end_from; $i = $i->modify('+1 month')){
    for($k = $begin_to; $k <= $end_to; $k = $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";
    }
}

这修复了for循环......但没有给予你想要的结果,因为如果你有两个嵌套循环,每个循环迭代12次,结果将是144次迭代--想象一下用12列12行填充一个网格。
实际上,我们需要的是一个 single 循环,它控制开始和结束日期,有几种方法可以编写它,但与现有代码最相似的可能是保留$i的循环,然后在此基础上定义$k

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

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

相关问题