php / php-src

The PHP Interpreter
https://www.php.net
Other
38.22k stars 7.75k forks source link

DatePeriod with 1 hour DateInterval misses hour on long DST day #8860

Closed RobertWeideKyos closed 2 years ago

RobertWeideKyos commented 2 years ago

Description

The following code attempts to loop over hours during the long day that has 1 extra hour because of the daylight saving switch in the location Europe/Amsterdam:

<?php
$timezone = new DateTimeZone('Europe/Amsterdam');
$interval = new DateInterval('PT1H');
$startDate = new DateTime('2022-10-30 01:00:00', $timezone);
$endDate = new DateTime('2022-10-30 04:00:00', $timezone);

$period = new DatePeriod($startDate, $interval, $endDate);

foreach ($period as $date) {
    echo $date->format('Y-m-d H:i:s I U') . PHP_EOL;
}
Resulted in this output: 8.1.6 8.1.7
2022-10-30 01:00:00 1 1667084400
2022-10-30 02:00:00 0 1667091600
2022-10-30 03:00:00 0 1667095200
2022-10-30 01:00:00 1 1667084400
2022-10-30 02:00:00 1 1667088000
2022-10-30 03:00:00 0 1667095200
But I expected this output instead: Expected
2022-10-30 01:00:00 1 1667084400
2022-10-30 02:00:00 1 1667088000
2022-10-30 02:00:00 0 1667091600
2022-10-30 03:00:00 0 1667095200

Note that making a similar loop on the short day does indeed leave out the skipped hour.

PHP Version

8.1.7

Operating System

No response

hormus commented 2 years ago

PT1H (P period) (T time) this can indicate an hour but in the transition period it is not simply the difference of 3600 seconds but the output of the local time, Europe/Amsterdam dayligth saving time ends on 2022-10-30 03:00:00 with backward 1 hour From php 8.1.7 expected and correct 2022-10-30 01:00:00 1 1667084400 2022-10-30 02:00:00 1 1667088000 2022-10-30 03:00:00 0 1667095200

zoispag commented 2 years ago

I don't understand this. If I change the interval to 3600 seconds, it is still producing the same output:

<?php

$timezone = new DateTimeZone('Europe/Amsterdam');
$interval = new DateInterval('PT3600S');
$startDate = new DateTime('2022-10-30 01:00:00', $timezone);
$endDate = new DateTime('2022-10-30 04:00:00', $timezone);

$period = new DatePeriod($startDate, $interval, $endDate);

foreach ($period as $date) {
    echo $date->format('Y-m-d H:i:s I U') . PHP_EOL;
}

// 2022-10-30 01:00:00 1 1667084400
// 2022-10-30 02:00:00 1 1667088000
// 2022-10-30 03:00:00 0 1667095200

Looping over the DatePeriod object is skipping 3600 seconds, even if the interval is set to 3600 seconds.

Are you suggesting that we should never rely on DateInterval for time zones with transitions?

hormus commented 2 years ago

to learn more read the Documentation DateInterval which mentions which specification of http://en.wikipedia.org/wiki/Iso8601#Durations between the text you can also read Thus, "PT36H" could be used as well as "P1DT12H" for representing the same duration. But keep in mind that "PT36H" is not the same as "P1DT12H" when switching from or to Daylight saving time. in php one hour or 3600 seconds is controlled by the date output, for example where you see 01:00:00 or 02:00:00 or 03:00:00 instead of the Unix timestamp, i.e. it works for its offset. From the dayligth saving time or standard time DST ends at 03:00:00 for Europe/Amsterdam means offset +01:00 instead of +02:00

<?php

// 2022-10-30 01:00:00 1 Europa/Amsterdam offset +02:00 1667084400 Unix Timestamp UTC 2022-10-29 23:00:00

// 2022-10-30 02:00:00 1 Europa/Amsterdam offset +02:00:00 1667088000 Unix Timestamp UTC 2022-10-30 00:00:00

//Valid 2022-10-30 03:00:00 0  Europa/Amsterdam offset +01:00 1667095200 Unix Timestamp UTC 2022-10-30 02:00:00

//Not valid var_dump((new DateTime('2022-10-30 02:00:00', new DateTimeZone('Europe/Amsterdam')))->format('U'), '1667091600 => CET 2022-10-30 02:00:00 CEST 2022-10-30 03:00:00'); or 2022-10-30 03:00:00 1 Europa/Amsterdam offset +02:00 1667091600 Unix Timestamp UTC 2022-10-30 01:00:00
?>

https://wiki.php.net/rfc/datetime_and_daylight_saving_time this specification indicates the backward transition when in doubt I open another issue. However there are only three dates and not 4 possible

<?php
//2022-10-30 01:00:00 1 1667084400
//2022-10-30 02:00:00 0 1667091600
//2022-10-30 03:00:00 0 1667095200
?>
RobertWeideKyos commented 2 years ago

I am not sure what you are saying. This is key: But keep in mind that "PT36H" is not the same as "P1DT12H" when switching from or to Daylight saving time. But for PHP this is the case when switching from DST, so it doesn't follow the ISO principles of time.

The rest of your comment states that PHP currently doesn't work like that. I understand, that is why I reported it. I am somewhat surprised though that this has been a known issue since 2011. It isn't clear to me from the request for comments if there has been follow-up action?

derickr commented 2 years ago

The behaviour with PT1H is now correct:

Adding intervals like this is meant to add to the wall time. In that regard, what PHP 8.1.7 does is more correct than PHP 8.1.6. In 8.1.7 it adds 1 to the hour to make it go from 1 to 2, without being tipped up by the DST transition. The next addition makes it go from 2 to 3, but as 3:00 only exists as non-DST, the UTC offset than changes.

If you want to do every 3600 then your workaround is:

<?php
$utc = new DateTimeZone('UTC');
$timezone = new DateTimeZone('Europe/Amsterdam');
$interval = new DateInterval('PT1H');
$startDate = (new DateTime('2022-10-30 01:00:00', $timezone))->setTimezone($utc);
$endDate = (new DateTime('2022-10-30 04:00:00', $timezone))->setTimezone($utc);

$period = new DatePeriod($startDate, $interval, $endDate);

foreach ($period as $date) {
    echo $date->setTimezone($timezone)->format('Y-m-d H:i:s I U') . PHP_EOL;
}
?>

Which outputs:

2022-10-30 01:00:00 1 1667084400
2022-10-30 02:00:00 1 1667088000
2022-10-30 02:00:00 0 1667091600
2022-10-30 03:00:00 0 1667095200
savemetenminutes commented 11 months ago

All this code for a simple task that should be intuitive? And furthermore this issue has been reported times over...