NotionCommotion Posted July 17, 2019 Share Posted July 17, 2019 If there are two DataTime's which are exactly X seconds apart and the difference between those two DateTime's is added to a third DateTime, then the third DataTime's timestamp would change by exactly X seconds, true? Then why doesn't the second test below show those results, but is off by 1 day? <?php function test(string $start, string $end, string $newStart) { $startDt = new \DateTime($start); $endDt = new \DateTime($end); $intervalDt=$startDt->diff($endDt); $startSeconds=$startDt->getTimestamp(); $endSeconds=$endDt->getTimestamp(); $seconds = $endSeconds - $startSeconds; $dt = new \DateTime($newStart); $newStartSeconds=$dt->getTimestamp(); $dt->add($intervalDt); $newEndSeconds=$dt->getTimestamp(); $newSeconds = $newEndSeconds - $newStartSeconds; $newEnd = $dt->format(\DateTime::RFC3339); echo("original: $start => $end $endSeconds - $startSeconds = $seconds seconds<br>"); echo("new: $newStart => $newEnd $newEndSeconds - $newStartSeconds = $newSeconds seconds<br>"); echo("Error: seconds - newSeconds = ".($seconds - $newSeconds)); } test("2017-06-26T00:00:00Z", "2017-07-09T00:00:00Z", "2018-03-05T00:00:00Z"); echo('<br><br>'); test("2017-12-08T00:00:00Z", "2018-02-15T00:00:00Z", "2018-03-05T00:00:00Z"); Quote original: 2017-06-26T00:00:00Z => 2017-07-09T00:00:00Z 1499558400 - 1498435200 = 1123200 seconds new: 2018-03-05T00:00:00Z => 2018-03-18T00:00:00+00:00 1521331200 - 1520208000 = 1123200 seconds Error: seconds - newSeconds = 0 original: 2017-12-08T00:00:00Z => 2018-02-15T00:00:00Z 1518652800 - 1512691200 = 5961600 seconds new: 2018-03-05T00:00:00Z => 2018-05-12T00:00:00+00:00 1526083200 - 1520208000 = 5875200 seconds Error: seconds - newSeconds = 86400 Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/ Share on other sites More sharing options...
Barand Posted July 17, 2019 Share Posted July 17, 2019 When I do this I get exactly the same differences??? function test2 ($start, $end, $newstart) { $dt1 = new DateTime($start); $dt2 = new DateTime($end); $dif = $dt1->diff($dt2); $dt3 = new DateTime($newstart); $dt4 = clone $dt3; $dt4->add($dif); echo $dt1->format('Y-m-d H:i:s') . ' | ' ; echo $dt2->format('Y-m-d H:i:s') . ' | ' ; echo $dif->format('%y %m %d %h %i %s') . '<br>' ; echo $dt3->format('Y-m-d H:i:s') . ' | ' ; echo $dt4->format('Y-m-d H:i:s') . ' | ' ; echo $dt3->diff($dt4)->format('%y %m %d %h %i %s') . '<br>' ; } test2('2019-07-15 12:30:00', '2019-07-17 14:15:30', '2019-08-15'); Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568471 Share on other sites More sharing options...
requinix Posted July 17, 2019 Share Posted July 17, 2019 If I remember correctly, ->diff measures the apparent difference between dates. Like the difference between 00:00 and 05:00 is apparently five hours. But timestamps are a measure of elapsed time. 00:00 to 05:00 could be five hours, or it could be four or six if it crosses a DST boundary. Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568472 Share on other sites More sharing options...
Barand Posted July 17, 2019 Share Posted July 17, 2019 That could account for a one hour difference, but a whole day!? Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568473 Share on other sites More sharing options...
Barand Posted July 17, 2019 Share Posted July 17, 2019 Immedialtely after posting my first reply I realised I hadn't posted my results. I therefore edited the reply and added them. But they aren't there! Anyway, they were 2019-07-15 12:30:00 | 2019-07-17 14:15:30 | 0 0 2 1 45 30 2019-08-15 00:00:00 | 2019-08-17 01:45:30 | 0 0 2 1 45 30 Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568474 Share on other sites More sharing options...
taquitosensei Posted July 17, 2019 Share Posted July 17, 2019 $dt4->add($dif); I think that gives the result expected. Not sure why. 2019-07-15 12:30:00 | 2019-07-17 14:15:30 | 0 0 2 1 45 30<br>2019-08-17 01:45:30 | 2019-08-19 03:31:00 | 0 0 2 1 45 30<br> Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568475 Share on other sites More sharing options...
requinix Posted July 17, 2019 Share Posted July 17, 2019 It's (cough) possible I saw the number 86400, thought "oh I recognize that", and went straight into the DST explanation. Which is wrong for two reasons: that number is for a day not an hour, and because we're dealing with UTC here. But what I was right about was the apparent vs. elapsed issue. Look at the dates - just the dates: 2017-06-26 => 2017-07-09 = 4 days of June + 9 days of July = 13 days 2018-03-05 => 2018-03-18 = 13 days Good 2017-12-08 => 2018-02-15 = 24 days of December + 31 days of January + 15 days of February = 69 days 2018-03-05 => 2018-05-12 = 27 days of March + 30 days of April + 12 days of May = 69 days Good But that's elapsed time and diff deals with apparent time. And apparently, 2017-06-26 => 2017-07-09 = 0 months + 13 days (overflowing after 30) = 13 days 2018-03-05 => 2018-03-18 = 0 months + 13 days = 13 days Same 2017-12-08 => 2018-02-15 = 2 months (December with 31 days, January with 31 days) + 7 days = 69 days 2018-03-05 => 2018-05-12 = 2 months (March with 31 days, April with 30 days) + 7 days = 68 days Oh no The results are even more off when you have to deal with February and its mere 28 days. It's weird, but that's what you get when you try to do date math. And unfortunately there's no simple moral to this story: sometimes a diff-ed DateInterval is appropriate (tends to look nicer across DST boundaries), sometimes a diff-ed timestamp is appropriate (tends to work more like what you'd want), and using the wrong one will screw you up. Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568476 Share on other sites More sharing options...
NotionCommotion Posted July 18, 2019 Author Share Posted July 18, 2019 Thanks for the explanation. What also threw me for a loop was when I attempted to compare DateInterval's (i.e. if($di1>$di2)...) similar to comparing DateTime's. It sometimes works but other times does not and one gets different results when stepping through with a debugger opposed to fully running. I think there is a takeaway moral for this one... Just don't do it. Any disagreement? Regarding whether diff-ed DataIntervals are appropriate or diff-ed timestamps, what do you think for getSubstitute()? I have a bunch of time-series data, and when a day or many days is missing data, wish to find another range of data both with the same duration and starts and ends on the same day of the week (guess ends goes without saying). public function read(int $id):array { $results = []; //$data is grouped by day. [['time'=>'2017-06-21','count'=>123]...] $data=$this->getData($id); //Groups is ['gaps'=>[['start'=>'2017-06-21','end'=>'2017-06-21'], ...], 'fills'=>[sameAsGaps]] $groups=$this->getGroups($data); foreach($groups['gaps'] as $gap) { if($substitute=$this->getSubstitute($gap, $groups['fills'])) { $results[$gap['time']]=true; $this->processGroup($gap, $substitute); } else { //No substitute found for this range $results[$gap['time']]=false; } } return $results; } private function getGroups(array $data):array { $gaps=[]; $fills=[['start'=>$data[0]['time']]]; $missing=false; //First group must have data and last group must be the end of data foreach($data as $day) { if($missing) { if($day['count']>$this->minimumSamples) { //End of missing data $missing=false; $fills[] = ['start'=>$day['time']]; $gaps[count($gaps)-1]['end']=$day['time']; } } else { if($day['count']<=$this->minimumSamples) { //Start of missing data $missing=true; $gaps[] = ['start'=>$day['time']]; $fills[count($fills)-1]['end']=$day['time']; } } } $fills[count($fills)-1]['end']=$data[count($data)-1]['time']; return ['gaps'=>$gaps, 'fills'=>$fills]; } public function getSubstitute(array $gap, $fills):?array { $gapStart = new \DateTime($gap['start']); $gapEnd = new \DateTime($gap['end']); $gapInterval=$gapStart->diff($gapEnd); $gapStartTs = $gapStart->getTimestamp(); $gapEndTs = $gapEnd->getTimestamp(); $gapDiffSeconds = $gapEndTs - $gapStartTs; $startWeekDay = $gapStart->format('l'); $endWeekDay = $gapEnd->format('l'); $substitute=null; $substituteOffset=null; foreach($fills as $fill) { $fillStart=new \DateTime($fill['start']); $fillStart->modify($startWeekDay); $fillEnd=new \DateTime($fill['end']); $fillEnd->modify("previous $endWeekDay"); if($fillEnd > $fillStart) { $fillStartTs=$fillStart->getTimestamp(); if(($fillEnd->getTimestamp() - $fillStartTs) >= $gapDiffSeconds) { if($fillStart > $gapStart) { //Fill data is newer than gap data. Takes precidence $offset=$fillStartTs - $gapStartTs; if(is_null($substituteOffset) || $offset <= $substituteOffset) { $substituteOffset=$offset; $substitute=$fill; $substitute['start']=$fillStart; $substitute['end']=(clone $fillStart)->add($gapInterval);; } } else { //Fill data is older than gap data. Does not take precidence $offset=$gapStartTs - $fillStartTs; if(is_null($substituteOffset) || $offset < $substituteOffset) { $substituteOffset=$offset; $substitute=$fill; $substitute['end']=$fillEnd; $substitute['start']=(clone $fillEnd)->sub($gapInterval);; } } } } //else {syslog(LOG_INFO, 'Start time occurs later than end time. Ignore');} } return $substitute; } Quote Link to comment https://forums.phpfreaks.com/topic/308981-unexpected-results-when-adding-intervals-to-datetime/#findComment-1568499 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.