$pairs */ private function set(array $pairs): array { return array_map(static fn (array $p): TimeInterval => new TimeInterval($p[0], $p[1]), $pairs); } /** @return list */ private function flatten(array $intervals): array { return array_map(static fn (TimeInterval $i): array => [$i->start, $i->end], $intervals); } public function testEndMustBeAfterStart(): void { $this->expectException(\InvalidArgumentException::class); new TimeInterval(600, 600); } public function testMergeJoinsOverlappingAndTouchingIntervals(): void { $merged = TimeInterval::mergeAll($this->set([[540, 780], [700, 900], [900, 960], [1200, 1260]])); self::assertSame([[540, 960], [1200, 1260]], $this->flatten($merged)); } public function testMergeSortsUnorderedInput(): void { $merged = TimeInterval::mergeAll($this->set([[1200, 1260], [540, 600]])); self::assertSame([[540, 600], [1200, 1260]], $this->flatten($merged)); } public function testIntersectKeepsOnlyTheCommonPart(): void { $result = TimeInterval::intersectAll( $this->set([[540, 1020]]), $this->set([[600, 720]]), ); self::assertSame([[600, 720]], $this->flatten($result)); } /** بازه‌های صرفاً چسبیده تقاطع ندارند — نیم‌باز بودن دقیقاً همین را می‌گوید. */ public function testTouchingIntervalsDoNotIntersect(): void { $result = TimeInterval::intersectAll($this->set([[540, 780]]), $this->set([[780, 900]])); self::assertSame([], $result); } public function testIntersectOfDisjointSetsIsEmpty(): void { $result = TimeInterval::intersectAll($this->set([[540, 600]]), $this->set([[1080, 1200]])); self::assertSame([], $result); } /** بریدن از وسط، دو تکه می‌سازد و بقیهٔ روز سرِ جایش می‌ماند. */ public function testSubtractSplitsAnIntervalInTwo(): void { $result = TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[600, 720]])); self::assertSame([[540, 600], [720, 1020]], $this->flatten($result)); } public function testSubtractTrimsTheEdges(): void { self::assertSame( [[600, 1020]], $this->flatten(TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[500, 600]]))), ); self::assertSame( [[540, 900]], $this->flatten(TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[900, 1100]]))), ); } public function testSubtractCanEmptyTheSet(): void { self::assertSame([], TimeInterval::subtractAll($this->set([[540, 1020]]), $this->set([[400, 1200]]))); } /** دو مسدودکنندهٔ هم‌پوشان اول اتحاد می‌شوند، بعد کسر — نه کسر پشت‌سرهم. */ public function testOverlappingBlocksAreUnionedBeforeSubtracting(): void { $result = TimeInterval::subtractAll( $this->set([[540, 1020]]), $this->set([[600, 780], [720, 900]]), ); self::assertSame([[540, 600], [900, 1020]], $this->flatten($result)); } public function testSubtractingNothingReturnsTheInputMerged(): void { self::assertSame( [[540, 1020]], $this->flatten(TimeInterval::subtractAll($this->set([[540, 780], [780, 1020]]), [])), ); } /** تبدیل «دقیقه از نیمه‌شب» به timestamp — ضرب در ۶۰ باید صریح بماند. */ public function testMinutesToAbsoluteMultipliesBySixty(): void { $absolute = (new TimeInterval(540, 1020))->minutesToAbsolute(1_000_000); self::assertSame(1_000_000 + 540 * 60, $absolute->start); self::assertSame(1_000_000 + 1020 * 60, $absolute->end); self::assertSame(480 * 60, $absolute->end - $absolute->start); } }