From cceec840f8d4a26a4f463afa5cfd11421807c86b Mon Sep 17 00:00:00 2001 From: Lucas Michot Date: Thu, 23 Apr 2026 21:16:40 +0200 Subject: [PATCH 1/5] Add Carbon clamp method --- src/Illuminate/Support/Carbon.php | 30 +++++++++++++++++ tests/Support/SupportCarbonTest.php | 51 ++++++++++++++++++++++++++--- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Support/Carbon.php b/src/Illuminate/Support/Carbon.php index 81f9fce89733..57e5dcc1f224 100644 --- a/src/Illuminate/Support/Carbon.php +++ b/src/Illuminate/Support/Carbon.php @@ -4,10 +4,12 @@ use Carbon\Carbon as BaseCarbon; use Carbon\CarbonImmutable as BaseCarbonImmutable; +use DateTimeInterface; use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\Dumpable; use Ramsey\Uuid\Uuid; use Symfony\Component\Uid\Ulid; +use ValueError; class Carbon extends BaseCarbon { @@ -34,6 +36,34 @@ public static function createFromId(Uuid|Ulid|string $id): static return static::createFromInterface($id->getDateTime()); } + /** + * Clamp the date to be within the given range. + * + * @param \DateTimeInterface|string $min + * @param \DateTimeInterface|string $max + * @return static + * + * @throws \ValueError + */ + public function clamp(DateTimeInterface|string $min, DateTimeInterface|string $max): static + { + $min = self::parse($min); + $max = self::parse($max); + + if ($min->greaterThan($max)) { + throw new ValueError(sprintf( + '%s(): Argument #1 ($min) must be less than or equal to Argument #2 ($max)', + __METHOD__ + )); + } + + return match (true) { + $this->lessThan($min) => $min->copy(), + $this->greaterThan($max) => $max->copy(), + default => $this->copy(), + }; + } + /** * Get the current date / time plus a given amount of time. */ diff --git a/tests/Support/SupportCarbonTest.php b/tests/Support/SupportCarbonTest.php index 942d783d252e..3f42909886af 100644 --- a/tests/Support/SupportCarbonTest.php +++ b/tests/Support/SupportCarbonTest.php @@ -8,13 +8,11 @@ use DateTimeInterface; use Illuminate\Support\Carbon; use PHPUnit\Framework\TestCase; +use ValueError; class SupportCarbonTest extends TestCase { - /** - * @var \Illuminate\Support\Carbon - */ - protected $now; + protected Carbon $now; protected function setUp(): void { @@ -159,4 +157,49 @@ public function testMinus(): void $carbon = Carbon::parse('2026-05-31'); $this->assertSame('2026-04-30', $carbon->minus(months: 1, overflow: false)->toDateString()); } + + public function testClampReturnsSelfWhenWithinRange(): void + { + $carbon = Carbon::parse('2023-06-15'); + $result = $carbon->clamp('2023-01-01', '2024-12-31'); + + $this->assertTrue($result->isSameAs('Y-m-d', $carbon)); + } + + public function testClampReturnsMinWhenBefore(): void + { + $result = Carbon::parse('2020-01-01')->clamp('2023-01-01', '2024-12-31'); + + $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2023-01-01'))); + } + + public function testClampReturnsMaxWhenAfter(): void + { + $result = Carbon::parse('2025-09-01')->clamp('2023-01-01', '2024-12-31'); + + $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2024-12-31'))); + } + + public function testClampDoesNotMutateOriginal(): void + { + $carbon = Carbon::parse('2020-01-01'); + $carbon->clamp('2023-01-01', '2024-12-31'); + + $this->assertTrue($carbon->isSameAs('Y-m-d', Carbon::parse('2020-01-01'))); + } + + public function testClampThrowsValueErrorWhenMinGreaterThanMax(): void + { + $this->expectException(ValueError::class); + $this->expectExceptionMessage('Illuminate\Support\Carbon::clamp(): Argument #1 ($min) must be less than or equal to Argument #2 ($max)'); + + Carbon::parse('2023-06-15')->clamp('2025-01-01', '2023-01-01'); + } + + public function testClampWorksWithCarbonInstances(): void + { + $result = Carbon::parse('2020-01-01')->clamp(Carbon::parse('2023-01-01'), Carbon::parse('2024-12-31')); + + $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2023-01-01'))); + } } From 0d1c1f404ba5818703c79bbac56f4b89e0469131 Mon Sep 17 00:00:00 2001 From: Lucas Michot Date: Thu, 23 Apr 2026 22:31:27 +0200 Subject: [PATCH 2/5] Let clamp swap min and max if max lower than min --- src/Illuminate/Support/Carbon.php | 10 +--------- tests/Support/SupportCarbonTest.php | 8 +++----- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Support/Carbon.php b/src/Illuminate/Support/Carbon.php index 57e5dcc1f224..3db95f0ba423 100644 --- a/src/Illuminate/Support/Carbon.php +++ b/src/Illuminate/Support/Carbon.php @@ -9,7 +9,6 @@ use Illuminate\Support\Traits\Dumpable; use Ramsey\Uuid\Uuid; use Symfony\Component\Uid\Ulid; -use ValueError; class Carbon extends BaseCarbon { @@ -42,20 +41,13 @@ public static function createFromId(Uuid|Ulid|string $id): static * @param \DateTimeInterface|string $min * @param \DateTimeInterface|string $max * @return static - * - * @throws \ValueError */ public function clamp(DateTimeInterface|string $min, DateTimeInterface|string $max): static { $min = self::parse($min); $max = self::parse($max); - if ($min->greaterThan($max)) { - throw new ValueError(sprintf( - '%s(): Argument #1 ($min) must be less than or equal to Argument #2 ($max)', - __METHOD__ - )); - } + [$min, $max] = $min->lte($max) ? [$min, $max] : [$max, $min]; return match (true) { $this->lessThan($min) => $min->copy(), diff --git a/tests/Support/SupportCarbonTest.php b/tests/Support/SupportCarbonTest.php index 3f42909886af..de06e27ba525 100644 --- a/tests/Support/SupportCarbonTest.php +++ b/tests/Support/SupportCarbonTest.php @@ -8,7 +8,6 @@ use DateTimeInterface; use Illuminate\Support\Carbon; use PHPUnit\Framework\TestCase; -use ValueError; class SupportCarbonTest extends TestCase { @@ -188,12 +187,11 @@ public function testClampDoesNotMutateOriginal(): void $this->assertTrue($carbon->isSameAs('Y-m-d', Carbon::parse('2020-01-01'))); } - public function testClampThrowsValueErrorWhenMinGreaterThanMax(): void + public function testClampSwapMinIfGreaterThanMax(): void { - $this->expectException(ValueError::class); - $this->expectExceptionMessage('Illuminate\Support\Carbon::clamp(): Argument #1 ($min) must be less than or equal to Argument #2 ($max)'); + $result = Carbon::parse('2025-05-31')->clamp('2030-12-31', '2020-01-01'); - Carbon::parse('2023-06-15')->clamp('2025-01-01', '2023-01-01'); + $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2025-05-31'))); } public function testClampWorksWithCarbonInstances(): void From f0672e570bc4b092c76d4a51087d2624a87d1314 Mon Sep 17 00:00:00 2001 From: Lucas Michot Date: Fri, 24 Apr 2026 10:44:24 +0200 Subject: [PATCH 3/5] Simplify clamp --- src/Illuminate/Support/Carbon.php | 12 +++---- tests/Support/SupportCarbonTest.php | 51 +++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/Illuminate/Support/Carbon.php b/src/Illuminate/Support/Carbon.php index 3db95f0ba423..9b2363ca484f 100644 --- a/src/Illuminate/Support/Carbon.php +++ b/src/Illuminate/Support/Carbon.php @@ -44,16 +44,12 @@ public static function createFromId(Uuid|Ulid|string $id): static */ public function clamp(DateTimeInterface|string $min, DateTimeInterface|string $max): static { - $min = self::parse($min); - $max = self::parse($max); + $min = $this->resolveCarbon($min); + $max = $this->resolveCarbon($max); - [$min, $max] = $min->lte($max) ? [$min, $max] : [$max, $min]; + [$min, $max] = $min <= $max ? [$min, $max] : [$max, $min]; - return match (true) { - $this->lessThan($min) => $min->copy(), - $this->greaterThan($max) => $max->copy(), - default => $this->copy(), - }; + return $this->min($max)->max($min)->copy(); } /** diff --git a/tests/Support/SupportCarbonTest.php b/tests/Support/SupportCarbonTest.php index de06e27ba525..6dc3375d6971 100644 --- a/tests/Support/SupportCarbonTest.php +++ b/tests/Support/SupportCarbonTest.php @@ -5,6 +5,7 @@ use BadMethodCallException; use Carbon\Carbon as BaseCarbon; use Carbon\CarbonImmutable as BaseCarbonImmutable; +use Carbon\CarbonInterface; use DateTimeInterface; use Illuminate\Support\Carbon; use PHPUnit\Framework\TestCase; @@ -159,45 +160,69 @@ public function testMinus(): void public function testClampReturnsSelfWhenWithinRange(): void { - $carbon = Carbon::parse('2023-06-15'); - $result = $carbon->clamp('2023-01-01', '2024-12-31'); + $carbon = Carbon::parse('2025-06-15'); + $result = $carbon->clamp( + min: '2020-01-01', + max: '2030-12-31', + ); - $this->assertTrue($result->isSameAs('Y-m-d', $carbon)); + $this->assertCarbonIs($result, '2025-06-15'); } public function testClampReturnsMinWhenBefore(): void { - $result = Carbon::parse('2020-01-01')->clamp('2023-01-01', '2024-12-31'); + $result = Carbon::parse('2020-01-01')->clamp( + min: '2025-06-15', + max: '2030-12-31', + ); - $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2023-01-01'))); + $this->assertCarbonIs($result, '2025-06-15'); } public function testClampReturnsMaxWhenAfter(): void { - $result = Carbon::parse('2025-09-01')->clamp('2023-01-01', '2024-12-31'); + $result = Carbon::parse('2030-12-31')->clamp( + min: '2020-01-01', + max: '2025-06-15', + ); - $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2024-12-31'))); + $this->assertCarbonIs($result, '2025-06-15'); } public function testClampDoesNotMutateOriginal(): void { $carbon = Carbon::parse('2020-01-01'); - $carbon->clamp('2023-01-01', '2024-12-31'); + $carbon->clamp( + min: '2025-06-15', + max: '2030-12-31', + ); - $this->assertTrue($carbon->isSameAs('Y-m-d', Carbon::parse('2020-01-01'))); + $this->assertCarbonIs($carbon, '2020-01-01'); } public function testClampSwapMinIfGreaterThanMax(): void { - $result = Carbon::parse('2025-05-31')->clamp('2030-12-31', '2020-01-01'); + $result = Carbon::parse('2025-06-15')->clamp( + min: '2030-12-31', + max: '2020-01-01', + ); + + $this->assertCarbonIs($result, '2025-06-15'); - $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2025-05-31'))); } public function testClampWorksWithCarbonInstances(): void { - $result = Carbon::parse('2020-01-01')->clamp(Carbon::parse('2023-01-01'), Carbon::parse('2024-12-31')); + $result = Carbon::parse('2020-01-01')->clamp( + min: Carbon::parse('2025-06-15'), + max: Carbon::parse('2030-12-31'), + ); + + $this->assertCarbonIs($result, '2025-06-15'); + } - $this->assertTrue($result->isSameAs('Y-m-d', Carbon::parse('2023-01-01'))); + protected function assertCarbonIs(CarbonInterface $carbon, string $expected, $format = 'Y-m-d'): void + { + $this->assertTrue($carbon->isSameAs($format, Carbon::createFromFormat($format, $expected))); } } From 30627bbe65daa21478015f09a7646924f085b639 Mon Sep 17 00:00:00 2001 From: Lucas Michot Date: Sat, 25 Apr 2026 09:02:05 +0200 Subject: [PATCH 4/5] Fic CS --- tests/Support/SupportCarbonTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Support/SupportCarbonTest.php b/tests/Support/SupportCarbonTest.php index 6dc3375d6971..757ef5f1ab82 100644 --- a/tests/Support/SupportCarbonTest.php +++ b/tests/Support/SupportCarbonTest.php @@ -208,7 +208,6 @@ public function testClampSwapMinIfGreaterThanMax(): void ); $this->assertCarbonIs($result, '2025-06-15'); - } public function testClampWorksWithCarbonInstances(): void From b42acd14ef2dd3a104a9487bad3b6c9502320e70 Mon Sep 17 00:00:00 2001 From: Lucas Michot Date: Sat, 25 Apr 2026 09:03:24 +0200 Subject: [PATCH 5/5] Update tests --- tests/Support/SupportCarbonTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Support/SupportCarbonTest.php b/tests/Support/SupportCarbonTest.php index 757ef5f1ab82..d5b1ac336a5e 100644 --- a/tests/Support/SupportCarbonTest.php +++ b/tests/Support/SupportCarbonTest.php @@ -160,8 +160,7 @@ public function testMinus(): void public function testClampReturnsSelfWhenWithinRange(): void { - $carbon = Carbon::parse('2025-06-15'); - $result = $carbon->clamp( + $result = Carbon::parse('2025-06-15')->clamp( min: '2020-01-01', max: '2030-12-31', );