calculator = new AbsenceDayCalculator(new PublicHolidayProvider()); } public function testFullWeekIsFiveWorkingDays(): void { // Mon 2026-06-01 to Fri 2026-06-05, no holidays that week self::assertSame(5.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-05'), )); } public function testWeekendIsSkipped(): void { // Fri 2026-06-05 to Mon 2026-06-08 => Fri + Mon = 2 self::assertSame(2.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-05'), new DateTimeImmutable('2026-06-08'), )); } public function testHolidayIsSkipped(): void { // Thu 2026-05-07 to Fri 2026-05-08 (Victoire 1945) => Thu only = 1 self::assertSame(1.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-05-07'), new DateTimeImmutable('2026-05-08'), )); } public function testHalfDayStartSubtractsHalf(): void { self::assertSame(4.5, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-05'), HalfDay::Afternoon, )); } public function testBothHalfDaysSubtractOne(): void { self::assertSame(4.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-05'), HalfDay::Afternoon, HalfDay::Morning, )); } public function testSingleFullDayCountsOne(): void { // Most common request: a single full working day = 1.0 (no half-day). self::assertSame(1.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-01'), )); } public function testSingleHalfDay(): void { self::assertSame(0.5, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-01'), HalfDay::Morning, )); } public function testHalfDayEndOnlySubtractsHalf(): void { // Mirror of testHalfDayStartSubtractsHalf: end half-day only, on a // counted multi-day range => 5 - 0.5 = 4.5. self::assertSame(4.5, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-05'), null, HalfDay::Morning, )); } public function testEndBeforeStartCountsZero(): void { // Inverted range guard (end < start) => 0, never negative. self::assertSame(0.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-05'), new DateTimeImmutable('2026-06-01'), )); } public function testSingleDayOnWeekendCountsZero(): void { // Saturday, jours ouvrés: not counted, even with a half-day => 0. self::assertSame(0.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-06'), new DateTimeImmutable('2026-06-06'), HalfDay::Morning, HalfDay::Morning, )); } public function testSingleDayOnHolidayCountsZero(): void { // Fri 2026-05-08 (Victoire 1945): public holiday => 0. self::assertSame(0.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-05-08'), new DateTimeImmutable('2026-05-08'), )); } public function testHalfDayOnSaturdayCountedInOpenDays(): void { // Fri + Sat in "ouvrables" mode = 2; the end half-day sits on Saturday, // which IS counted here => 2 - 0.5 = 1.5. self::assertSame(1.5, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-05'), new DateTimeImmutable('2026-06-06'), null, HalfDay::Morning, false, )); } public function testSingleDayWithBothHalfDaysIsStillHalf(): void { // Real-world single-day request: the form mirrors the start half-day // onto the end (same day), so both boundaries carry a half-day. It must // still count as 0.5, not 0 (the two boundaries collapse onto one day). self::assertSame(0.5, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-01'), new DateTimeImmutable('2026-06-01'), HalfDay::Morning, HalfDay::Morning, )); } public function testHalfDayOnNonCountedStartIsIgnored(): void { // Sat 2026-06-06 → Mon 2026-06-08, jours ouvrés : Sat & Sun skipped, Mon = 1. // startHalfDay sits on Saturday (not counted) => must NOT subtract 0.5. self::assertSame(1.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-06'), new DateTimeImmutable('2026-06-08'), HalfDay::Afternoon, )); } public function testHalfDayOnNonCountedEndIsIgnored(): void { // Fri 2026-06-05 → Sun 2026-06-07, jours ouvrés : Fri = 1, weekend skipped. // endHalfDay sits on Sunday (not counted) => must NOT subtract 0.5. self::assertSame(1.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-05'), new DateTimeImmutable('2026-06-07'), null, HalfDay::Morning, )); } public function testWorkingDaysVsOpenDays(): void { // Fri 2026-06-05 to Mon 2026-06-08, "ouvrables" includes Saturday // => Fri + Sat + Mon = 3 (Sunday always skipped) self::assertSame(3.0, $this->calculator->countWorkingDays( new DateTimeImmutable('2026-06-05'), new DateTimeImmutable('2026-06-08'), null, null, false, )); } }