2

Can someone explain to me why adding and subtracting the same DateInterval from DateTime objects results in different dates? Look at the hours: it goes from 20:00 to 19:00 when I subtract the interval, but when I add the interval it's still 19:00.

$date = new DateTime("2015-04-21 20:00", new DateTimeZone('Europe/Berlin'));

$days = 28;
$minutes = $days * 24 * 60;

$interval = new DateInterval("PT{$minutes}M");

var_dump($date);
$date->sub($interval);
var_dump($date);
$date->add($interval);
var_dump($date);

Results in:

object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2015-04-21 20:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/Berlin"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2015-03-24 19:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/Berlin"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2015-04-21 19:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/Berlin"
}

It looks to me that $date->sub($interval); changes the hour from 20:00 to 19:00 because of Daylight Saving Time difference (it works as expected with other dates, i.e. 2015-05-21 20:00), but $date->add($interval); doesn't apply DTS. Could it be a bug?

pgorecki
  • 639
  • 6
  • 8
  • Why this question is being downvoted? – pgorecki Jun 17 '15 at 12:24
  • It might be because it is maths 101?? Put your `A - B` in brackets, and it might work correctly – Jaques Jun 17 '15 at 12:26
  • 2
    What do you mean? Take a closer look at the code. If I subtract 28 days from 2015-04-21 20:00:00 then I'm getting 2015-03-24 19:00:00. When I add 28 days to 2015-03-24 19:00:00 I'm getting 2015-04-21 19:00:00. So: (2015-04-21 20:00:00 - 28 days) + 28 days becomes 2015-04-21 19:00:00. Where is your math101 here? – pgorecki Jun 17 '15 at 12:28

3 Answers3

1

Using $interval->invert seems to solve the problem:

$date = new DateTime("2015-04-21 20:00", new DateTimeZone('Europe/Berlin'));

$days = 28;
$minutes = $days * 24 * 60;

$interval = new DateInterval("PT{$minutes}M");
$interval->invert = 1;

var_dump($date);
$date->add($interval);
var_dump($date);
$date->sub($interval);
var_dump($date);

Which results in:

object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2015-04-21 20:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/Berlin"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2015-03-24 19:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/Berlin"
}
object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2015-04-21 20:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(13) "Europe/Berlin"
}
pgorecki
  • 639
  • 6
  • 8
0

It is indeed because of the Daylight saving time. For the given time if you change your Time Zone to 'America/Los_Angeles' for example it would be fine because in LA the time changed in the beginning of March. You can also test with the same time zone putting your date at the start of November then you will see that 28 days before you will get 21:00.

Check here for the supported time zones.

Akis
  • 193
  • 10
  • That doesn't answer my question. It looks like sub() method applies DTS, but add() doesn't. – pgorecki Jun 17 '15 at 12:44
  • Oh sorry didn't notice your last sentence. My bad. This might related to https://bugs.php.net/bug.php?id=55253 although its only for zone type 2. I will run some test and i ll come back. – Akis Jun 17 '15 at 13:44
0

I recently haved a similary problem with this piece of code :

private function convertSecondsToInterval($value)
{
    $dt1 = new \DateTime();
    $dt2 = clone $dt1;
    $dt2->add(new \DateInterval('PT'. $value . 'S'));
    return $dt2->diff($dt1);
}

During night from 2018-03-24 to 2018-03-25 (timezone change from CET to CEST in France), I noticed a strange behavior : the DateInterval result was 1 hour less than expected. This is because by default, the DateTime object is on Europe/Paris timezone. So, for example when adding 7 hours to 2018-03-24 20:00:00 I raise the timezone change (+01:00 to +02:00), resulting in a sum with 1 hour less than expected.

The implemented solution is to initialize the DateTime object in with UTC timezone to skip daylight change problems.

private function convertSecondsToInterval($value)
{
    $dt1 = new \DateTime('now', new \DateTimeZone('UTC'));
    $dt2 = clone $dt1;
    $dt2->add(new \DateInterval('PT'. $value . 'S'));

    return $dt2->diff($dt1);
}
David
  • 550
  • 5
  • 7