From 5669694a412cb6f434658c3200c3fd2ca5b378e3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 8 Aug 2023 18:32:07 +0200 Subject: [PATCH 1/2] Cron/OneOff: Always use the application's time zone Since `Cron` & `OneOff` frequencies work with `DatemTime` by either calling the methods of the class manually or constructing from a serialized representation, time zones can differ from the application's time zone. However, in the frontend we don't display time zone information, so the time may appear incorrect. This change ensures that both frequencies always work with the application's time zone as well. --- src/Cron.php | 3 +++ src/OneOff.php | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Cron.php b/src/Cron.php index 56de840..3f391a7 100644 --- a/src/Cron.php +++ b/src/Cron.php @@ -5,6 +5,7 @@ use Cron\CronExpression; use DateTime; use DateTimeInterface; +use DateTimeZone; use InvalidArgumentException; use ipl\Scheduler\Contract\Frequency; @@ -98,6 +99,7 @@ public function getExpression(): string public function startAt(DateTimeInterface $start): self { $this->start = clone $start; + $this->start->setTimezone(new DateTimeZone(date_default_timezone_get())); return $this; } @@ -112,6 +114,7 @@ public function startAt(DateTimeInterface $start): self public function endAt(DateTimeInterface $end): Frequency { $this->end = clone $end; + $this->end->setTimezone(new DateTimeZone(date_default_timezone_get())); return $this; } diff --git a/src/OneOff.php b/src/OneOff.php index 1c99e40..15a1a64 100644 --- a/src/OneOff.php +++ b/src/OneOff.php @@ -4,17 +4,18 @@ use DateTime; use DateTimeInterface; -use InvalidArgumentException; +use DateTimeZone; use ipl\Scheduler\Contract\Frequency; class OneOff implements Frequency { - /** @var DateTime Start time of this frequency */ + /** @var DateTimeInterface Start time of this frequency */ protected $dateTime; - public function __construct(DateTime $dateTime) + public function __construct(DateTimeInterface $dateTime) { $this->dateTime = clone $dateTime; + $this->dateTime->setTimezone(new DateTimeZone(date_default_timezone_get())); } public function isDue(DateTimeInterface $dateTime): bool From c8fe1073eb2a0c3807a45e299e69875a95a584e7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 9 Aug 2023 14:53:09 +0200 Subject: [PATCH 2/2] Add missing `Cron/OneOff` tz test cases --- tests/CronTest.php | 34 ++++++++++++++++++++++++++++++++++ tests/OneOffTest.php | 27 ++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/CronTest.php b/tests/CronTest.php index 59d2ba0..686221a 100644 --- a/tests/CronTest.php +++ b/tests/CronTest.php @@ -3,6 +3,7 @@ namespace ipl\Tests\Scheduler; use DateTime; +use DateTimeZone; use InvalidArgumentException; use ipl\Scheduler\Contract\Frequency; use ipl\Scheduler\Cron; @@ -132,4 +133,37 @@ public function testJsonSerializeAndDeserialize() $this->assertEquals($start, $fromJson->getStart()); $this->assertEquals($end, $fromJson->getEnd()); } + + public function testSerializeAndDeserializeHandleTimezonesCorrectly() + { + $oldTz = date_default_timezone_get(); + date_default_timezone_set('Europe/Berlin'); + + try { + $start = new DateTime('01-06-2023T12:00:00', new DateTimeZone('America/New_York')); + $end = new DateTime('01-06-2024T07:00:00', new DateTimeZone('America/New_York')); + + $cron = Cron::fromJson( + json_encode( + (new Cron('@minutely')) + ->startAt($start) + ->endAt($end) + ) + ); + + $this->assertEquals( + new DateTime('2023-06-01T18:00:00'), + $cron->getStart(), + 'Cron::jsonSerialize() does not restore the start date with a time zone correctly' + ); + + $this->assertEquals( + new DateTime('2024-06-01T13:00:00'), + $cron->getEnd(), + 'Cron::jsonSerialize() does not restore the end date with a time zone correctly' + ); + } finally { + date_default_timezone_set($oldTz); + } + } } diff --git a/tests/OneOffTest.php b/tests/OneOffTest.php index 1b3cb46..d3219ec 100644 --- a/tests/OneOffTest.php +++ b/tests/OneOffTest.php @@ -3,7 +3,7 @@ namespace ipl\Tests\Scheduler; use DateTime; -use ipl\Scheduler\Contract\Frequency; +use DateTimeZone; use ipl\Scheduler\OneOff; use PHPUnit\Framework\TestCase; @@ -45,4 +45,29 @@ public function testJsonSerialize() $this->assertEquals($oneOff, $fromJson); $this->assertEquals($now, $fromJson->getStart()); } + + public function testSerializeAndDeserializeHandleTimezonesCorrectly() + { + $oldTz = date_default_timezone_get(); + date_default_timezone_set('Europe/Berlin'); + + try { + $dateTime = new DateTime('01-06-2023T12:00:00', new DateTimeZone('America/New_York')); + $oneOff = OneOff::fromJson(json_encode(new OneOff($dateTime))); + + $this->assertEquals( + new DateTime('2023-06-01T18:00:00'), + $oneOff->getStart(), + 'OneOff::jsonSerialize() does not restore the start date with a time zone correctly' + ); + + $this->assertEquals( + new DateTime('2023-06-01T18:00:00'), + $oneOff->getEnd(), + 'OneOff::jsonSerialize() does not restore the start/end date with a time zone correctly' + ); + } finally { + date_default_timezone_set($oldTz); + } + } }