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

发布于 2025-01-15 05:42:59 字数 1600 浏览 0 评论 0原文

进行 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 上运行没有 bug

如何解释,以及应该做什么我用于此目的是为了确保它有效且稳定

Make diff and then apply it to the same date, in result we want to see the same date, but result can be unpredictable.

Tested on 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!

NOTE. On php 8.1.3 works without bug

How can it be explained, and what should i use for this purpose to be sure it is valid and stable

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

千紇 2025-01-22 05:42:59

因此,我将您的代码重新排列为:

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

Whichoutputs in 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

和 in 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 上有所不同代码> 定义“一个月”。我敢打赌,这种差异与时区数据和/或 DST 转换有关,因此 UTC 和阿姆斯特丹 TZ 之间存在差异。

我这里的首选解决方案始终是“始终以 UTC 存储和计算日期,所有其他时区都只是为了人类的眼球”。

但如果这不是一个可行的要求,那么对于这种特定情况,您不处理比几天更细化的事情,您可能可以逃脱:

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

这应该消除模糊的问题“月份和剩余天数”并仅保留“总天数”,这样可以在所有当前 PHP 版本中提供一致的结果。但是,如果在您的实际代码中存在时间组件,那么您将不得不欺骗该格式调用以生成正确的间隔规范。

8.1 版本中的日期错误修复清单,但我无法告诉您其中哪一个适用。

So I rearranged your code to:

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

Which outputs in 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

and in 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

Which appears to differ on how either DateTime->diff() or DateInterval define 'a month'. I would wager that that difference has something to do with the timezone data and/or DST transition, hence the difference between UTC and Amesterdam TZs.

My go-to solution here is always going to be "always store and compute dates in UTC, all other timezones are simply for human eyeballs".

But if that is not a feasible ask, then for this specific situation where you're not dealing with anything more granular than a number of days, you can probably get away with:

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

Which should strip out the nebulous "months and remainder days" and leave only the "total days", which gives consistent results across all current PHP versions. But if, in your actual code, there is a time component then you're going to have to finagle that format call to produce the correct interval spec.

There is a laundry list of Date bugfixes in the 8.1 release but I couldn't tell you which of these is applicable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文