diff --git a/src/Generators/Google.php b/src/Generators/Google.php index 5ff4fe6..08fcef7 100644 --- a/src/Generators/Google.php +++ b/src/Generators/Google.php @@ -21,19 +21,9 @@ public function generate(Link $link): string { $url = 'https://calendar.google.com/calendar/render?action=TEMPLATE'; - $utcStartDateTime = (clone $link->from)->setTimezone(new DateTimeZone('UTC')); - $utcEndDateTime = (clone $link->to)->setTimezone(new DateTimeZone('UTC')); $dateTimeFormat = $link->allDay ? $this->dateFormat : $this->dateTimeFormat; - $url .= '&dates='.$utcStartDateTime->format($dateTimeFormat).'/'.$utcEndDateTime->format($dateTimeFormat); - - // Add timezone name if it is specified in both from and to dates and is the same for both - if ( - $link->from->getTimezone() && $link->to->getTimezone() - && $link->from->getTimezone()->getName() === $link->to->getTimezone()->getName() - ) { - $url .= '&ctz=' . $link->from->getTimezone()->getName(); - } - + $url .= '&dates='.$link->from->format($dateTimeFormat).'/'.$link->to->format($dateTimeFormat); + $url .= '&ctz=UTC'; $url .= '&text='.urlencode($link->title); if ($link->description) { diff --git a/src/Link.php b/src/Link.php index 9230480..8592a46 100644 --- a/src/Link.php +++ b/src/Link.php @@ -40,15 +40,20 @@ class Link public function __construct(string $title, \DateTimeInterface $from, \DateTimeInterface $to, bool $allDay = false) { + $this->from = clone $from; + $this->to = clone $to; $this->title = $title; $this->allDay = $allDay; - if ($from > $to) { + // Ensures from date is earlier than to date. + if ($this->from > $this->to) { throw InvalidLink::negativeDateRange($from, $to); } - $this->from = clone $from; - $this->to = clone $to; + // Ensures timezones match. + if ($this->from->getTimezone()->getName() !== $this->to->getTimezone()->getName()) { + $this->to->setTimezone($from->getTimezone()); + } } /** @@ -62,15 +67,15 @@ public function __construct(string $title, \DateTimeInterface $from, \DateTimeIn */ public static function create(string $title, \DateTimeInterface $from, \DateTimeInterface $to, bool $allDay = false) { - // When creating all day events, we need to be in the UTC timezone as all day events are "floating" based on the user's timezone - if ($allDay) { - $startDate = new \DateTime($from->format('Y-m-d'), new \DateTimeZone('UTC')); - $numberOfDays = $from->diff($to)->days + 1; + $from_date = clone $from; + $to_date = clone $to; - return self::createAllDay($title, $startDate, $numberOfDays); + // If all day, we need to add 1 day to end date to get the correct duration. + if ($allDay) { + $to_date->modify('+1 day'); } - return new static($title, $from, $to, $allDay); + return new static($title, $from_date, $to_date, $allDay); } /** @@ -83,12 +88,7 @@ public static function create(string $title, \DateTimeInterface $from, \DateTime */ public static function createAllDay(string $title, \DateTimeInterface $fromDate, int $numberOfDays = 1): self { - // In cases where the from date is not UTC, make sure it's UTC, size all day events are floating and non UTC dates cause bugs in the generators - if ($fromDate->getTimezone() !== new \DateTimeZone('UTC')) { - $fromDate = \DateTime::createFromFormat('Y-m-d', $fromDate->format('Y-m-d')); - } - - $from = (clone $fromDate)->modify('midnight'); + $from = (clone $fromDate); $to = (clone $from)->modify("+$numberOfDays days"); return new self($title, $from, $to, true); diff --git a/tests/Generators/GoogleGeneratorTest.php b/tests/Generators/GoogleGeneratorTest.php index 079e15b..2087142 100644 --- a/tests/Generators/GoogleGeneratorTest.php +++ b/tests/Generators/GoogleGeneratorTest.php @@ -35,4 +35,12 @@ public function it_correctly_generates_all_day_events_by_dates(): void $this->generator()->generate($this->createEventMultipleDaysViaStartEndWithTimezoneLink()) ); } + + /** @test */ + public function it_correctly_generates_all_day_events_by_dates_diff_tz(): void + { + $this->assertMatchesSnapshot( + $this->generator()->generate($this->createEventMultipleDaysViaStartEndWithDiffTimezoneLink()) + ); + } } diff --git a/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates_diff_tz__1.txt b/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates_diff_tz__1.txt new file mode 100644 index 0000000..a605ede --- /dev/null +++ b/tests/Generators/__snapshots__/GoogleGeneratorTest__it_correctly_generates_all_day_events_by_dates_diff_tz__1.txt @@ -0,0 +1 @@ +https://calendar.google.com/calendar/render?action=TEMPLATE&dates=20240125/20240201&ctz=UTC&text=All+day+bugs&details=Testing+all+day \ No newline at end of file diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt index 28af5d4..ad20f49 100644 --- a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_dates__1.txt @@ -2,7 +2,7 @@ BEGIN:VCALENDAR VERSION:2.0 PRODID:Spatie calendar-links BEGIN:VEVENT -UID:8fe3eececcd6b020db3b47ef55e7cf89 +UID:cb8469bae9cf10a9ee8f1179c6a932f5 SUMMARY:All day bugs DTSTAMP:20240125 DTSTART:20240125 diff --git a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt index f159c15..d230a35 100644 --- a/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt +++ b/tests/Generators/__snapshots__/IcsGeneratorTest__it_correctly_generates_all_day_events_by_days__1.txt @@ -2,7 +2,7 @@ BEGIN:VCALENDAR VERSION:2.0 PRODID:Spatie calendar-links BEGIN:VEVENT -UID:a05fc4dac68ae6064aaae69dcdfd60a6 +UID:b4be522f87b9894dadd2b9cd5479136b SUMMARY:All day bugs DTSTAMP:20240125 DTSTART:20240125 diff --git a/tests/TestCase.php b/tests/TestCase.php index 903f6ae..28a6de9 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -50,7 +50,7 @@ protected function createSingleDayAllDayEventLink(bool $immutable = false): Link return Link::createAllDay( 'Birthday', - $dateTimeClass::createFromFormat('Y-m-d', '2018-02-01', new DateTimeZone('UTC')) + $dateTimeClass::createFromFormat('Y-m-d', '2018-02-01', new DateTimeZone('UTC'))->setTime(0, 0) )->description($description)->address('Party Lane 1A, 1337 Funtown'); } @@ -63,7 +63,7 @@ protected function createMultipleDaysAllDayEventLink(bool $immutable = false): L return Link::createAllDay( 'Birthday', - $dateTimeClass::createFromFormat('Y-m-d', '2018-02-01', new DateTimeZone('UTC')), + $dateTimeClass::createFromFormat('Y-m-d', '2018-02-01', new DateTimeZone('UTC'))->setTime(0, 0), 5 )->description($description)->address('Party Lane 1A, 1337 Funtown'); } @@ -95,6 +95,21 @@ protected function createEventMultipleDaysViaStartEndWithTimezoneLink(bool $immu )->description($description); } + protected function createEventMultipleDaysViaStartEndWithDiffTimezoneLink(bool $immutable = false): Link + { + $description = 'Testing all day'; + + $dateTimeClass = $immutable ? DateTimeImmutable::class : DateTime::class; + + // This should result in 7 days duration (2024-01-25 00:00 to 2024-01-31 00:00 Pacific/Wake). + return Link::create( + 'All day bugs', + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-25', new DateTimeZone('Pacific/Wake'))->setTime(0, 0), + $dateTimeClass::createFromFormat('Y-m-d', '2024-01-30', new DateTimeZone('Europe/Luxembourg'))->setTime(13, 00), + true, + )->description($description); + } + protected function createDescriptionIsHTMLcodeEventLink(bool $immutable = false): Link { $description = 'With balloons, clowns and stuff