getConstructor(); $constructor->invoke($reflection->newInstanceWithoutConstructor()); // THEN expect(fn () => $reflection->newInstance()) ->toThrow(\ReflectionException::class); }); it('can parse various date formats', function (string $input, string $expectedFormat): void { // GIVEN & WHEN $result = DatesHelper::parseDateTime($input); // THEN expect($result)->toBeInstanceOf(\DateTimeInterface::class) ->and($result->format('Y-m-d'))->toBe($expectedFormat); })->with([ ['2025-12-25 12:10:23', '2025-12-25'], ['2025-12-25', '2025-12-25'], ['25-12-2025', '2025-12-25'], ['2025-12-25T12:10:23Z', '2025-12-25'], ['2025-12-25T12:10:23+02:00', '2025-12-25'], ['2025-12-25T12:10:23.123456+02:00', '2025-12-25'], ['2025/12/25', '2025-12-25'], ]); it('throws InvalidDateFormatException for unparseable date strings', function (): void { // GIVEN $invalidDate = 'not-a-real-date-string'; // WHEN & THEN expect(fn () => DatesHelper::parseDateTime($invalidDate)) ->toThrow(InvalidDateFormatException::class, 'Unrecognized date/time format: not-a-real-date-string'); }); it('falls back to DateTimeImmutable for valid formats not fully handled by createFromFormat', function (): void { // GIVEN $fallbackDate = '2025-12-25T12:10:23.1234567Z'; // 7 digit microsecond matches regex but fails createFromFormat 'u' (6 digits) // WHEN $result = DatesHelper::parseDateTime($fallbackDate); // THEN expect($result)->toBeInstanceOf(\DateTimeInterface::class) ->and($result->format('Y-m-d'))->toBe('2025-12-25'); }); it('throws InvalidDateFormatException in parseFallback when fallback date is completely invalid logically', function (): void { // GIVEN $invalidFallbackDate = '2025-12-25T25:10:23.1234567Z'; // Invalid hour 25, matches regex but fails both createFromFormat and new DateTimeImmutable // WHEN & THEN expect(fn () => DatesHelper::parseDateTime($invalidFallbackDate)) ->toThrow(InvalidDateFormatException::class, "Unrecognized date/time format: {$invalidFallbackDate}"); }); it('handles isIn date boundary checks correctly', function (): void { // GIVEN $now = CarbonImmutable::create(2025, 6, 15, 12, 0, 0); CarbonImmutable::setTestNow($now); $past = CarbonImmutable::create(2025, 1, 1); $future = CarbonImmutable::create(2025, 12, 31); $farFuture = CarbonImmutable::create(2025, 12, 30); // WHEN & THEN expect(DatesHelper::isIn($past, $future))->toBeTrue() ->and(DatesHelper::isIn($farFuture, $future))->toBeFalse() ->and(DatesHelper::isIn($past, null))->toBeTrue() ->and(DatesHelper::isIn($future, null))->toBeFalse() ->and(DatesHelper::isIn(null, $future))->toBeTrue() ->and(DatesHelper::isIn(null, $past))->toBeFalse() ->and(DatesHelper::isIn(null, null))->toBeFalse(); }); it('formats string or DateTimeInterface to RFC3339', function (): void { // GIVEN $dateTimeObj = new \DateTimeImmutable('2025-12-25 12:10:23'); // WHEN $resultFromObj = DatesHelper::toRfc3339($dateTimeObj); $resultFromStr = DatesHelper::toRfc3339('2025-12-25 12:10:23'); // THEN expect($resultFromObj)->toContain('2025-12-25T12:10:23') ->and($resultFromStr)->toContain('2025-12-25T12:10:23'); }); });