Почему метод DateTime diff ведет себя по-другому в PHP (связанный с часовым поясом)? - PullRequest
1 голос
/ 16 июня 2020

Я хочу понять, почему этой простой операции DateTime (2020-07-01)->diff(2020-07-31) нельзя доверять.

<?php

$timeZones = ['GMT', 'Europe/Paris', 'America/Denver', 'Asia/Qatar', 'Australia/Sydney'];

echo('Doing (2020-07-01)->diff(2020-07-31)' . "\n\n");

foreach ($timeZones as $timeZone) {

    date_default_timezone_set($timeZone);

    $startDate = new \DateTime('2020-07-01');

    $calcDateA = (new \DateTime('2020-07-01'))->modify('+1 month -1 day');
    $calcDateB = (new \DateTime('2020-07-01'))->modify('+30 days');
    $calcDateC = new \DateTime('2020-07-31');

    $diffA = $startDate->diff($calcDateA);
    $diffB = $startDate->diff($calcDateB);
    $diffC = $startDate->diff($calcDateC);

    echo("Using $timeZone\n");

    echo('days (A): ' . $diffA->d . "\n");
    echo('months (A): ' . $diffA->m . "\n");

    echo('days (B): ' . $diffB->d . "\n");
    echo('months (B): ' . $diffB->m . "\n");

    echo('days (C): ' . $diffC->d . "\n");
    echo('months (C): ' . $diffC->m . "\n\n");
}

Этот код, когда он выполняется в любой версии PHP, выводит следующее:

Doing (2020-07-01)->diff(2020-07-31)

Using GMT

days (A): 30
months (A): 0

days (B): 30
months (B): 0

days (C): 30
months (C): 0

Using Europe/Paris

days (A): 0
months (A): 1

days (B): 0
months (B): 1

days (C): 0
months (C): 1

Using America/Denver

days (A): 30
months (A): 0

days (B): 30
months (B): 0

days (C): 30
months (C): 0

Using Asia/Qatar

days (A): 0
months (A): 1

days (B): 0
months (B): 1

days (C): 0
months (C): 1

Using Australia/Sydney

days (A): 0
months (A): 1

days (B): 0
months (B): 1

days (C): 0
months (C): 1

У меня такое ощущение, что внутренние вычисления производятся с UT C и кем-то забыл компенсировать часовой пояс. Кто-нибудь может подтвердить?

Спасибо.

1 Ответ

2 голосов
/ 16 июня 2020

Краткий ответ: да, это ошибка.

Я упростил ваш тестовый пример, чтобы сделать его более очевидным:

$timeZones = ['GMT', 'Europe/Paris'];
foreach ($timeZones as $timeZone) {
    date_default_timezone_set($timeZone);
    $startDate = new \DateTime('2020-07-01');
    $calcDateA = (new \DateTime('2020-07-01'))->modify('+1 month -1 day');
    $diffA = $startDate->diff($calcDateA);
    echo("Using $timeZone\n");
    echo $startDate->format('r'), "\n";
    echo $calcDateA->format('r'), "\n";
    echo $diffA->format('Days: %d / Months: %m'), "\n\n";
}
Using GMT
Wed, 01 Jul 2020 00:00:00 +0000
Fri, 31 Jul 2020 00:00:00 +0000
Days: 30 / Months: 0

Using Europe/Paris
Wed, 01 Jul 2020 00:00:00 +0200
Fri, 31 Jul 2020 00:00:00 +0200
Days: 0 / Months: 1

Я предполагал, что, как вы предполагаю, вычисления выполняются в UT C:

$utc = new DateTimeZone('UTC');
foreach ($timeZones as $timeZone) {
    date_default_timezone_set($timeZone);
    $startDate = new \DateTime('2020-07-01');
    $startDate->setTimezone($utc);
    $calcDateA = (new \DateTime('2020-07-01'))->modify('+1 month -1 day');
    $calcDateA->setTimezone($utc);
    $diffA = $startDate->diff($calcDateA);
    echo("Using $timeZone\n");
    echo $startDate->format('r'), "\n";
    echo $calcDateA->format('r'), "\n";
    echo $diffA->format('Days: %d / Months: %m'), "\n\n";
}

Это приводит к потере некоторого контекста:

Using GMT
Wed, 01 Jul 2020 00:00:00 +0000
Fri, 31 Jul 2020 00:00:00 +0000
Days: 30 / Months: 0

Using Europe/Paris
Tue, 30 Jun 2020 22:00:00 +0000
Thu, 30 Jul 2020 22:00:00 +0000
Days: 0 / Months: 1

В системе отслеживания ошибок PHP есть несколько тикетов. В один из них участник объясняет:

это не связано с переходом на летнее время (на этот раз). Но случается, что PHP сначала преобразует его во время UT C, что в итоге приводит к сравнению 2016-12-31 23:00 с 2017-09-30 22:00 - а затем алгоритм становится еще более запутанным и беспорядочным. вверх.

Этот код сложный, и, к сожалению, его нужно переписать. Это займет некоторое время.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...