value => [3, 2, 2, 2, 2], WorkloadPlacement::Middle->value => [2, 2, 3, 2, 2], WorkloadPlacement::End->value => [2, 2, 2, 2, 3], ]; foreach (WorkloadPlacement::cases() as $placement) { $dates = $scheduler->scheduledDates( assignmentCount: 11, startDate: $this->utc('2026-08-10'), targetDate: $this->utc('2026-08-14'), workloadPlacement: $placement, ); $this->assertSame( $expectedCounts[$placement->value], $this->dailyCounts($dates), ); } } public function test_it_keeps_multiple_heavier_days_together(): void { $scheduler = new EvenDistributionScheduler; $expectedCounts = [ WorkloadPlacement::Start->value => [3, 3, 2, 2, 2], WorkloadPlacement::Middle->value => [2, 3, 3, 2, 2], WorkloadPlacement::End->value => [2, 2, 2, 3, 3], ]; foreach (WorkloadPlacement::cases() as $placement) { $dates = $scheduler->scheduledDates( assignmentCount: 12, startDate: $this->utc('2026-08-10'), targetDate: $this->utc('2026-08-14'), workloadPlacement: $placement, ); $this->assertSame( $expectedCounts[$placement->value], $this->dailyCounts($dates), ); } } public function test_it_places_sparse_assignments_at_the_requested_position(): void { $scheduler = new EvenDistributionScheduler; $expectedDates = [ WorkloadPlacement::Spread->value => [ '2026-08-10', '2026-08-12', '2026-08-13', '2026-08-15', ], WorkloadPlacement::Start->value => [ '2026-08-10', '2026-08-11', '2026-08-12', '2026-08-13', ], WorkloadPlacement::Middle->value => [ '2026-08-11', '2026-08-12', '2026-08-13', '2026-08-14', ], WorkloadPlacement::End->value => [ '2026-08-12', '2026-08-13', '2026-08-14', '2026-08-15', ], ]; foreach (WorkloadPlacement::cases() as $placement) { $dates = $scheduler->scheduledDates( assignmentCount: 4, startDate: $this->utc('2026-08-10'), targetDate: $this->utc('2026-08-15'), workloadPlacement: $placement, ); $this->assertSame( $expectedDates[$placement->value], $this->formattedDates($dates), ); } } public function test_it_places_one_assignment_at_the_requested_position(): void { $scheduler = new EvenDistributionScheduler; $expectedDates = [ WorkloadPlacement::Spread->value => '2026-08-10', WorkloadPlacement::Start->value => '2026-08-10', WorkloadPlacement::Middle->value => '2026-08-12', WorkloadPlacement::End->value => '2026-08-15', ]; foreach (WorkloadPlacement::cases() as $placement) { $dates = $scheduler->scheduledDates( assignmentCount: 1, startDate: $this->utc('2026-08-10'), targetDate: $this->utc('2026-08-15'), workloadPlacement: $placement, ); $this->assertSame( [$expectedDates[$placement->value]], $this->formattedDates($dates), ); } } public function test_it_distributes_even_and_single_day_schedules(): void { $scheduler = new EvenDistributionScheduler; $evenDates = $scheduler->scheduledDates( assignmentCount: 10, startDate: $this->utc('2026-08-10'), targetDate: $this->utc('2026-08-14'), workloadPlacement: WorkloadPlacement::End, ); $singleDayDates = $scheduler->scheduledDates( assignmentCount: 4, startDate: $this->utc('2026-08-10'), targetDate: $this->utc('2026-08-10'), workloadPlacement: WorkloadPlacement::Middle, ); $this->assertSame([2, 2, 2, 2, 2], $this->dailyCounts($evenDates)); $this->assertSame( [ '2026-08-10', '2026-08-10', '2026-08-10', '2026-08-10', ], $this->formattedDates($singleDayDates), ); } /** * @param list $dates * @return list */ private function dailyCounts(array $dates): array { return array_values(array_count_values($this->formattedDates($dates))); } /** * @param list $dates * @return list */ private function formattedDates(array $dates): array { return array_map(function (DateTimeImmutable $date): string { return $date->format('Y-m-d'); }, $dates); } private function utc(string $date): DateTimeImmutable { return new DateTimeImmutable($date, new DateTimeZone('UTC')); } }