Alright I got a wrapper class working. It calculates real time passed. First it compares the offsets from UTC and add or subtract this time difference to the datetime-object passed as an argument. Thereafter it need not do anything more than to call parent::diff. Well ok I needed to introduce a one-liner to hack what could be yet another bug in PHP (see source code below). The DateTimeDiff:diff method calculates REAL time passed. In order to understand what that means, I advise you to test this class using various different dates and times and to aid your workload I also included at the bottom of this comment a rather simple HTML-page I wrote. This link could be a good starting point to get some ideas for date and time combinations:
https://wiki.php.net/rfc/datetime_and_daylight_saving_time
Moreover, take note that when we have a backward transition in DST, some date/time combinations can belong to both timezones. This ambiguity can make the results of this class differ from what was expected. Thus if you're seriously thinking about using this class, develop it further and ask for user clarification in these cases.
Here you are, the class:
<?php
class DateTimeDiff extends DateTime
{
public function diff($datetime, $absolute = false)
{
// Future releases could fix this bug and if so, this method would become counterproductive.
if (version_compare(PHP_VERSION, '5.4.0') > 0)
trigger_error("You are using a PHP version that might have adressed the problems of DateTime::diff", E_USER_WARNING);
// Have the clock changed?
$offset_start = $this->getOffset();
$offset_end = $datetime->getOffset();
if ($offset_start != $offset_end)
{
// Remember the difference.
$clock_moved = $offset_end - $offset_start;
// We wouldn't wanna mess things up for our caller; thus work on a clone.
$copy = clone $datetime;
if ($clock_moved > 0)
{
$timestamp_beforesub = $copy->getTimestamp();
// Subtract timedifference from end-datetime should make parent::diff produce accurate results.
$copy->sub( DateInterval::createFromDateString("$clock_moved seconds") );
// No change occured; sometimes sub() fails. This is a workable hack.
if ($timestamp_beforesub == $copy->getTimestamp())
$copy->setTimezone(new DateTimeZone("UTC"));
}
else // ..else < 0 and its a negative.
{
$clock_moved *= -1;
// Adding that timedifference to end-datetime should make parent::diff produce accurate results.
$copy->add( DateInterval::createFromDateString("$clock_moved seconds") );
}
return parent::diff($copy, $absolute);
} // <-- END "if ($offset_start != $offset_end)"
return parent::diff($datetime, $absolute);
}
}
?>
And a page for testing (will display results using both DateTime::diff and DateTimeDiff::diff):
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>DateTimeDiff-class</title>
<?php
if (! (empty($_GET['identifier']) && empty($_GET['start']) && empty($_GET['end'])))
{
$dt1_new = new DateTimeDiff("{$_GET['start']} {$_GET['identifier']}");
$dt1_old = new DateTime("{$_GET['start']} {$_GET['identifier']}");
$dt2 = new DateTime("{$_GET['end']} {$_GET['identifier']}");
$di_new = $dt1_new->diff($dt2);
$di_old = $dt1_old->diff($dt2);
// Extract UNIX timestamp and transitional data
$timezone_start = $dt1_new->getTimezone();
$timezone_end = $dt2->getTimezone();
$timestamp_start = $dt1_new->getTimeStamp();
$timestamp_end = $dt2->getTimeStamp();
$transitions_start = $timezone_start->getTransitions($timestamp_start, $timestamp_start);
$transitions_end = $timezone_end->getTransitions($timestamp_end, $timestamp_end);
echo <<<BUILDCONTAINER
<script type='text/javascript'>
function Container() { }
var c_new = new Container;
var c_old = new Container;
var t_start = new Container;
var t_end = new Container;
</script>
BUILDCONTAINER;
echo <<<SETTRANSITIONS
<script type='text/javascript'>
t_start.ts = '{$transitions_start[0]['ts']}';
t_start.time = '{$transitions_start[0]['time']}';
t_start.offset = '{$transitions_start[0]['offset']}';
t_end.ts = '{$transitions_end[0]['ts']}';
t_end.time = '{$transitions_end[0]['time']}';
t_end.offset = '{$transitions_end[0]['offset']}';
</script>
SETTRANSITIONS;
foreach ($di_new as $property => $value)
echo "<script type='text/javascript'>c_new.$property = $value</script>";
foreach ($di_old as $property => $value)
echo "<script type='text/javascript'>c_old.$property = $value</script>";
}
?>
<script type='text/javascript'>
window.onload = function()
{
if (c_new != null) // <-- em assume everything else is valid too.
{
// Update page with the results
for (var prop in c_new)
addtext(prop + ": " + c_new[prop] + " (" + c_old[prop] + ")");
addtext("Read like so..");
addtext("PROPERTY of DateInterval: VALUE using DateTimeDiff::diff ( VALUE using DateTime::diff )");
// Restore values sent/recieved
<?php
foreach ($_GET as $key => $value)
echo "document.getElementById('$key').value = '$value';";
?>
// Display transitiondata (For DateTime start)
var p_start = document.getElementById('p_start');
var appendstring = "TS: " + t_start.ts + ", Time: " + t_start.time + ", Offset: " + t_start.offset;
p_start.appendChild(document.createTextNode(appendstring));
// Display transitiondata (For DateTime end)
var p_end = document.getElementById('p_end');
appendstring = "TS: " + t_end.ts + ", Time: " + t_end.time + ", Offset: " + t_end.offset;
p_end.appendChild(document.createTextNode(appendstring));
}
}
function addtext()
{
var p = document.createElement("p");
p.appendChild(document.createTextNode(arguments[0]));
document.forms[0].appendChild(p);
}
</script>
</head>
<body>
<form action="test2.php" method="get">
<p>Identifier: <input type="text" name="identifier" id="identifier" value="Europe/Stockholm" /></p>
<p id="p_start">Start: <input type="text" name="start" id="start" /></p>
<p id="p_end">End: <input type="text" name="end" id="end" /></p>
<p><input type="submit" /></p>
</form>
</body>
</html>
DateTime::sub()
andDateTime::add()
– Phil