PHP date_diff() ведет себя неожиданно

#php

#php

Вопрос:

Вероятно, это просто я тупой, но…

 <?php
   $startDate = date_create('2019-11-01');
   $endDate = date_create('2019-12-01');
       
    $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);       
?>
  

в PHPfiddle выдает (как и ожидалось):

object(DateInterval) #3 (15)
{ [«y»]=> int(0)
[«m»]=> int(1) <=== это то, что вызывает у меня проблемы на моем локальном компьютере
[«d»] => int(0)
[«h»]=> int(0)
[«i»]=> int (0)
[«s»]=> int(0)
[«weekday»]=> int(0)
[«weekday_behavior»]=> int(0)
[«first_last_day_of»]=> int(0)
[«invert»]=> int(0)
[«days»]=> int(30)
[«special_type»]=> int(0)
[«special_amount»]=> int(0)
[«have_weekday_relative»]=> int(0)
[«have_special_relative»]=> int(0) }

Проверьте значение m , которое является количеством месяцев.

Однако на моем локальном компьютере тот же код выдает:

 class DateInterval#4 (16) {
  public $y =>
  int(0)
  public $m =>
  int(0)                          <======  why ??!!
  public $d =>
  int(30)
  public $h =>
  int(0)
  public $i =>
  int(0)
  public $s =>
  int(0)
  public $f =>
  double(0)
  public $weekday =>
  int(0)
  public $weekday_behavior =>
  int(0)
  public $first_last_day_of =>
  int(0)
  public $invert =>
  int(0)
  public $days =>
  int(30)
  public $special_type =>
  int(0)
  public $special_amount =>
  int(0)
  public $have_weekday_relative =>
  int(0)
  public $have_special_relative =>
  int(0)
}
  

Хорошо, я признаю, что это 30 дней, но ответ, который я искал, был 1 месяц и $interval->m равен нулю0.

Чего мне не хватает? Я не знаю, какую версию PHP использует веб-сайт PHPfiddle, но я использую 7.3.11 локально.

Комментарии:

1. PHPFiddle работает 5.6.40

2. Я бы сказал, что вы нашли ошибку в более новом PHP. Разница между датами с двумя разными значениями месяца должна возвращать интервал с ненулевым полем месяцев…

3. FWIW, обозначающий любую временную дельту в «месяцах», нарушается по определению, поскольку нет единого определенного значения для длины месяца … 🙂 Не ваша вина, в основном ошибка PHP…

4. Теперь это не так. Календарный месяц не является постоянной длиной, но он используется повсеместно в повседневной жизни. Разница между одной и той же датой в последовательных месяцах — это разница в месяц, будь то 28, 29, 30 или 31 день. Возможность вычислять дельты в терминах этих разбитых значений, возвращаемых, в date_diff основном полезна именно потому , что календарные месяцы (и годы) не переводятся в фиксированное количество дней; если бы все месяцы были одинаковой длины, вы могли бы просто получить дельту в днях и разделить.

5. @deceze: я изучаю календари как хобби; вы не говорите мне ничего, чего я не знаю. 🙂 Я просто говорю, что в повседневной жизни у нас есть контракты, интервалы выставления счетов за аренду и кредитную карту и другие вещи, которые указаны в месяцах. Крайние случаи будут крайними случаями, но утилита все еще существует.

Ответ №1:

Вероятно, разница в часовом поясе.

попробуйте это:

 <?php
   date_default_timezone_set('UTC');

   $startDate = date_create('2019-11-01');
   $endDate = date_create('2019-12-01');
       
   $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);  
/*
object(DateInterval)#3 (16) {
  ["y"]=>
  int(0)
  ["m"]=>
  int(1)
  ...
*/
  

также попробуйте это:

 <?php
   $startDate = date_create('2019-11-01 23:00');
   $endDate = date_create('2019-12-01 23:00');
       
   $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);       
   /* outputs the same ["m"]=> int(1) as above */
  

Вероятно, date_diff ищет разницу между меткой времени UTC и часовым поясом по умолчанию в вашем PHP, установленным на некоторое положительное значение.

Допустим, у вас есть некоторый часовой пояс по умолчанию, который равен UTC 02:00, поэтому date_create('2019-11-01') фактически устанавливается дата 2019-10-31 22:00:00 (UTC) и date_create('2019-12-01') фактически устанавливается дата 2019-11-30 22:00:00 (UTC)

теперь вы можете видеть, что у них нет разницы в целый месяц.

Но вы можете уловить еще один забавный эффект:

 <?php
   //assuming your timezone is UTC something
   $startDate = date_create('2019-10-31'); // creates a date 2019-10-30 XX:00 UTC
   $endDate = date_create('2019-12-01');   // creates a date 2019-11-30 XX:00 UTC
       
   $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);       

/*
object(DateInterval)#3 (16) {
  ["y"]=>
  int(0)  
  ["m"]=>
  int(1)   sic(!)
  ["d"]=>
  int(0)   sic(!)
*/
  

Также у вас может быть неожиданный результат, если в вашем часовом поясе установлен переход на летнее время.

 <?php
   // Germany has the daylight saving shift at last Sunday of March and October.
   date_default_timezone_set('Europe/Berlin'); 

   $startDate = date_create('2020-03-28 03:00');
   $endDate = date_create('2020-03-29 02:00');

   $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);  //   ["d"]=>  int(1)  ["h"]=>  int(0)

   
   
   $startDate = date_create('2020-03-28 03:00');
   $endDate = date_create('2020-03-29 03:00');

   $interval = date_diff($startDate, $endDate); 

   var_dump($interval); //   ["d"]=>  int(1)  ["h"]=>  int(0)


   $startDate = date_create('2020-03-28 03:00');
   $endDate = date_create('2020-03-28 03:00'); // the same
   $endDate->modify(' 23 hour');

   $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);  //   ["d"]=>  int(1)  ["h"]=>  int(0)

  

Для PHPFiddle по умолчанию установлен часовой пояс UTC, поэтому по умолчанию он дает разницу в один месяц.

Чтобы избежать этого, просто выполните все вычисления даты в UTC с самого начала:

 <?php
   $timeZone = new DateTimeZone('UTC');

   $startDate = new DateTime('2019-10-31', $timeZone);
   $endDate = new DateTime('2019-12-01', $timeZone);
       
   $interval = date_diff($startDate, $endDate);
    
   var_dump($interval);       
  

Комментарии:

1. Я посмеялся над (тем, что я считал) вашей наивностью, когда прочитал «вероятно, часовой пояс», потому что у них обоих один и тот же часовой пояс. И тогда у меня отвисла челюсть, потому что добавление ` date_default_timezone_set('UTC'); действительно привело к разнице в один месяц. Я не утверждаю, что понимаю это, но спасибо за ответ 🙂

Ответ №2:

Да, это ошибка и связана с часовым поясом вашего сервера. Вы можете использовать это обходное решение:

    $startDate = date_create('2019-11-01 UTC');
   $endDate = date_create('2019-12-01 UTC');
       
   $interval = date_diff($startDate, $endDate);
    
   echo $interval->m; //1     
  

Ответ №3:

Похоже, вы столкнулись с ошибкой в PHP 7.3.11, которой не было в более старой версии, используемой PhpFiddle. Также исправлена последняя стабильная версия, 7.4.11, поэтому вам следует просто обновить локальный PHP.

(Я заметил, что 7.3.11 — это то, что поставляется с macOS; если вы используете Mac, вы можете установить более новый PHP с помощью Homebrew.)

Комментарии:

1. Я обновил (XAMPP) до версии 7.4.11 и все равно получаю тот же результат.

2. Странно, должно быть, у нас разные часовые пояса. Я получил то же поведение, что и вы в 7.3.11, но получил ожидаемую 1-месячную дельту в 7.4.11. Но, похоже, вы все равно получили свой ответ, так здорово!

3. Нет, часовые пояса идентичны. Я могу var_dump() , если вы хотите. Оба в TZ 3 (Берлин) — что меня озадачивает — вдвойне, когда работает UTC (upvote)

4. Нет, я не ставил под сомнение, что ваши часовые пояса идентичны. Я просто предположил, что причина, по которой вы все еще видите ошибку в 7.4.11, а я нет, возможно, потому, что вы и я находимся в разных часовых поясах. Но разве Берлин не UTC 2 (скоро будет UTC 1)? Что означает «TZ 3»?

5. Когда я оцениваю, я получаю $startDate = date : 2019-11-01 00:00:00.000000, timezone : Europe/Berlin, timezone_type : 3 , поэтому я предполагаю, что я был неправ там: timezone_type (что бы ни означало 3). В любом случае, спасибо за ответ