php date_diff 2021-12-01 而不是 UTC 时区的错误

php date_diff bug with 2021-12-01 and not UTC timezone

制作diff,然后将其应用于相同的日期,结果我们希望看到相同的日期,但结果可能无法预测。

已在 php 8.0.16、7.4.7

上测试
<?php
date_default_timezone_set('Europe/Amsterdam');

$dateTimeNow = new DateTimeImmutable('2022-03-18 00:00:00.000000');
$dateTimeOld =   new DateTimeImmutable('2021-12-01 00:00:00.000000');
$timeInterval = $dateTimeOld->diff($dateTimeNow);
var_dump($dateTimeNow); //output 2022-03-18
var_dump($dateTimeOld->add($timeInterval)); //output 2022-03-16 but must be 2022-03-18!

$dateTimeNow = new DateTimeImmutable('2022-03-01 00:00:00.000000');
$dateTimeOld =   new DateTimeImmutable('2021-12-01 00:00:00.000000');
$timeInterval = $dateTimeOld->diff($dateTimeNow);
var_dump($dateTimeNow); //output 2022-03-01
var_dump($dateTimeOld->add($timeInterval)); //output 2022-03-02 but must be 2022-03-01!

date_default_timezone_set('UTC');

$dateTimeNow = new DateTimeImmutable('2022-03-18 00:00:00.000000');
$dateTimeOld =   new DateTimeImmutable('2021-12-01 00:00:00.000000');
$timeInterval = $dateTimeOld->diff($dateTimeNow);
var_dump($dateTimeNow); //output 2022-03-18
var_dump($dateTimeOld->add($timeInterval)); //output 2022-03-18 correct!

$dateTimeNow = new DateTimeImmutable('2022-03-01 00:00:00.000000');
$dateTimeOld =   new DateTimeImmutable('2021-12-01 00:00:00.000000');
$timeInterval = $dateTimeOld->diff($dateTimeNow);
var_dump($dateTimeNow); //output 2022-03-01
var_dump($dateTimeOld->add($timeInterval)); //output 2022-03-01 correct!

注意。php 8.1.3 上没有错误

如何解释,我应该用什么来确保它有效和稳定

所以我将您的代码重新排列为:

$tzs = [
    new DateTimezone('Europe/Amsterdam'),
    new DateTimezone('UTC')
];

$base = '2021-12-01 00:00:00.000000';

$dates = [
    '2022-03-18 00:00:00.000000',
    '2022-03-01 00:00:00.000000',
    
];

foreach( $tzs as $tz ) {
    $basedate = new DateTimeImmutable($base, $tz);
    foreach( $dates as $date ) {
        $curdate = new DateTimeImmutable($date, $tz);
        $diff = $basedate->diff($curdate);
        $test = $basedate->add($diff);
        
        printf("%s %s %5s/%4s %s %s\n",
            $basedate->format('c'),
            $curdate->format('c'),
            $diff->format('%mm%dd'),
            $diff->format('%ad'),
            $test->format('c'),
            ($curdate == $test) ? '1' : '0'
        );
    }
}

在php>=8.1中输出:

2021-12-01T00:00:00+01:00 2022-03-18T00:00:00+01:00 3m17d/107d 2022-03-18T00:00:00+01:00 1
2021-12-01T00:00:00+01:00 2022-03-01T00:00:00+01:00  3m0d/ 90d 2022-03-01T00:00:00+01:00 1
2021-12-01T00:00:00+00:00 2022-03-18T00:00:00+00:00 3m17d/107d 2022-03-18T00:00:00+00:00 1
2021-12-01T00:00:00+00:00 2022-03-01T00:00:00+00:00  3m0d/ 90d 2022-03-01T00:00:00+00:00 1

并在 php <8.1:

2021-12-01T00:00:00+01:00 2022-03-18T00:00:00+01:00 3m15d/107d 2022-03-16T00:00:00+01:00 0
2021-12-01T00:00:00+01:00 2022-03-01T00:00:00+01:00 2m29d/ 90d 2022-03-02T00:00:00+01:00 0
2021-12-01T00:00:00+00:00 2022-03-18T00:00:00+00:00 3m17d/107d 2022-03-18T00:00:00+00:00 1
2021-12-01T00:00:00+00:00 2022-03-01T00:00:00+00:00  3m0d/ 90d 2022-03-01T00:00:00+00:00 1

DateTime->diff()DateInterval 如何定义 'a month' 似乎有所不同。我敢打赌,这种差异与时区数据有关 and/or DST 转换,因此 UTC 和阿姆斯特丹 TZ 之间存在差异。

我的 go-to 解决方案始终是“始终以 UTC 格式存储和计算日期,所有其他时区仅供人类观察”。

但如果这不是一个可行的问题,那么对于这种特定情况,你没有处理比几天更细粒度的事情,你可能会逃脱与:

$diff = $old->diff($new);
$diff = new DateInterval($diff->format('P%aD'));

应该去掉含糊不清的“月份和剩余天数”,只留下“总天数”,这会在所有当前 PHP 版本中给出一致的结果。但是,如果在您的实际代码中有一个时间组件,那么您将不得不欺骗该格式调用以生成正确的间隔规范。

a laundry list of Date bugfixes in the 8.1 release,但我无法告诉您哪一个适用。