From b9115a7f7ca0ec2dd01b2f94a6d854f2c58793f7 Mon Sep 17 00:00:00 2001 From: Nico Oelgart Date: Sat, 13 Apr 2024 12:57:10 +0200 Subject: [PATCH] Add average round duration (#17) * Guess end time base on YouTube's start time if times mismatch * Guess end time based on the average of past events --- bin/avg-round-duration | 54 ++++++ config/services/domain.yml | 4 + src/Domain/Round/IFSCAverageRoundDuration.php | 77 +++++++++ src/Domain/Round/IFSCRoundFactory.php | 52 ++++-- src/Domain/Schedule/IFSCSchedule.php | 2 +- src/Domain/Schedule/IFSCScheduleFactory.php | 2 +- src/Domain/Tags/IFSCParsedTags.php | 2 +- .../YouTube/YouTubeLiveStreamFinder.php | 2 +- .../YouTubeLiveStreamFinderInterface.php | 16 ++ .../Schedule/InfoSheetScheduleProvider.php | 6 +- .../Domain/Round/IFSCRoundFactoryTest.php | 156 ++++++++++++++++++ .../Schedule/PDFScheduleProviderTest.php | 54 +++--- 12 files changed, 379 insertions(+), 48 deletions(-) create mode 100755 bin/avg-round-duration create mode 100644 src/Domain/Round/IFSCAverageRoundDuration.php create mode 100644 src/Domain/YouTube/YouTubeLiveStreamFinderInterface.php create mode 100644 tests/unit/Domain/Round/IFSCRoundFactoryTest.php diff --git a/bin/avg-round-duration b/bin/avg-round-duration new file mode 100755 index 0000000..6cd9da9 --- /dev/null +++ b/bin/avg-round-duration @@ -0,0 +1,54 @@ +#!/usr/bin/env php +name; + } + + sort($key); + + return strtolower(implode('_', $key)); +} + +$videoDurations = []; + +foreach ($videos as $video) { + $tags = $tagsParser->fromString($video->title); + + if (!$tags->hasTag(Tag::WOMEN) && !$tags->hasTag(Tag::MEN)) { + $videoTags = [Tag::WOMEN, Tag::MEN, ...$tags->allTags()]; + } else { + $videoTags = $tags->allTags(); + } + + $videoDurations[cache_key($videoTags)][] = $video->duration; +} + +$results = []; + +foreach ($videoDurations as $key => $minutes) { + $averageDuration = (int) floor(array_sum($minutes) / count($minutes)); + $averageDuration -= ($averageDuration % 30); + + if ($averageDuration > 30) { + $results[$key] = $averageDuration; + } +} + +ksort($results); +var_export($results); diff --git a/config/services/domain.yml b/config/services/domain.yml index 67ea8f7..49cfa71 100644 --- a/config/services/domain.yml +++ b/config/services/domain.yml @@ -100,11 +100,15 @@ services: - '@nicoSWD\IfscCalendar\Domain\Round\IFSCRoundFactory' - '@nicoSWD\IfscCalendar\Infrastructure\Round\InfoSheetRoundProvider' + nicoSWD\IfscCalendar\Domain\Round\IFSCAverageRoundDuration: + class: nicoSWD\IfscCalendar\Domain\Round\IFSCAverageRoundDuration + nicoSWD\IfscCalendar\Domain\Round\IFSCRoundFactory: class: nicoSWD\IfscCalendar\Domain\Round\IFSCRoundFactory arguments: - '@nicoSWD\IfscCalendar\Domain\Tags\IFSCTagsParser' - '@nicoSWD\IfscCalendar\Domain\YouTube\YouTubeLiveStreamFinder' + - '@nicoSWD\IfscCalendar\Domain\Round\IFSCAverageRoundDuration' nicoSWD\IfscCalendar\Domain\Round\IFSCRoundNameNormalizer: class: nicoSWD\IfscCalendar\Domain\Round\IFSCRoundNameNormalizer diff --git a/src/Domain/Round/IFSCAverageRoundDuration.php b/src/Domain/Round/IFSCAverageRoundDuration.php new file mode 100644 index 0000000..324125c --- /dev/null +++ b/src/Domain/Round/IFSCAverageRoundDuration.php @@ -0,0 +1,77 @@ + + */ +namespace nicoSWD\IfscCalendar\Domain\Round; + +use nicoSWD\IfscCalendar\Domain\Event\IFSCEventTagsRegex as Tag; + +final readonly class IFSCAverageRoundDuration +{ + private const int DEFAULT_ROUND_DURATION = 90; + + // Run `bin/avg-round-duration` to generate an updated list + private const array AVERAGE_DURATIONS = [ + 'boulder_combined_men_qualification' => 120, + 'boulder_combined_men_qualification_women' => 120, + 'boulder_combined_qualification_women' => 120, + 'boulder_final_lead_men' => 180, + 'boulder_final_lead_men_semi_final_women' => 360, + 'boulder_final_lead_men_women' => 240, + 'boulder_final_lead_women' => 180, + 'boulder_final_men' => 120, + 'boulder_final_men_paraclimbing' => 150, + 'boulder_final_men_women' => 180, + 'boulder_final_women' => 120, + 'boulder_lead_men_semi_final' => 180, + 'boulder_lead_men_semi_final_women' => 120, + 'boulder_lead_semi_final_women' => 90, + 'boulder_men_semi_final' => 120, + 'boulder_men_semi_final_women' => 120, + 'boulder_semi_final_women' => 120, + 'combined_final_men' => 240, + 'combined_final_men_women' => 420, + 'combined_final_women' => 240, + 'combined_lead_men_qualification' => 90, + 'combined_lead_men_qualification_women' => 90, + 'combined_lead_qualification_women' => 120, + 'combined_men_qualification_speed_women' => 60, + 'final_lead_men' => 60, + 'final_lead_men_women' => 120, + 'final_lead_women' => 120, + 'final_men_paraclimbing_women' => 210, + 'final_men_speed_women' => 90, + 'final_speed_women' => 60, + 'lead_men_semi_final' => 120, + 'lead_men_semi_final_women' => 150, + 'lead_semi_final_women' => 210, + 'men_paraclimbing_women' => 120, + 'men_qualification_speed' => 60, + 'men_qualification_speed_women' => 90, + ]; + + /** @param Tag[] $tags */ + public function fromTags(array $tags): int + { + return self::AVERAGE_DURATIONS[$this->lookupKey($tags)] + ?? self::DEFAULT_ROUND_DURATION; + } + + /** @param Tag[] $tags */ + private function lookupKey(array $tags): string + { + $tags = array_map( + static fn (Tag $tag): string => $tag->name, + $tags, + ); + + sort($tags); + + return strtolower( + implode('_', $tags), + ); + } +} diff --git a/src/Domain/Round/IFSCRoundFactory.php b/src/Domain/Round/IFSCRoundFactory.php index 60a7bbe..6e999db 100644 --- a/src/Domain/Round/IFSCRoundFactory.php +++ b/src/Domain/Round/IFSCRoundFactory.php @@ -14,15 +14,14 @@ use nicoSWD\IfscCalendar\Domain\Stream\LiveStream; use nicoSWD\IfscCalendar\Domain\Tags\IFSCParsedTags; use nicoSWD\IfscCalendar\Domain\Tags\IFSCTagsParser; -use nicoSWD\IfscCalendar\Domain\YouTube\YouTubeLiveStreamFinder; +use nicoSWD\IfscCalendar\Domain\YouTube\YouTubeLiveStreamFinderInterface; final readonly class IFSCRoundFactory { - private const int DEFAULT_ROUND_DURATION = 90; - public function __construct( private IFSCTagsParser $tagsParser, - private YouTubeLiveStreamFinder $liveStreamFinder, + private YouTubeLiveStreamFinderInterface $liveStreamFinder, + private IFSCAverageRoundDuration $averageRoundDuration, ) { } @@ -30,18 +29,35 @@ public function create( IFSCEventInfo $event, string $roundName, DateTimeImmutable $startTime, - DateTimeImmutable $endTime, + ?DateTimeImmutable $endTime, IFSCRoundStatus $status, ): IFSCRound { $tags = $this->getTags($roundName); $liveStream = $this->findLiveStream($event, $roundName); if ($liveStream->scheduledStartTime) { - $startTime = $this->buildStartTime($liveStream, $event); - $endTime = $this->buildEndTime($startTime, $liveStream); + $youTubeStartTime = $this->buildStartTime($liveStream, $event); + + if ($endTime && !$this->startTimeMatchesYouTubes($startTime, $liveStream)) { + $diff = $startTime->diff($endTime); + $endTime = $youTubeStartTime->add($diff); + } + + $startTime = $youTubeStartTime; + + if ($liveStream->duration > 0) { + $endTime = $startTime->modify( + sprintf('+%d minutes', $liveStream->duration), + ); + } + $status = IFSCRoundStatus::CONFIRMED; } + if (!$endTime) { + $endTime = $this->calcEndTime($startTime, $tags); + } + return new IFSCRound( name: $roundName, categories: $tags->getCategories(), @@ -80,16 +96,24 @@ private function findLiveStream(IFSCEventInfo $event, string $roundName): LiveSt return $this->liveStreamFinder->findLiveStream($event, $roundName); } - private function buildEndTime(DateTimeImmutable $startTime, LiveStream $liveStream): DateTimeImmutable + private function averageRoundDuration(IFSCParsedTags $tags): int { - if ($liveStream->duration > 0) { - $duration = $liveStream->duration; - } else { - $duration = self::DEFAULT_ROUND_DURATION; - } + return $this->averageRoundDuration->fromTags($tags->allTags()); + } + + private function startTimeMatchesYouTubes(DateTimeImmutable $startTime, LiveStream $liveStream): bool + { + $diff = $startTime->diff($liveStream->scheduledStartTime); + return + $diff->i <= 5 && + ($diff->y + $diff->m + $diff->d + $diff->h) === 0; + } + + private function calcEndTime(DateTimeImmutable $startTime, IFSCParsedTags $tags): DateTimeImmutable + { return $startTime->modify( - sprintf('+%d minutes', $duration) + sprintf('+%d minutes', $this->averageRoundDuration($tags)), ); } } diff --git a/src/Domain/Schedule/IFSCSchedule.php b/src/Domain/Schedule/IFSCSchedule.php index 5a0dc73..55fc3e5 100644 --- a/src/Domain/Schedule/IFSCSchedule.php +++ b/src/Domain/Schedule/IFSCSchedule.php @@ -14,7 +14,7 @@ public function __construct( public string $name, public DateTimeImmutable $startsAt, - public DateTimeImmutable $endsAt, + public ?DateTimeImmutable $endsAt, public bool $isPreRound, ) { } diff --git a/src/Domain/Schedule/IFSCScheduleFactory.php b/src/Domain/Schedule/IFSCScheduleFactory.php index 72e0c8b..35f869c 100644 --- a/src/Domain/Schedule/IFSCScheduleFactory.php +++ b/src/Domain/Schedule/IFSCScheduleFactory.php @@ -23,7 +23,7 @@ public function __construct( public function create( string $name, DateTimeImmutable $startsAt, - DateTimeImmutable $endsAt, + ?DateTimeImmutable $endsAt, ): IFSCSchedule { $tags = $this->tagsParser->fromString($name); diff --git a/src/Domain/Tags/IFSCParsedTags.php b/src/Domain/Tags/IFSCParsedTags.php index 3e1d760..9af9e56 100644 --- a/src/Domain/Tags/IFSCParsedTags.php +++ b/src/Domain/Tags/IFSCParsedTags.php @@ -98,7 +98,7 @@ public function isPreRound(): bool return $this->hasTag(Tag::PRE_ROUND); } - private function hasTag(Tag $tag): bool + public function hasTag(Tag $tag): bool { return in_array($tag, $this->tags, strict: true); } diff --git a/src/Domain/YouTube/YouTubeLiveStreamFinder.php b/src/Domain/YouTube/YouTubeLiveStreamFinder.php index 7f44f77..f3354dc 100644 --- a/src/Domain/YouTube/YouTubeLiveStreamFinder.php +++ b/src/Domain/YouTube/YouTubeLiveStreamFinder.php @@ -10,7 +10,7 @@ use nicoSWD\IfscCalendar\Domain\Event\Info\IFSCEventInfo; use nicoSWD\IfscCalendar\Domain\Stream\LiveStream; -final readonly class YouTubeLiveStreamFinder +final readonly class YouTubeLiveStreamFinder implements YouTubeLiveStreamFinderInterface { public function __construct( private YouTubeLinkMatcher $linkMatcher, diff --git a/src/Domain/YouTube/YouTubeLiveStreamFinderInterface.php b/src/Domain/YouTube/YouTubeLiveStreamFinderInterface.php new file mode 100644 index 0000000..2f7d851 --- /dev/null +++ b/src/Domain/YouTube/YouTubeLiveStreamFinderInterface.php @@ -0,0 +1,16 @@ + + */ +namespace nicoSWD\IfscCalendar\Domain\YouTube; + +use nicoSWD\IfscCalendar\Domain\Event\Info\IFSCEventInfo; +use nicoSWD\IfscCalendar\Domain\Stream\LiveStream; + +interface YouTubeLiveStreamFinderInterface +{ + public function findLiveStream(IFSCEventInfo $event, string $roundName): LiveStream; +} diff --git a/src/Infrastructure/Schedule/InfoSheetScheduleProvider.php b/src/Infrastructure/Schedule/InfoSheetScheduleProvider.php index 3c5ed75..76870d6 100644 --- a/src/Infrastructure/Schedule/InfoSheetScheduleProvider.php +++ b/src/Infrastructure/Schedule/InfoSheetScheduleProvider.php @@ -80,7 +80,7 @@ private function parseDaySchedule(string $schedule, DateTimeZone $timeZone): Ite $startsAt = $this->createStartDate($dayName, $match['start_time'][$key], $timeZone); } - $endsAt = $this->createEndDate($dayName, $match['end_time'][$key] ?? null, $startsAt, $timeZone); + $endsAt = $this->createEndDate($dayName, $match['end_time'][$key] ?? null, $timeZone); $lastStart = $startsAt; $schedule = $this->scheduleFactory->create( @@ -113,13 +113,13 @@ private function createStartDate(string $day, string $time, DateTimeZone $timeZo ); } - private function createEndDate(string $dayName, ?string $time, DateTimeImmutable $startsAt, DateTimeZone $timeZone): DateTimeImmutable + private function createEndDate(string $dayName, ?string $time, DateTimeZone $timeZone): ?DateTimeImmutable { if ($time !== null && trim($time) !== '') { return $this->createStartDate($dayName, $time, $timeZone); } - return $startsAt->modify('+2 hours'); + return null; } /** @return string[] */ diff --git a/tests/unit/Domain/Round/IFSCRoundFactoryTest.php b/tests/unit/Domain/Round/IFSCRoundFactoryTest.php new file mode 100644 index 0000000..d0c29c7 --- /dev/null +++ b/tests/unit/Domain/Round/IFSCRoundFactoryTest.php @@ -0,0 +1,156 @@ + + */ +namespace nicoSWD\IfscCalendar\tests\Domain\Round; + +use DateTimeImmutable; +use DateTimeInterface; +use DateTimeZone; +use nicoSWD\IfscCalendar\Domain\Event\Info\IFSCEventInfo; +use nicoSWD\IfscCalendar\Domain\Round\IFSCAverageRoundDuration; +use nicoSWD\IfscCalendar\Domain\Round\IFSCRoundFactory; +use nicoSWD\IfscCalendar\Domain\Round\IFSCRoundStatus; +use nicoSWD\IfscCalendar\Domain\Stream\LiveStream; +use nicoSWD\IfscCalendar\Domain\Tags\IFSCTagsParser; +use nicoSWD\IfscCalendar\Domain\YouTube\YouTubeLiveStreamFinderInterface; +use Override; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; + +final class IFSCRoundFactoryTest extends TestCase +{ + #[Test] public function finished_live_stream_start_and_end_time_is_used(): void + { + $roundFactory = $this->roundFactoryReturningLiveStreamWith( + scheduledStartTime: '2024-04-12T10:55:00Z', + duration: 60, + ); + + $round = $roundFactory->create( + event: $this->createEvent(), + roundName: "Men's & Women's Lead Qualification", + startTime: $this->createDateTime('2024-04-12 18:30'), + endTime: $this->createDateTime('2024-04-12 21:23'), + status: IFSCRoundStatus::PROVISIONAL, + ); + + $this->assertSame(IFSCRoundStatus::CONFIRMED, $round->status); + $this->assertSame('2024-04-12T19:00:00+08:00', $this->formatDate($round->startTime)); + $this->assertSame('2024-04-12T20:00:00+08:00', $this->formatDate($round->endTime)); + } + + #[Test] public function average_round_duration_is_used_when_not_streamed_yet(): void + { + $roundFactory = $this->roundFactoryReturningLiveStreamWith( + scheduledStartTime: '2024-04-12T10:55:00Z', + duration: 0, + ); + + $round = $roundFactory->create( + event: $this->createEvent(), + roundName: "Men's & Women's Lead Qualification", + startTime: $this->createDateTime('2024-04-12 18:30'), + endTime: null, + status: IFSCRoundStatus::PROVISIONAL, + ); + + $this->assertSame(IFSCRoundStatus::CONFIRMED, $round->status); + $this->assertSame('2024-04-12T19:00:00+08:00', $this->formatDate($round->startTime)); + $this->assertSame('2024-04-12T20:30:00+08:00', $this->formatDate($round->endTime)); + } + + #[Test] public function round_schedule_is_not_confirmed_unless_a_live_stream_was_found(): void + { + $roundFactory = $this->roundFactoryReturningLiveStreamWith( + scheduledStartTime: null, + duration: 0, + ); + + $round = $roundFactory->create( + event: $this->createEvent(), + roundName: "Men's & Women's Lead Qualification", + startTime: $this->createDateTime('2024-04-12 18:30'), + endTime: null, + status: IFSCRoundStatus::PROVISIONAL, + ); + + $this->assertSame(IFSCRoundStatus::PROVISIONAL, $round->status); + $this->assertSame('2024-04-13T02:30:00+08:00', $this->formatDate($round->startTime)); + $this->assertSame('2024-04-13T04:00:00+08:00', $this->formatDate($round->endTime)); + } + + #[Test] public function end_time_is_guessed_based_on_youtube_if_start_dates_mismatch(): void + { + $roundFactory = $this->roundFactoryReturningLiveStreamWith( + scheduledStartTime: '2024-04-12T17:55:00+08:00', + duration: 0, + ); + + $round = $roundFactory->create( + event: $this->createEvent(), + roundName: "Men's & Women's Lead Qualification", + startTime: $this->createDateTime('2024-04-12T19:00:00+08:00'), + endTime: $this->createDateTime('2024-04-12T20:00:00+08:00'), + status: IFSCRoundStatus::PROVISIONAL, + ); + + $this->assertSame(IFSCRoundStatus::CONFIRMED, $round->status); + $this->assertSame('2024-04-12T18:00:00+08:00', $this->formatDate($round->startTime)); + $this->assertSame('2024-04-12T19:00:00+08:00', $this->formatDate($round->endTime)); + } + + private function roundFactoryReturningLiveStreamWith(?string $scheduledStartTime, int $duration): IFSCRoundFactory + { + return new IFSCRoundFactory( + new IFSCTagsParser(), + new readonly class ($scheduledStartTime, $duration) implements YouTubeLiveStreamFinderInterface { + public function __construct( + private ?string $scheduledStartTime, + private int $duration, + ) { + } + + #[Override] public function findLiveStream(IFSCEventInfo $event, string $roundName): LiveStream + { + return new LiveStream( + scheduledStartTime: $this->scheduledStartTime ? new DateTimeImmutable($this->scheduledStartTime) : null, + duration: $this->duration, + ); + } + }, + new IFSCAverageRoundDuration(), + ); + } + + private function createEvent(): IFSCEventInfo + { + return new IFSCEventInfo( + eventId: 1292, + eventName: 'IFSC World Cup Salt Lake City 2024', + leagueId: 37, + leagueName: 'World Cups and World Championships', + leagueSeasonId: 12, + localStartDate: '2023-04-10T11:55:00Z', + localEndDate: '2023-04-10T13:55:00Z', + timeZone: new DateTimeZone('Asia/Shanghai'), + location: 'Barcelona', + country: 'ES', + disciplines: [], + categories: [], + ); + } + + private function formatDate(DateTimeImmutable $dateTime): string + { + return $dateTime->format(DateTimeInterface::RFC3339); + } + + private function createDateTime(string $dateTime): DateTimeImmutable + { + return (new DateTimeImmutable($dateTime))->setTimezone(new DateTimeZone('Asia/Shanghai')); + } +} diff --git a/tests/unit/Infrastructure/Schedule/PDFScheduleProviderTest.php b/tests/unit/Infrastructure/Schedule/PDFScheduleProviderTest.php index 630a67d..f51073d 100644 --- a/tests/unit/Infrastructure/Schedule/PDFScheduleProviderTest.php +++ b/tests/unit/Infrastructure/Schedule/PDFScheduleProviderTest.php @@ -30,27 +30,27 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Women's Boulder Qualification", $schedule[0]->name); $this->assertSameDate("2024-04-08T09:00:00+08:00", $schedule[0]->startsAt); - $this->assertSameDate("2024-04-08T11:00:00+08:00", $schedule[0]->endsAt); + $this->assertNull($schedule[0]->endsAt); $this->assertSame("Men's Boulder Qualification", $schedule[1]->name); $this->assertSameDate("2024-04-08T16:00:00+08:00", $schedule[1]->startsAt); - $this->assertSameDate("2024-04-08T18:00:00+08:00", $schedule[1]->endsAt); + $this->assertNull($schedule[1]->endsAt); $this->assertSame("Women's Boulder Semi-Final", $schedule[2]->name); $this->assertSameDate("2024-04-09T12:00:00+08:00", $schedule[2]->startsAt); - $this->assertSameDate("2024-04-09T14:00:00+08:00", $schedule[2]->endsAt); + $this->assertNull($schedule[2]->endsAt); $this->assertSame("Women's Boulder Final", $schedule[3]->name); $this->assertSameDate("2024-04-09T19:00:00+08:00", $schedule[3]->startsAt); - $this->assertSameDate("2024-04-09T21:00:00+08:00", $schedule[3]->endsAt); + $this->assertNull($schedule[3]->endsAt); $this->assertSame("Men's Boulder Semi-Final", $schedule[4]->name); $this->assertSameDate("2024-04-10T12:00:00+08:00", $schedule[4]->startsAt); - $this->assertSameDate("2024-04-10T14:00:00+08:00", $schedule[4]->endsAt); + $this->assertNull($schedule[4]->endsAt); $this->assertSame("Men's Boulder Final", $schedule[5]->name); $this->assertSameDate("2024-04-10T19:00:00+08:00", $schedule[5]->startsAt); - $this->assertSameDate("2024-04-10T21:00:00+08:00", $schedule[5]->endsAt); + $this->assertNull($schedule[5]->endsAt); } #[Test] public function wujiang_schedule_is_found(): void @@ -65,23 +65,23 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Men's & Women's Speed Qualification", $schedule[1]->name); $this->assertSameDate("2024-04-12T19:00:00+08:00", $schedule[1]->startsAt); - $this->assertSameDate("2024-04-12T21:00:00+08:00", $schedule[1]->endsAt); + $this->assertNull($schedule[1]->endsAt); $this->assertSame("Men's & Women's Lead Semi-Final", $schedule[2]->name); $this->assertSameDate("2024-04-13T15:00:00+08:00", $schedule[2]->startsAt); - $this->assertSameDate("2024-04-13T17:00:00+08:00", $schedule[2]->endsAt); + $this->assertNull($schedule[2]->endsAt); $this->assertSame("Men's & Women's Speed Final", $schedule[3]->name); $this->assertSameDate("2024-04-13T19:30:00+08:00", $schedule[3]->startsAt); - $this->assertSameDate("2024-04-13T21:30:00+08:00", $schedule[3]->endsAt); + $this->assertNull($schedule[3]->endsAt); $this->assertSame("Women's Lead Final", $schedule[4]->name); $this->assertSameDate("2024-04-14T19:00:00+08:00", $schedule[4]->startsAt); - $this->assertSameDate("2024-04-14T21:00:00+08:00", $schedule[4]->endsAt); + $this->assertNull($schedule[4]->endsAt); $this->assertSame("Men's Lead Final", $schedule[5]->name); $this->assertSameDate("2024-04-14T20:00:00+08:00", $schedule[5]->startsAt); - $this->assertSameDate("2024-04-14T22:00:00+08:00", $schedule[5]->endsAt); + $this->assertNull($schedule[5]->endsAt); } #[Test] public function salt_lake_city_schedule_is_found(): void @@ -108,11 +108,11 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Men's Boulder Final", $schedule[4]->name); $this->assertSameDate("2024-05-04T18:00:00-07:00", $schedule[4]->startsAt); - $this->assertSameDate("2024-05-04T20:00:00-07:00", $schedule[4]->endsAt); + $this->assertNull($schedule[4]->endsAt); $this->assertSame("Women's Speed Final", $schedule[5]->name); $this->assertSameDate("2024-05-04T20:00:00-07:00", $schedule[5]->startsAt); - $this->assertSameDate("2024-05-04T22:00:00-07:00", $schedule[5]->endsAt); + $this->assertNull($schedule[5]->endsAt); $this->assertSame("Women's Boulder Semi-Final", $schedule[6]->name); $this->assertSameDate("2024-05-05T10:00:00-07:00", $schedule[6]->startsAt); @@ -124,11 +124,11 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Women's Boulder Final", $schedule[8]->name); $this->assertSameDate("2024-05-05T18:00:00-07:00", $schedule[8]->startsAt); - $this->assertSameDate("2024-05-05T20:00:00-07:00", $schedule[8]->endsAt); + $this->assertNull($schedule[8]->endsAt); $this->assertSame("Men's Speed Final", $schedule[9]->name); $this->assertSameDate("2024-05-05T20:00:00-07:00", $schedule[9]->startsAt); - $this->assertSameDate("2024-05-05T22:00:00-07:00", $schedule[9]->endsAt); + $this->assertNull($schedule[9]->endsAt); } #[Test] public function innsbruck_schedule_is_found(): void @@ -194,11 +194,11 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Men's Lead Final", $schedule[2]->name); $this->assertSameDate("2024-09-07T20:00:00+02:00", $schedule[2]->startsAt); - $this->assertSameDate("2024-09-07T22:00:00+02:00", $schedule[2]->endsAt); + $this->assertNull($schedule[2]->endsAt); $this->assertSame("Women's Lead Final", $schedule[3]->name); $this->assertSameDate("2024-09-07T21:00:00+02:00", $schedule[3]->startsAt); - $this->assertSameDate("2024-09-07T23:00:00+02:00", $schedule[3]->endsAt); + $this->assertNull($schedule[3]->endsAt); } #[Test] public function chamonix_schedule_is_found(): void @@ -209,7 +209,7 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Men's & Women's Speed Qualification", $schedule[0]->name); $this->assertSameDate("2024-07-12T18:45:00+02:00", $schedule[0]->startsAt); - $this->assertSameDate("2024-07-12T20:45:00+02:00", $schedule[0]->endsAt); + $this->assertNull($schedule[0]->endsAt); $this->assertSame("Men's & Women's Lead Qualification", $schedule[1]->name); $this->assertSameDate("2024-07-13T09:00:00+02:00", $schedule[1]->startsAt); @@ -217,7 +217,7 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Men's & Women's Speed Final", $schedule[2]->name); $this->assertSameDate("2024-07-13T21:00:00+02:00", $schedule[2]->startsAt); - $this->assertSameDate("2024-07-13T23:00:00+02:00", $schedule[2]->endsAt); + $this->assertNull($schedule[2]->endsAt); $this->assertSame("Men's & Women's Lead Semi-Final", $schedule[3]->name); $this->assertSameDate("2024-07-14T10:00:00+02:00", $schedule[3]->startsAt); @@ -225,11 +225,11 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Women's Lead Final", $schedule[4]->name); $this->assertSameDate("2024-07-14T20:30:00+02:00", $schedule[4]->startsAt); - $this->assertSameDate("2024-07-14T22:30:00+02:00", $schedule[4]->endsAt); + $this->assertNull($schedule[4]->endsAt); $this->assertSame("Men's Lead Final", $schedule[5]->name); $this->assertSameDate("2024-07-14T21:25:00+02:00", $schedule[5]->startsAt); - $this->assertSameDate("2024-07-14T23:25:00+02:00", $schedule[5]->endsAt); + $this->assertNull($schedule[5]->endsAt); } #[Test] public function prague_schedule_is_found(): void @@ -240,27 +240,27 @@ final class PDFScheduleProviderTest extends TestCase $this->assertSame("Men's Boulder Qualification", $schedule[0]->name); $this->assertSameDate("2024-09-20T09:00:00+02:00", $schedule[0]->startsAt); - $this->assertSameDate("2024-09-20T11:00:00+02:00", $schedule[0]->endsAt); + $this->assertNull($schedule[0]->endsAt); $this->assertSame("Women's Boulder Qualification", $schedule[1]->name); $this->assertSameDate("2024-09-20T16:00:00+02:00", $schedule[1]->startsAt); - $this->assertSameDate("2024-09-20T18:00:00+02:00", $schedule[1]->endsAt); + $this->assertNull($schedule[1]->endsAt); $this->assertSame("Men's Boulder Semi-Final", $schedule[2]->name); $this->assertSameDate("2024-09-21T12:00:00+02:00", $schedule[2]->startsAt); - $this->assertSameDate("2024-09-21T14:00:00+02:00", $schedule[2]->endsAt); + $this->assertNull($schedule[2]->endsAt); $this->assertSame("Men's Boulder Final", $schedule[3]->name); $this->assertSameDate("2024-09-21T20:00:00+02:00", $schedule[3]->startsAt); - $this->assertSameDate("2024-09-21T22:00:00+02:00", $schedule[3]->endsAt); + $this->assertNull($schedule[3]->endsAt); $this->assertSame("Women's Boulder Semi-Final", $schedule[4]->name); $this->assertSameDate("2024-09-22T12:00:00+02:00", $schedule[4]->startsAt); - $this->assertSameDate("2024-09-22T14:00:00+02:00", $schedule[4]->endsAt); + $this->assertNull($schedule[4]->endsAt); $this->assertSame("Women's Boulder Final", $schedule[5]->name); $this->assertSameDate("2024-09-22T19:00:00+02:00", $schedule[5]->startsAt); - $this->assertSameDate("2024-09-22T21:00:00+02:00", $schedule[5]->endsAt); + $this->assertNull($schedule[5]->endsAt); } private function assertSameDate(string $expected, DateTimeImmutable $actual): void