From cceb2f8941b025e2f9178201579a44fae9b6d3c5 Mon Sep 17 00:00:00 2001 From: Yadd Date: Tue, 14 Oct 2025 10:16:44 +0200 Subject: [PATCH 1/2] feat: preserve UTC 'Z' indicator in Period JSON serialization When serializing PERIOD values to JSON (for jCal format), preserve the 'Z' timezone indicator for UTC datetimes. This ensures FREEBUSY periods maintain explicit UTC designation, avoiding timezone ambiguity. Without this change: FREEBUSY:20120226T230000Z/20120226T230000Z => serializes to: ["2012-02-26T23:00:00", "2012-02-26T23:00:00"] With this change: FREEBUSY:20120226T230000Z/20120226T230000Z => serializes to: ["2012-02-26T23:00:00Z", "2012-02-26T23:00:00Z"] This behavior is consistent with RFC 5545 which requires UTC times to be designated with the 'Z' suffix. Related: https://github.com/sabre-io/vobject/issues/411 --- lib/Property/ICalendar/Period.php | 10 +++++++--- tests/VObject/JCalTest.php | 2 +- tests/VObject/Parser/JsonTest.php | 2 +- tests/VObject/Parser/XmlTest.php | 10 +++++----- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/Property/ICalendar/Period.php b/lib/Property/ICalendar/Period.php index 7632cb4d1..ae4a5572a 100644 --- a/lib/Property/ICalendar/Period.php +++ b/lib/Property/ICalendar/Period.php @@ -87,17 +87,21 @@ public function getJsonValue(): array $start = DateTimeParser::parseDateTime($start); + // Check if the datetime is in UTC timezone + $isUtc = in_array($start->getTimezone()->getName(), ['UTC', 'GMT', 'Z', '+00:00']); + $zSuffix = $isUtc ? 'Z' : ''; + // This is a duration value. if ('P' === $end[0]) { $return[] = [ - $start->format('Y-m-d\\TH:i:s'), + $start->format('Y-m-d\\TH:i:s').$zSuffix, $end, ]; } else { $end = DateTimeParser::parseDateTime($end); $return[] = [ - $start->format('Y-m-d\\TH:i:s'), - $end->format('Y-m-d\\TH:i:s'), + $start->format('Y-m-d\\TH:i:s').$zSuffix, + $end->format('Y-m-d\\TH:i:s').$zSuffix, ]; } } diff --git a/tests/VObject/JCalTest.php b/tests/VObject/JCalTest.php index fa3479665..7624b8f84 100644 --- a/tests/VObject/JCalTest.php +++ b/tests/VObject/JCalTest.php @@ -97,7 +97,7 @@ public function testToJCal(): void 'sequence', new \stdClass(), 'integer', 5, ], [ - 'freebusy', new \stdClass(), 'period', ['2013-05-26T21:02:13', 'PT1H'], ['2013-06-26T12:00:00', '2013-06-26T13:00:00'], + 'freebusy', new \stdClass(), 'period', ['2013-05-26T21:02:13Z', 'PT1H'], ['2013-06-26T12:00:00Z', '2013-06-26T13:00:00Z'], ], [ 'url', new \stdClass(), 'uri', 'http://example.org/', diff --git a/tests/VObject/Parser/JsonTest.php b/tests/VObject/Parser/JsonTest.php index 392cc7c11..dacfb3b55 100644 --- a/tests/VObject/Parser/JsonTest.php +++ b/tests/VObject/Parser/JsonTest.php @@ -261,7 +261,7 @@ public function testRoundTripJCal(): void 'sequence', new \stdClass(), 'integer', 5, ], [ - 'freebusy', new \stdClass(), 'period', ['2013-05-26T21:02:13', 'PT1H'], ['2013-06-26T12:00:00', '2013-06-26T13:00:00'], + 'freebusy', new \stdClass(), 'period', ['2013-05-26T21:02:13Z', 'PT1H'], ['2013-06-26T12:00:00Z', '2013-06-26T13:00:00Z'], ], [ 'url', new \stdClass(), 'uri', 'http://example.org/', diff --git a/tests/VObject/Parser/XmlTest.php b/tests/VObject/Parser/XmlTest.php index 6491d087b..f8b37f5c4 100644 --- a/tests/VObject/Parser/XmlTest.php +++ b/tests/VObject/Parser/XmlTest.php @@ -684,7 +684,7 @@ public function testRFC6321Section3Part6Part9(): void - 2011-05-17T12:00:00 + 2011-05-17T12:00:00Z P1H @@ -693,7 +693,7 @@ public function testRFC6321Section3Part6Part9(): void XML, 'BEGIN:VCALENDAR'."\n". - 'FREEBUSY:20110517T120000/P1H'."\n". + 'FREEBUSY:20110517T120000Z/P1H'."\n". 'END:VCALENDAR'."\n" ); @@ -705,8 +705,8 @@ public function testRFC6321Section3Part6Part9(): void - 2011-05-17T12:00:00 - 2012-05-17T12:00:00 + 2011-05-17T12:00:00Z + 2012-05-17T12:00:00Z @@ -714,7 +714,7 @@ public function testRFC6321Section3Part6Part9(): void XML, 'BEGIN:VCALENDAR'."\n". - 'FREEBUSY:20110517T120000/20120517T120000'."\n". + 'FREEBUSY:20110517T120000Z/20120517T120000Z'."\n". 'END:VCALENDAR'."\n" ); } From 3ce3a836f8d122a6014fec2df5708a8d30fc388e Mon Sep 17 00:00:00 2001 From: Renaud BOYER Date: Wed, 25 Apr 2018 10:42:16 +0200 Subject: [PATCH 2/2] linagora/lgs/openpaas/linagora.esn.calendar#1207: Fix PERIOD element json serialization --- lib/Property/ICalendar/Period.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/Property/ICalendar/Period.php b/lib/Property/ICalendar/Period.php index ae4a5572a..c27da35c9 100644 --- a/lib/Property/ICalendar/Period.php +++ b/lib/Property/ICalendar/Period.php @@ -72,6 +72,10 @@ function ($item) { parent::setJsonValue($value); } + function appendUtc($strDate) { + return strpos($strDate, 'Z') === false ? '' : 'Z'; + } + /** * Returns the value, in the format it should be encoded for json. * @@ -85,23 +89,19 @@ public function getJsonValue(): array foreach ($this->getParts() as $item) { list($start, $end) = explode('/', $item, 2); - $start = DateTimeParser::parseDateTime($start); - - // Check if the datetime is in UTC timezone - $isUtc = in_array($start->getTimezone()->getName(), ['UTC', 'GMT', 'Z', '+00:00']); - $zSuffix = $isUtc ? 'Z' : ''; - // This is a duration value. - if ('P' === $end[0]) { + $startDt = DateTimeParser::parseDateTime($start)->format('Y-m-d\\TH:i:s') . $this->appendUtc($start); + + if ($end[0] === 'P') { $return[] = [ - $start->format('Y-m-d\\TH:i:s').$zSuffix, - $end, + $startDt, + $end ]; } else { - $end = DateTimeParser::parseDateTime($end); + $endDt = DateTimeParser::parseDateTime($end)->format('Y-m-d\\TH:i:s') . $this->appendUtc($end); $return[] = [ - $start->format('Y-m-d\\TH:i:s').$zSuffix, - $end->format('Y-m-d\\TH:i:s').$zSuffix, + $startDt, + $endDt ]; } }