Skip to content

Commit 50ca8e5

Browse files
fix: handle UTC offset timezones
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
1 parent 01d1edc commit 50ca8e5

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

‎lib/TimezoneGuesser/FindFromOffset.php‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ public function find(string $tzid, ?bool $failIfUncertain = false): ?\DateTimeZo
2424
// @codeCoverageIgnoreEnd
2525
}
2626

27+
if (0 === strncasecmp($tzid, 'UTC', 3)) {
28+
$offset = substr($tzid, 3);
29+
if (6 === strlen($offset) && ':' === $offset[3]) {
30+
$offset = substr($offset, 0, 3).substr($offset, 4);
31+
}
32+
33+
if (5 !== strlen($offset)
34+
|| ('+' !== $offset[0] && '-' !== $offset[0])
35+
|| 4 !== strspn(substr($offset, 1), '0123456789')
36+
|| (int) substr($offset, 1, 2) > 23
37+
|| (int) substr($offset, 3, 2) > 59
38+
) {
39+
return null;
40+
}
41+
42+
return new \DateTimeZone($offset);
43+
}
44+
2745
return null;
2846
}
2947
}

‎tests/VObject/TimeZoneUtilTest.php‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,78 @@ public function testTimezoneOffset(): void
221221
self::assertEquals($ex->getName(), $tz->getName());
222222
}
223223

224+
/**
225+
* @dataProvider getValidUtcOffsets
226+
*/
227+
#[\PHPUnit\Framework\Attributes\DataProvider('getValidUtcOffsets')]
228+
public function testValidUtcOffset(string $tzid, string $expectedOffset): void
229+
{
230+
$tz = TimeZoneUtil::getTimeZone($tzid, null, true);
231+
232+
self::assertSame($expectedOffset, $tz->getName());
233+
}
234+
235+
public static function getValidUtcOffsets(): array
236+
{
237+
return [
238+
['UTC-04:00', '-04:00'],
239+
['UTC-0400', '-04:00'],
240+
['UTC+05:30', '+05:30'],
241+
['UTC+0545', '+05:45'],
242+
['utc-03:30', '-03:30'],
243+
['UTC+00:00', '+00:00'],
244+
['UTC-00:00', '+00:00'],
245+
['UTC+23:59', '+23:59'],
246+
];
247+
}
248+
249+
/**
250+
* @dataProvider getInvalidUtcOffsets
251+
*/
252+
#[\PHPUnit\Framework\Attributes\DataProvider('getInvalidUtcOffsets')]
253+
public function testInvalidUtcOffset(string $tzid): void
254+
{
255+
self::assertSame(date_default_timezone_get(), TimeZoneUtil::getTimeZone($tzid)->getName());
256+
257+
$this->expectException(\InvalidArgumentException::class);
258+
TimeZoneUtil::getTimeZone($tzid, null, true);
259+
}
260+
261+
public static function getInvalidUtcOffsets(): array
262+
{
263+
return [
264+
['UTC-24:00'],
265+
['UTC+04:60'],
266+
['UTC+4:00'],
267+
['UTC+04'],
268+
['UTC04:00'],
269+
['UTC+ab:cd'],
270+
['UTC+04:00extra'],
271+
["UTC+04:00\n"],
272+
['UTC+04::00'],
273+
['UTC+'],
274+
];
275+
}
276+
277+
public function testParseUtcOffsetDateTime(): void
278+
{
279+
$calendar = Reader::read(<<<ICS
280+
BEGIN:VCALENDAR
281+
VERSION:2.0
282+
BEGIN:VEVENT
283+
UID:fixed-offset
284+
DTSTART;TZID="UTC-04:00":20260728T111500
285+
END:VEVENT
286+
END:VCALENDAR
287+
ICS
288+
);
289+
290+
$start = $calendar->VEVENT->DTSTART->getDateTime();
291+
292+
self::assertSame('2026-07-28 11:15:00 -04:00', $start->format('Y-m-d H:i:s P'));
293+
self::assertSame((new \DateTimeImmutable('2026-07-28T15:15:00Z'))->getTimestamp(), $start->getTimestamp());
294+
}
295+
224296
public function testTimezoneFail(): void
225297
{
226298
$this->expectException(\InvalidArgumentException::class);

0 commit comments

Comments
 (0)