Bug #81072 [Com]: Incorrect behavior of DateTime->modify across DST changes

From: Date: Sun, 23 May 2021 09:45:58 +0000
Subject: Bug #81072 [Com]: Incorrect behavior of DateTime->modify across DST changes
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-233994@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=81072&edit=1 ID: 81072 Comment by: php-bugs at allenjb dot me dot uk Reported by: ph-bugs at allenjb dot me dot uk Summary: Incorrect behavior of DateTime->modify across DST changes Status: Feedback Type: Bug Package: Date/time related PHP Version: 8.0.6 Block user comment: N Private report: N New Comment: I've tested this with current master (8.1-dev) and the issue still exists. I noticed a bug in the reproduction script I previously posted and have fixed it. Updated script: https://3v4l.org/01oD9 <?php function incrementWithSwitch($date, $invert) { $timezone = $date->getTimezone(); $date = $date->setTimezone(new DateTimeZone('UTC')); $date = $date->modify(($invert ? '-' : '+') . '1 hour'); $date = $date->setTimezone($timezone); $date = $date->setTime((int)$date->format('H'), 0); return $date; } function incrementWithoutSwitch($date, $invert) { $date = $date->modify(($invert ? '-' : '+') . '1 hour'); return $date; } $tz = new \DateTimeZone("Europe/London"); $dtWith = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2021-03-28 04:00:00", $tz); $dtWithout = $dtWith; $invert = true; for ($i = 0; $i < 4; $i++) { print "\nRound {$i}:"; $dtWith = incrementWithSwitch($dtWith, $invert); $dtWithout = incrementWithoutSwitch($dtWithout, $invert); print "With switch: ". $dtWith->format(\DateTime::RFC3339); print " :: Without switch: ". $dtWithout->format(\DateTime::RFC3339); } print "\n---\n"; $dtWith = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2021-03-28 00:00:00"); $dtWithout = $dtWith; $invert = false; for ($i = 0; $i < 4; $i++) { print "\nRound {$i}:"; $dtWith = incrementWithSwitch($dtWith, $invert); $dtWithout = incrementWithoutSwitch($dtWithout, $invert); print "With switch: ". $dtWith->format(\DateTime::RFC3339); print " :: Without switch: ". $dtWithout->format(\DateTime::RFC3339); } Previous Comments: ------------------------------------------------------------------------ [2021-05-22 20:17:54] requinix@php.net Looks like 3v4l.org is behind a few versions - maybe was impacted by the GitHub move. I can't reproduce locally. Can you test in current master (8.1)? If it works there then this may be a duplicate of 74274. ------------------------------------------------------------------------ [2021-05-22 18:55:52] ph-bugs at allenjb dot me dot uk Description: ------------ See https://3v4l.org/hGXcr Working on a fix for https://github.com/dragonmantank/cron-expression/issues/111 As you can see, when working backwards with ->modify("-1 hour") DateTime fails to properly handle the DST change and keeps pushing the time forward to 03:00:00 Working forwards with ->modify("+1 hour") does not exhibit the same behavior. (The code examples also show a workaround, which involved switching timezones while performing the ->modify) Test script: --------------- <?php function incrementWithSwitch($date, $invert) { $timezone = $date->getTimezone(); $date = $date->setTimezone(new DateTimeZone('UTC')); $date = $date->modify(($invert ? '-' : '+') . '1 hour'); $date = $date->setTimezone($timezone); $date = $date->setTime((int)$date->format('H'), 0); return $date; } function incrementWithoutSwitch($date, $invert) { $date = $date->modify(($invert ? '-' : '+') . '1 hour'); return $date; } $tz = new \DateTimeZone("Europe/London"); $dtWith = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2021-03-28 04:00:00"); $dtWithout = $dtWith; $invert = true; for ($i = 0; $i < 3; $i++) { print "\nRound {$i}:"; $dtWith = incrementWithSwitch($dtWith, $invert); $dtWithout = incrementWithoutSwitch($dtWithout, $invert); print "With switch: ". $dtWith->format(\DateTime::RFC3339); print " :: Without switch: ". $dtWithout->format(\DateTime::RFC3339); } print "\n---\n"; $dtWith = \DateTimeImmutable::createFromFormat("!Y-m-d H:i:s", "2021-03-28 00:00:00"); $dtWithout = $dtWith; $invert = false; for ($i = 0; $i < 3; $i++) { print "\nRound {$i}:"; $dtWith = incrementWithSwitch($dtWith, $invert); $dtWithout = incrementWithoutSwitch($dtWithout, $invert); print "With switch: ". $dtWith->format(\DateTime::RFC3339); print " :: Without switch: ". $dtWithout->format(\DateTime::RFC3339); } Expected result: ---------------- Round 0:With switch: 2021-03-28T03:00:00+02:00 :: Without switch: 2021-03-28T03:00:00+02:00 Round 1:With switch: 2021-03-28T01:00:00+01:00 :: Without switch: 2021-03-28T01:00:00+02:00 Round 2:With switch: 2021-03-28T00:00:00+01:00 :: Without switch: 2021-03-28T00:00:00+02:00 --- Round 0:With switch: 2021-03-28T01:00:00+01:00 :: Without switch: 2021-03-28T01:00:00+01:00 Round 1:With switch: 2021-03-28T03:00:00+02:00 :: Without switch: 2021-03-28T03:00:00+02:00 Round 2:With switch: 2021-03-28T04:00:00+02:00 :: Without switch: 2021-03-28T04:00:00+02:00 Actual result: -------------- Round 0:With switch: 2021-03-28T03:00:00+02:00 :: Without switch: 2021-03-28T03:00:00+02:00 Round 1:With switch: 2021-03-28T01:00:00+01:00 :: Without switch: 2021-03-28T03:00:00+02:00 Round 2:With switch: 2021-03-28T00:00:00+01:00 :: Without switch: 2021-03-28T03:00:00+02:00 --- Round 0:With switch: 2021-03-28T01:00:00+01:00 :: Without switch: 2021-03-28T01:00:00+01:00 Round 1:With switch: 2021-03-28T03:00:00+02:00 :: Without switch: 2021-03-28T03:00:00+02:00 Round 2:With switch: 2021-03-28T04:00:00+02:00 :: Without switch: 2021-03-28T04:00:00+02:00 ------------------------------------------------------------------------ -- Edit this bug report at https://bugs.php.net/bug.php?id=81072&edit=1

« previous php.bugs (#233994) next »