php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #80748 DateInterval wrongly counts DateTime->diff days
Submitted: 2021-02-13 20:45 UTC Modified: 2021-02-22 09:35 UTC
From: palansher at outlook dot com Assigned:
Status: Not a bug Package: Date/time related
PHP Version: Irrelevant OS: ubuntu 18.04
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: palansher at outlook dot com
New email:
PHP Version: OS:

 

 [2021-02-13 20:45 UTC] palansher at outlook dot com
Description:
------------
DateTime->diff returns different days, depending on how many seconds passed from midnight on a target date.
This behaviour is independent of timezone settings.
Tested on PHP 7.2-7.4

The bug doesn't appear if you use short period:

<?php
$beforDate=DateTime::createFromFormat('Y-m-d G:i:s', '1900-01-01 00:00:00');
$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '1900-01-08 00:00:43');
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "days passed if target date have 42 seconds from midnight: $daysPassed" . PHP_EOL;
$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '1900-01-08 00:00:43');
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "days passed if target date have 43 seconds from midnight: $daysPassed" . PHP_EOL;



Test script:
---------------
<?php
$beforDate=DateTime::createFromFormat('Y-m-d G:i:s', '1900-01-01 00:00:00');
$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '2021-01-08 00:00:42');
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "days passed if target date have 42 seconds from midnight: $daysPassed" . PHP_EOL;
$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '2021-01-08 00:00:43');
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "days passed if target date have 43 seconds from midnight: $daysPassed" . PHP_EOL;


Expected result:
----------------
days passed: 44201

Actual result:
--------------
days passed: 44202

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2021-02-13 20:49 UTC] requinix@php.net
-Status: Open +Status: Feedback
 [2021-02-13 20:49 UTC] requinix@php.net
Google thinks the answer is 44202, and that's what I get on 3v4l for both $afterDates.
https://3v4l.org/QkOl8

Are you sure the timezone doesn't matter? Is your timezone database up to date?
 [2021-02-14 14:43 UTC] palansher at outlook dot com
-Status: Feedback +Status: Open
 [2021-02-14 14:43 UTC] palansher at outlook dot com
Just realized:
This error appears if default timezone set, like
date_default_timezone_set('Europe/Moscow'); 

But timezone must not affect the result.

corrected code:

date_default_timezone_set('Europe/Moscow'); 
$beforDate=DateTime::createFromFormat('Y-m-d G:i:s', '1900-01-01 00:00:00');
$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '2021-02-08 00:00:42');
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "1. days passed if target date have 42 seconds from midnight: $daysPassed" . PHP_EOL;
$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '2021-02-08 00:00:43');
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "2. days passed if target date have 43 seconds from midnight: $daysPassed" . PHP_EOL;

result:
1. days passed if target date have 42 seconds from midnight: 44232
2. days passed if target date have 43 seconds from midnight: 44233
 [2021-02-14 14:50 UTC] palansher at outlook dot com
requinix@php.net, you are right. 
The 
date_default_timezone_set('Europe/Moscow');
was set.

But!! a year has the same number of days in any timezone. How timezone can affect the total days number?
 [2021-02-14 17:17 UTC] palansher at outlook dot com
Found another incorrect behaviour:

$beforDate=DateTime::createFromFormat('Y-m-d G:i:s', '1900-01-01 00:00:00');
$beforDate->setTimezone(new DateTimeZone('UTC'));

$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '2021-02-08 00:00:00');
$afterDate->setTimezone(new DateTimeZone('UTC'));
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "1. days passed: $daysPassed" . PHP_EOL;

$afterDate=DateTime::createFromFormat('Y-m-d G:i:s', '2021-02-08 00:50:00');
$afterDate->setTimezone(new DateTimeZone('UTC'));
$dtInterval = $beforDate->diff($afterDate);
$daysPassed = $dtInterval->days;
echo "1. days passed if 50 minutes added to target date: $daysPassed" . PHP_EOL;

Have output:

days passed: 44232
days passed if 50 minutes added to target date: 44233
https://3v4l.org/49Dv0
 [2021-02-14 21:28 UTC] requinix@php.net
-Status: Open +Status: Not a bug
 [2021-02-14 21:28 UTC] requinix@php.net
Before 1917, Moscow was GMT+02:30:17. Those 17 seconds account for the odd behavior around :43 seconds when you use diffs between <1917 and >1917.
 [2021-02-15 13:21 UTC] palansher at outlook dot com
@requinix@php.net

>Before 1917, Moscow was GMT+02:30:17. Those 17 seconds account for the odd >behavior around:43 seconds when you use diffs between <1917 and >1917.

Thank you!

But I doubt it, because:

1) in the last code example I set UTC timezone, not Moscow.
2) How timezone affects the number of seconds in a year? Such a number is always the same in any timezone, depending on the particular year of course...
3) it seems that the reason can be the "leap seconds" https://en.wikipedia.org/wiki/Leap_second. 
And DT->diff doesn't pay attention to it.
 [2021-02-15 23:14 UTC] requinix@php.net
> it seems that the reason can be the "leap seconds"

1. There have been 37 leap seconds. I don't see how that can turn into 43 seconds.
2. The first leap second was in 1972. If you diff 1900 to 1970 then the difference still occurs with 25604/25605 days.

It is not leap seconds.

> in the last code example I set UTC timezone, not Moscow.

Actually no. In your code where you use setTimezone, what you do is create a DateTime *according to the default timezone* and then modify the date to use the new timezone. If you want PHP to parse a date string according to a certain timezone then you have to specify that timezone at the time of parsing: with createFromFormat.
https://3v4l.org/mWIjk

Note that 3v4l's default timezone is Europe/Amsterdam, which used unusual UTC offsets through the 1930s. (Of course UTC didn't actually exist until the 1960s, so accusing Amsterdam of doing something weird according to a standard that hadn't been developed yet would be unfair.)
 [2021-02-16 17:14 UTC] palansher at outlook dot com
@requinix@php.net

Thank you for a detailed explanation.
The only thing I cannot understand is: where and how applicable dt->diff is?

For example (in my case) I need only count the time difference (in whole seconds, days, weeks) independent from geography. in one city, during a 100-200 years period.
Why a time zone affects these calculations at all? It is the same number of seconds for a certain year in a certain place. It is a very important and interesting question.

Maybe you would so kind and advise me on a PHP tool to do this?

My final goal is to design a countable week numbers system that is independent of geography. Like countable years. For example week number 1,2 ..53245. It will allow me to get the "minus three weeks" number (i.e.) from the current week just deducting =53245-3 even across the year border. The very frst week can be selected arbitrarily, i.e 1st Jan 1900 (Monday) or 1st Jan 1990 (Monday) .

ISO weeks system (1-52(53)) is not suitable for that because in the case of ISO "week number 1" deducting "1" in PHP code we will get "0" but not the correct result: 52(53)'th week of the previous year.

Maybe the Unix timestamps will give precise accuracy as the tool for whole weeks calculations?

Thank you for your time!
 [2021-02-16 17:40 UTC] requinix@php.net
> Why a time zone affects these calculations at all? It is the same number of
> seconds for a certain year in a certain place.

DST and other clock changing events mean that some days are more than 86400 seconds long and some days are less.

> Maybe you would so kind and advise me on a PHP tool to do this?

I cannot give you suitable advice because (a) this is a bug tracker and (b) I don't know your application. If PHP's diff is not suitable for your needs then you may have to come up with your own.
One common approach that might also be suitable for you is to count calendar days only. That algorithm is simple: number of days from the start to the end of its month + number of days in the (whole) months in between the start and end + the number of days at the end.

Dates are hard, and there is no single solution that works for everyone.
 [2021-02-16 19:45 UTC] palansher at outlook dot com
Thank you!

I need only know the one thing before creating my own solution:

does PHP7 consider leap years and leap seconds in DT functions:

dt->diff, ->modify, ->add, ->sub if to use UTC for every date?
 [2021-02-16 20:04 UTC] requinix@php.net
> does PHP7 consider leap years and leap seconds in DT functions:

Leap years definitely, leap seconds I don't know.
 [2021-02-22 09:35 UTC] derick@php.net
PHP's Date/Time support does not deal with leap seconds: https://derickrethans.nl/leap-seconds-and-what-to-do-with-them.html
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri May 03 22:01:33 2024 UTC