From 6200a5fc88f7003649d7660449ad127bc1aa4c53 Mon Sep 17 00:00:00 2001 From: faissaloux Date: Sun, 21 Jul 2024 23:59:27 +0200 Subject: [PATCH 1/7] Implement isStartOfWeek and isEndOfWeek --- src/Carbon/CarbonInterface.php | 26 ++++++++- src/Carbon/Traits/Boundaries.php | 2 +- src/Carbon/Traits/Comparison.php | 38 ++++++++++++- tests/Carbon/IsTest.php | 34 ++++++++++++ tests/CarbonImmutable/IsTest.php | 95 ++++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 5 deletions(-) diff --git a/src/Carbon/CarbonInterface.php b/src/Carbon/CarbonInterface.php index 65b5730c61..8e85dd2118 100644 --- a/src/Carbon/CarbonInterface.php +++ b/src/Carbon/CarbonInterface.php @@ -2700,7 +2700,18 @@ public function isEndOfTime(): bool; * Carbon::parse('2019-02-28 20:13:00')->isEndOfUnit(Unit::Hour, '15 minutes'); // false * ``` */ - public function isEndOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + public function isEndOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, mixed ...$params): bool; + + /** + * Determines if the instance is end of week (last day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->endOfWeek()->isEndOfWeek(); // true + * Carbon::parse('2024-08-31')->isEndOfWeek(); // false + * ``` + */ + public function isEndOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, WeekDay|int|null $weekEndsAt = null): bool; /** * Determines if the instance is in the future, ie. greater (after) than now. @@ -2931,7 +2942,18 @@ public function isStartOfTime(): bool; * Carbon::parse('2019-02-28 20:13:00')->isStartOfUnit(Unit::Hour, '15 minutes'); // true * ``` */ - public function isStartOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + public function isStartOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, mixed ...$params): bool; + + /** + * Determines if the instance is start of week (first day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->startOfWeek()->isStartOfWeek(); // true + * Carbon::parse('2024-08-31')->isStartOfWeek(); // false + * ``` + */ + public function isStartOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, WeekDay|int|null $weekStartsAt = null): bool; /** * Returns true if the strict mode is globally in use, false else. diff --git a/src/Carbon/Traits/Boundaries.php b/src/Carbon/Traits/Boundaries.php index ad0ae3ff2d..e4f0abc603 100644 --- a/src/Carbon/Traits/Boundaries.php +++ b/src/Carbon/Traits/Boundaries.php @@ -295,7 +295,7 @@ public function startOfWeek(WeekDay|int|null $weekStartsAt = null): static * echo Carbon::parse('2018-07-25 12:45:16')->endOfWeek(Carbon::SATURDAY) . "\n"; * ``` * - * @param WeekDay|int|null $weekEndsAt optional start allow you to specify the day of week to use to end the week + * @param WeekDay|int|null $weekEndsAt optional end allow you to specify the day of week to use to end the week * * @return static */ diff --git a/src/Carbon/Traits/Comparison.php b/src/Carbon/Traits/Comparison.php index 730e235753..7807d9a343 100644 --- a/src/Carbon/Traits/Comparison.php +++ b/src/Carbon/Traits/Comparison.php @@ -789,13 +789,14 @@ public function isStartOfDay( public function isStartOfUnit( Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + mixed ...$params, ): bool { $interval ??= match ($unit) { Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, default => Unit::Day, }; - $startOfUnit = $this->avoidMutation()->startOf($unit); + $startOfUnit = $this->avoidMutation()->startOf($unit, ...$params); $startOfUnitDateTime = $startOfUnit->rawFormat('Y-m-d H:i:s.u'); $maximumDateTime = $startOfUnit ->add($interval instanceof Unit ? '1 '.$interval->value : $interval) @@ -879,13 +880,14 @@ public function isEndOfDay( public function isEndOfUnit( Unit $unit, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + mixed ...$params, ): bool { $interval ??= match ($unit) { Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, default => Unit::Day, }; - $endOfUnit = $this->avoidMutation()->endOf($unit); + $endOfUnit = $this->avoidMutation()->endOf($unit, ...$params); $endOfUnitDateTime = $endOfUnit->rawFormat('Y-m-d H:i:s.u'); $minimumDateTime = $endOfUnit ->sub($interval instanceof Unit ? '1 '.$interval->value : $interval) @@ -898,6 +900,38 @@ public function isEndOfUnit( return $this->rawFormat('Y-m-d H:i:s.u') > $minimumDateTime; } + /** + * Determines if the instance is start of week (first day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->startOfWeek()->isStartOfWeek(); // true + * Carbon::parse('2024-08-31')->isStartOfWeek(); // false + * ``` + */ + public function isStartOfWeek( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + WeekDay|int|null $weekStartsAt = null, + ): bool { + return $this->isStartOfUnit(Unit::Week, $interval, $weekStartsAt); + } + + /** + * Determines if the instance is end of week (last day by default but interval can be customized). + * + * @example + * ``` + * Carbon::parse('2024-08-31')->endOfWeek()->isEndOfWeek(); // true + * Carbon::parse('2024-08-31')->isEndOfWeek(); // false + * ``` + */ + public function isEndOfWeek( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + WeekDay|int|null $weekEndsAt = null, + ): bool { + return $this->isEndOfUnit(Unit::Week, $interval, $weekEndsAt); + } + /** * Check if the instance is start of day / midnight. * diff --git a/tests/Carbon/IsTest.php b/tests/Carbon/IsTest.php index a38ed6b3b5..996b8db18b 100644 --- a/tests/Carbon/IsTest.php +++ b/tests/Carbon/IsTest.php @@ -958,6 +958,40 @@ public function testIsEndOfUnit() $this->assertFalse(Carbon::parse('Sunday 23:59:59.999999')->isEndOfUnit(Unit::Week, CarbonInterval::day(-1))); } + public function testIsStartOfWeek() + { + $this->assertTrue(Carbon::parse('Monday 01:06:12')->isStartOfWeek()); + $this->assertFalse(Carbon::parse('Saturday 01:06:12')->isStartOfWeek()); + + Carbon::setLocale('ar'); + + $this->assertTrue(Carbon::parse('Saturday 01:06:12')->isStartOfWeek()); + $this->assertFalse(Carbon::parse('Monday 01:06:12')->isStartOfWeek()); + + $this->assertTrue(Carbon::parse('Sunday 01:06:12')->isStartOfWeek(weekStartsAt: WeekDay::Sunday)); + $this->assertFalse(Carbon::parse('Saturday 01:06:12')->isStartOfWeek(weekStartsAt: WeekDay::Sunday)); + + $this->assertTrue(Carbon::parse('Saturday 01:06:12')->isStartOfWeek('2 hours')); + $this->assertFalse(Carbon::parse('Saturday 01:06:12')->isStartOfWeek('1 hour')); + } + + public function testIsEndOfWeek() + { + $this->assertTrue(Carbon::parse('Sunday 01:06:12')->isEndOfWeek()); + $this->assertFalse(Carbon::parse('Friday 01:06:12')->isEndOfWeek()); + + Carbon::setLocale('ar'); + + $this->assertTrue(Carbon::parse('Friday 01:06:12')->isEndOfWeek()); + $this->assertFalse(Carbon::parse('Sunday 01:06:12')->isEndOfWeek()); + + $this->assertTrue(Carbon::parse('Saturday 01:06:12')->isEndOfWeek(weekEndsAt: WeekDay::Saturday)); + $this->assertFalse(Carbon::parse('Friday 01:06:12')->isEndOfWeek(weekEndsAt: WeekDay::Saturday)); + + $this->assertTrue(Carbon::parse('Friday 22:06:12')->isEndOfWeek('2 hours')); + $this->assertFalse(Carbon::parse('Saturday 22:06:12')->isEndOfWeek('1 hour')); + } + public function testIsMidnight() { $this->assertTrue(Carbon::parse('00:00:00')->isMidnight()); diff --git a/tests/CarbonImmutable/IsTest.php b/tests/CarbonImmutable/IsTest.php index 9177548558..89b8653749 100644 --- a/tests/CarbonImmutable/IsTest.php +++ b/tests/CarbonImmutable/IsTest.php @@ -14,6 +14,9 @@ namespace Tests\CarbonImmutable; use Carbon\CarbonImmutable as Carbon; +use Carbon\CarbonInterval; +use Carbon\Unit; +use DateInterval; use DateTime; use InvalidArgumentException; use PHPUnit\Framework\Attributes\DataProvider; @@ -856,6 +859,53 @@ public function testIsStartOfDay() $this->assertFalse(Carbon::now()->endOfDay()->isStartOfDay()); } + public function testIsStartOfDayInterval() + { + $this->assertTrue(Carbon::parse('00:00:00')->isStartOfDay('15 minutes')); + $this->assertTrue(Carbon::parse('00:14:59.999999')->isStartOfDay('15 minutes')); + $this->assertFalse(Carbon::parse('00:15:00')->isStartOfDay('15 minutes')); + $this->assertTrue(Carbon::parse('00:59:59.999999')->isStartOfDay(Unit::Hour)); + $this->assertFalse(Carbon::parse('01:00:00')->isStartOfDay(Unit::Hour)); + $this->assertTrue(Carbon::parse('00:00:59.999999')->isStartOfDay(new DateInterval('PT1M'))); + $this->assertFalse(Carbon::parse('00:01:00')->isStartOfDay(new DateInterval('PT1M'))); + + $this->assertTrue(Carbon::parse('00:00:00')->isStartOfDay(interval: '15 minutes')); + $this->assertTrue(Carbon::parse('00:14:59.999999')->isStartOfDay(interval: '15 minutes')); + $this->assertFalse(Carbon::parse('00:15:00')->isStartOfDay(interval: '15 minutes')); + $this->assertTrue(Carbon::parse('00:59:59.999999')->isStartOfDay(interval: Unit::Hour)); + $this->assertFalse(Carbon::parse('01:00:00')->isStartOfDay(interval: Unit::Hour)); + $this->assertTrue(Carbon::parse('00:00:59.999999')->isStartOfDay(interval: new DateInterval('PT1M'))); + $this->assertFalse(Carbon::parse('00:01:00')->isStartOfDay(interval: new DateInterval('PT1M'))); + + $this->assertTrue(Carbon::parse('00:01:59.999999')->isStartOfDay(interval: CarbonInterval::minutes(2))); + $this->assertFalse(Carbon::parse('00:02:00')->isStartOfDay(interval: CarbonInterval::minutes(2))); + + // Always false with negative interval + $this->assertFalse(Carbon::parse('00:00:00')->isStartOfDay(interval: CarbonInterval::minutes(-2))); + + // Always true with interval bigger than 1 day + $this->assertTrue(Carbon::parse('23:59:59.999999')->isStartOfDay(interval: CarbonInterval::hours(36))); + } + + public function testIsStartOfUnit() + { + $this->assertTrue(Carbon::parse('00:00:00')->isStartOfUnit(Unit::Hour)); + + $this->assertFalse(Carbon::parse('00:00:00.000001')->isStartOfUnit(Unit::Hour)); + $this->assertFalse(Carbon::parse('00:00:01')->isStartOfUnit(Unit::Hour)); + + $this->assertTrue(Carbon::parse('00:00:00')->isStartOfUnit(Unit::Hour, '5 minutes')); + $this->assertTrue(Carbon::parse('00:04:59.999999')->isStartOfUnit(Unit::Hour, '5 minutes')); + + $this->assertFalse(Carbon::parse('00:05:00')->isStartOfUnit(Unit::Hour, '5 minutes')); + + $this->assertTrue(Carbon::parse('Monday')->isStartOfUnit(Unit::Week)); + $this->assertTrue(Carbon::parse('Monday 23:59:59.999999')->isStartOfUnit(Unit::Week)); + + $this->assertFalse(Carbon::parse('Tuesday')->isStartOfUnit(Unit::Week)); + $this->assertFalse(Carbon::parse('Monday')->isStartOfUnit(Unit::Week, CarbonInterval::day(-1))); + } + public function testIsStartOfDayWithMicroseconds() { $this->assertTrue(Carbon::parse('00:00:00')->isStartOfDay(true)); @@ -881,6 +931,31 @@ public function testIsEndOfDay() $this->assertFalse(Carbon::now()->startOfDay()->isEndOfDay()); } + public function testIsEndOfDayInterval() + { + $this->assertTrue(Carbon::parse('23:59:59.999999')->isEndOfDay('15 minutes')); + $this->assertTrue(Carbon::parse('23:45:00')->isEndOfDay('15 minutes')); + $this->assertFalse(Carbon::parse('23:44:59.999999')->isEndOfDay('15 minutes')); + $this->assertTrue(Carbon::parse('23:00:00')->isEndOfDay(Unit::Hour)); + $this->assertFalse(Carbon::parse('22:59:59.999999')->isEndOfDay(Unit::Hour)); + $this->assertTrue(Carbon::parse('23:59:00')->isEndOfDay(new DateInterval('PT1M'))); + $this->assertFalse(Carbon::parse('23:58:59.999999')->isEndOfDay(new DateInterval('PT1M'))); + + $this->assertTrue(Carbon::parse('23:59:59.999999')->isEndOfDay(interval: '15 minutes')); + $this->assertTrue(Carbon::parse('23:45:00')->isEndOfDay(interval: '15 minutes')); + $this->assertFalse(Carbon::parse('23:44:59.999999')->isEndOfDay(interval: '15 minutes')); + $this->assertTrue(Carbon::parse('23:00:00')->isEndOfDay(interval: Unit::Hour)); + $this->assertFalse(Carbon::parse('22:59:59.999999')->isEndOfDay(interval: Unit::Hour)); + $this->assertTrue(Carbon::parse('23:59:00')->isEndOfDay(interval: new DateInterval('PT1M'))); + $this->assertFalse(Carbon::parse('23:58:59.999999')->isEndOfDay(interval: new DateInterval('PT1M'))); + + // Always false with negative interval + $this->assertFalse(Carbon::parse('00:00:00')->isEndOfDay(interval: CarbonInterval::minutes(-2))); + + // Always true with interval bigger than 1 day + $this->assertTrue(Carbon::parse('23:59:59.999999')->isEndOfDay(interval: CarbonInterval::hours(36))); + } + public function testIsEndOfDayWithMicroseconds() { $this->assertTrue(Carbon::parse('23:59:59.999999')->isEndOfDay(true)); @@ -890,6 +965,26 @@ public function testIsEndOfDayWithMicroseconds() $this->assertFalse(Carbon::parse('23:59:59.999998')->isEndOfDay(true)); } + public function testIsEndOfUnit() + { + $this->assertTrue(Carbon::parse('23:59:59.999999')->isEndOfUnit(Unit::Hour)); + + $this->assertFalse(Carbon::parse('23:59:59.999998')->isEndOfUnit(Unit::Hour)); + $this->assertFalse(Carbon::parse('23:59:59')->isEndOfUnit(Unit::Hour)); + + $this->assertTrue(Carbon::parse('23:55:00.000001')->isEndOfUnit(Unit::Hour, '5 minutes')); + $this->assertTrue(Carbon::parse('23:55:00')->isEndOfUnit(Unit::Hour, '5 minutes')); + + $this->assertFalse(Carbon::parse('23:54:59.999999')->isEndOfUnit(Unit::Hour, '5 minutes')); + + $this->assertTrue(Carbon::parse('Sunday 23:59:59')->isEndOfUnit(Unit::Week, '2 days')); + $this->assertTrue(Carbon::parse('Saturday 00:00')->isEndOfUnit(Unit::Week, '2 days')); + + $this->assertFalse(Carbon::parse('Saturday 00:00')->isEndOfUnit(Unit::Week)); + $this->assertFalse(Carbon::parse('Friday 23:59:59.999999')->isEndOfUnit(Unit::Week, '2 days')); + $this->assertFalse(Carbon::parse('Sunday 23:59:59.999999')->isEndOfUnit(Unit::Week, CarbonInterval::day(-1))); + } + public function testIsMidnight() { $this->assertTrue(Carbon::parse('00:00:00')->isMidnight()); From f4c06df271a8c8e1cc1a050ca8505fc7f820e9d0 Mon Sep 17 00:00:00 2001 From: KyleKatarn Date: Mon, 22 Jul 2024 00:26:27 +0200 Subject: [PATCH 2/7] Implement isStartOf/isEndOf for every unit --- src/Carbon/CarbonInterface.php | 100 ++++++++++ src/Carbon/Traits/Comparison.php | 308 ++++++++++++++++++++++++------- 2 files changed, 344 insertions(+), 64 deletions(-) diff --git a/src/Carbon/CarbonInterface.php b/src/Carbon/CarbonInterface.php index 8e85dd2118..03ce865861 100644 --- a/src/Carbon/CarbonInterface.php +++ b/src/Carbon/CarbonInterface.php @@ -2662,6 +2662,11 @@ public function isCurrentUnit(string $unit): bool; */ public function isDayOfWeek($dayOfWeek): bool; + /** + * Determines if the instance is end of century (first day by default but interval can be customized). + */ + public function isEndOfCentury(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** * Check if the instance is end of day. * @@ -2684,6 +2689,46 @@ public function isDayOfWeek($dayOfWeek): bool; */ public function isEndOfDay(Unit|DateInterval|Closure|CarbonConverterInterface|string|bool $checkMicroseconds = false, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** + * Determines if the instance is end of decade (first day by default but interval can be customized). + */ + public function isEndOfDecade(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of hour (first microsecond by default but interval can be customized). + */ + public function isEndOfHour(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of millennium (first day by default but interval can be customized). + */ + public function isEndOfMillennium(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of millisecond (first microsecond by default but interval can be customized). + */ + public function isEndOfMillisecond(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of minute (first microsecond by default but interval can be customized). + */ + public function isEndOfMinute(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of month (first day by default but interval can be customized). + */ + public function isEndOfMonth(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of quarter (first day by default but interval can be customized). + */ + public function isEndOfQuarter(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is end of second (first microsecond by default but interval can be customized). + */ + public function isEndOfSecond(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** * Returns true if the date was created using CarbonImmutable::endOfTime() * @@ -2713,6 +2758,11 @@ public function isEndOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverte */ public function isEndOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, WeekDay|int|null $weekEndsAt = null): bool; + /** + * Determines if the instance is end of year (first day by default but interval can be customized). + */ + public function isEndOfYear(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** * Determines if the instance is in the future, ie. greater (after) than now. * @@ -2906,6 +2956,11 @@ public function isSameQuarter(DateTimeInterface|string $date, bool $ofSameYear = */ public function isSameUnit(string $unit, DateTimeInterface|string $date): bool; + /** + * Determines if the instance is start of century (first day by default but interval can be customized). + */ + public function isStartOfCentury(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** * Check if the instance is start of day / midnight. * @@ -2926,6 +2981,46 @@ public function isSameUnit(string $unit, DateTimeInterface|string $date): bool; */ public function isStartOfDay(Unit|DateInterval|Closure|CarbonConverterInterface|string|bool $checkMicroseconds = false, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** + * Determines if the instance is start of decade (first day by default but interval can be customized). + */ + public function isStartOfDecade(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of hour (first microsecond by default but interval can be customized). + */ + public function isStartOfHour(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of millennium (first day by default but interval can be customized). + */ + public function isStartOfMillennium(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of millisecond (first microsecond by default but interval can be customized). + */ + public function isStartOfMillisecond(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of minute (first microsecond by default but interval can be customized). + */ + public function isStartOfMinute(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of month (first day by default but interval can be customized). + */ + public function isStartOfMonth(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of quarter (first day by default but interval can be customized). + */ + public function isStartOfQuarter(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + + /** + * Determines if the instance is start of second (first microsecond by default but interval can be customized). + */ + public function isStartOfSecond(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** * Returns true if the date was created using CarbonImmutable::startOfTime() * @@ -2955,6 +3050,11 @@ public function isStartOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConver */ public function isStartOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, WeekDay|int|null $weekStartsAt = null): bool; + /** + * Determines if the instance is start of year (first day by default but interval can be customized). + */ + public function isStartOfYear(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; + /** * Returns true if the strict mode is globally in use, false else. * (It can be overridden in specific instances.) diff --git a/src/Carbon/Traits/Comparison.php b/src/Carbon/Traits/Comparison.php index 7807d9a343..bc7e55da5a 100644 --- a/src/Carbon/Traits/Comparison.php +++ b/src/Carbon/Traits/Comparison.php @@ -718,6 +718,142 @@ public function isLastOfMonth(): bool return $this->day === $this->daysInMonth; } + /** + * Check if the instance is start of a given unit (tolerating a given interval). + * + * @example + * ``` + * // Check if a date-time is the first 15 minutes of the hour it's in + * Carbon::parse('2019-02-28 20:13:00')->isStartOfUnit(Unit::Hour, '15 minutes'); // true + * ``` + */ + public function isStartOfUnit( + Unit $unit, + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + mixed ...$params, + ): bool { + $interval ??= match ($unit) { + Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, + default => Unit::Day, + }; + + $startOfUnit = $this->avoidMutation()->startOf($unit, ...$params); + $startOfUnitDateTime = $startOfUnit->rawFormat('Y-m-d H:i:s.u'); + $maximumDateTime = $startOfUnit + ->add($interval instanceof Unit ? '1 '.$interval->value : $interval) + ->rawFormat('Y-m-d H:i:s.u'); + + if ($maximumDateTime < $startOfUnitDateTime) { + return false; + } + + return $this->rawFormat('Y-m-d H:i:s.u') < $maximumDateTime; + } + + /** + * Check if the instance is end of a given unit (tolerating a given interval). + * + * @example + * ``` + * // Check if a date-time is the last 15 minutes of the hour it's in + * Carbon::parse('2019-02-28 20:13:00')->isEndOfUnit(Unit::Hour, '15 minutes'); // false + * ``` + */ + public function isEndOfUnit( + Unit $unit, + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + mixed ...$params, + ): bool { + $interval ??= match ($unit) { + Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, + default => Unit::Day, + }; + + $endOfUnit = $this->avoidMutation()->endOf($unit, ...$params); + $endOfUnitDateTime = $endOfUnit->rawFormat('Y-m-d H:i:s.u'); + $minimumDateTime = $endOfUnit + ->sub($interval instanceof Unit ? '1 '.$interval->value : $interval) + ->rawFormat('Y-m-d H:i:s.u'); + + if ($minimumDateTime > $endOfUnitDateTime) { + return false; + } + + return $this->rawFormat('Y-m-d H:i:s.u') > $minimumDateTime; + } + + /** + * Determines if the instance is start of millisecond (first microsecond by default but interval can be customized). + */ + public function isStartOfMillisecond( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Millisecond, $interval); + } + + /** + * Determines if the instance is end of millisecond (first microsecond by default but interval can be customized). + */ + public function isEndOfMillisecond( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Millisecond, $interval); + } + + /** + * Determines if the instance is start of second (first microsecond by default but interval can be customized). + */ + public function isStartOfSecond( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Second, $interval); + } + + /** + * Determines if the instance is end of second (first microsecond by default but interval can be customized). + */ + public function isEndOfSecond( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Second, $interval); + } + + /** + * Determines if the instance is start of minute (first microsecond by default but interval can be customized). + */ + public function isStartOfMinute( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Minute, $interval); + } + + /** + * Determines if the instance is end of minute (first microsecond by default but interval can be customized). + */ + public function isEndOfMinute( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Minute, $interval); + } + + /** + * Determines if the instance is start of hour (first microsecond by default but interval can be customized). + */ + public function isStartOfHour( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Hour, $interval); + } + + /** + * Determines if the instance is end of hour (first microsecond by default but interval can be customized). + */ + public function isEndOfHour( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Hour, $interval); + } + /** * Check if the instance is start of day / midnight. * @@ -777,38 +913,6 @@ public function isStartOfDay( : $this->rawFormat('H:i:s') === '00:00:00'; } - /** - * Check if the instance is start of a given unit (tolerating a given interval). - * - * @example - * ``` - * // Check if a date-time is the first 15 minutes of the hour it's in - * Carbon::parse('2019-02-28 20:13:00')->isStartOfUnit(Unit::Hour, '15 minutes'); // true - * ``` - */ - public function isStartOfUnit( - Unit $unit, - Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, - mixed ...$params, - ): bool { - $interval ??= match ($unit) { - Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, - default => Unit::Day, - }; - - $startOfUnit = $this->avoidMutation()->startOf($unit, ...$params); - $startOfUnitDateTime = $startOfUnit->rawFormat('Y-m-d H:i:s.u'); - $maximumDateTime = $startOfUnit - ->add($interval instanceof Unit ? '1 '.$interval->value : $interval) - ->rawFormat('Y-m-d H:i:s.u'); - - if ($maximumDateTime < $startOfUnitDateTime) { - return false; - } - - return $this->rawFormat('Y-m-d H:i:s.u') < $maximumDateTime; - } - /** * Check if the instance is end of day. * @@ -868,38 +972,6 @@ public function isEndOfDay( : $this->rawFormat('H:i:s') === '23:59:59'; } - /** - * Check if the instance is end of a given unit (tolerating a given interval). - * - * @example - * ``` - * // Check if a date-time is the last 15 minutes of the hour it's in - * Carbon::parse('2019-02-28 20:13:00')->isEndOfUnit(Unit::Hour, '15 minutes'); // false - * ``` - */ - public function isEndOfUnit( - Unit $unit, - Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, - mixed ...$params, - ): bool { - $interval ??= match ($unit) { - Unit::Day, Unit::Hour, Unit::Minute, Unit::Second, Unit::Millisecond, Unit::Microsecond => Unit::Microsecond, - default => Unit::Day, - }; - - $endOfUnit = $this->avoidMutation()->endOf($unit, ...$params); - $endOfUnitDateTime = $endOfUnit->rawFormat('Y-m-d H:i:s.u'); - $minimumDateTime = $endOfUnit - ->sub($interval instanceof Unit ? '1 '.$interval->value : $interval) - ->rawFormat('Y-m-d H:i:s.u'); - - if ($minimumDateTime > $endOfUnitDateTime) { - return false; - } - - return $this->rawFormat('Y-m-d H:i:s.u') > $minimumDateTime; - } - /** * Determines if the instance is start of week (first day by default but interval can be customized). * @@ -932,6 +1004,114 @@ public function isEndOfWeek( return $this->isEndOfUnit(Unit::Week, $interval, $weekEndsAt); } + /** + * Determines if the instance is start of month (first day by default but interval can be customized). + */ + public function isStartOfMonth( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Month, $interval); + } + + /** + * Determines if the instance is end of month (first day by default but interval can be customized). + */ + public function isEndOfMonth( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Month, $interval); + } + + /** + * Determines if the instance is start of quarter (first day by default but interval can be customized). + */ + public function isStartOfQuarter( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Quarter, $interval); + } + + /** + * Determines if the instance is end of quarter (first day by default but interval can be customized). + */ + public function isEndOfQuarter( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Quarter, $interval); + } + + /** + * Determines if the instance is start of year (first day by default but interval can be customized). + */ + public function isStartOfYear( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Year, $interval); + } + + /** + * Determines if the instance is end of year (first day by default but interval can be customized). + */ + public function isEndOfYear( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Year, $interval); + } + + /** + * Determines if the instance is start of decade (first day by default but interval can be customized). + */ + public function isStartOfDecade( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Decade, $interval); + } + + /** + * Determines if the instance is end of decade (first day by default but interval can be customized). + */ + public function isEndOfDecade( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Decade, $interval); + } + + /** + * Determines if the instance is start of century (first day by default but interval can be customized). + */ + public function isStartOfCentury( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Century, $interval); + } + + /** + * Determines if the instance is end of century (first day by default but interval can be customized). + */ + public function isEndOfCentury( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Century, $interval); + } + + /** + * Determines if the instance is start of millennium (first day by default but interval can be customized). + */ + public function isStartOfMillennium( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isStartOfUnit(Unit::Millennium, $interval); + } + + /** + * Determines if the instance is end of millennium (first day by default but interval can be customized). + */ + public function isEndOfMillennium( + Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, + ): bool { + return $this->isEndOfUnit(Unit::Millennium, $interval); + } + /** * Check if the instance is start of day / midnight. * From d47a0783a4031f00e91975bb47ce71fefdf72997 Mon Sep 17 00:00:00 2001 From: kylekatarnls Date: Mon, 22 Jul 2024 21:39:28 +0200 Subject: [PATCH 3/7] Fix documentation for isEndOf methods --- src/Carbon/Traits/Comparison.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Carbon/Traits/Comparison.php b/src/Carbon/Traits/Comparison.php index bc7e55da5a..87e16989ee 100644 --- a/src/Carbon/Traits/Comparison.php +++ b/src/Carbon/Traits/Comparison.php @@ -792,7 +792,7 @@ public function isStartOfMillisecond( } /** - * Determines if the instance is end of millisecond (first microsecond by default but interval can be customized). + * Determines if the instance is end of millisecond (last microsecond by default but interval can be customized). */ public function isEndOfMillisecond( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -810,7 +810,7 @@ public function isStartOfSecond( } /** - * Determines if the instance is end of second (first microsecond by default but interval can be customized). + * Determines if the instance is end of second (last microsecond by default but interval can be customized). */ public function isEndOfSecond( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -828,7 +828,7 @@ public function isStartOfMinute( } /** - * Determines if the instance is end of minute (first microsecond by default but interval can be customized). + * Determines if the instance is end of minute (last microsecond by default but interval can be customized). */ public function isEndOfMinute( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -846,7 +846,7 @@ public function isStartOfHour( } /** - * Determines if the instance is end of hour (first microsecond by default but interval can be customized). + * Determines if the instance is end of hour (last microsecond by default but interval can be customized). */ public function isEndOfHour( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -1014,7 +1014,7 @@ public function isStartOfMonth( } /** - * Determines if the instance is end of month (first day by default but interval can be customized). + * Determines if the instance is end of month (last day by default but interval can be customized). */ public function isEndOfMonth( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -1032,7 +1032,7 @@ public function isStartOfQuarter( } /** - * Determines if the instance is end of quarter (first day by default but interval can be customized). + * Determines if the instance is end of quarter (last day by default but interval can be customized). */ public function isEndOfQuarter( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -1050,7 +1050,7 @@ public function isStartOfYear( } /** - * Determines if the instance is end of year (first day by default but interval can be customized). + * Determines if the instance is end of year (last day by default but interval can be customized). */ public function isEndOfYear( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -1068,7 +1068,7 @@ public function isStartOfDecade( } /** - * Determines if the instance is end of decade (first day by default but interval can be customized). + * Determines if the instance is end of decade (last day by default but interval can be customized). */ public function isEndOfDecade( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -1086,7 +1086,7 @@ public function isStartOfCentury( } /** - * Determines if the instance is end of century (first day by default but interval can be customized). + * Determines if the instance is end of century (last day by default but interval can be customized). */ public function isEndOfCentury( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, @@ -1104,7 +1104,7 @@ public function isStartOfMillennium( } /** - * Determines if the instance is end of millennium (first day by default but interval can be customized). + * Determines if the instance is end of millennium (last day by default but interval can be customized). */ public function isEndOfMillennium( Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, From bc41878f110b26fdccdbeb8ff04f336a1ab83ed4 Mon Sep 17 00:00:00 2001 From: kylekatarnls Date: Tue, 23 Jul 2024 23:21:29 +0200 Subject: [PATCH 4/7] Add tests for isStartOf/isEndOf for units from month to millennium --- tests/Carbon/IsTest.php | 136 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/Carbon/IsTest.php b/tests/Carbon/IsTest.php index 996b8db18b..ca26ef185d 100644 --- a/tests/Carbon/IsTest.php +++ b/tests/Carbon/IsTest.php @@ -992,6 +992,142 @@ public function testIsEndOfWeek() $this->assertFalse(Carbon::parse('Saturday 22:06:12')->isEndOfWeek('1 hour')); } + public function testIsStartOfMonth() + { + $this->assertFalse(Carbon::parse('2024-01-31 23:59:59.999999')->isStartOfMonth()); + $this->assertTrue(Carbon::parse('2024-02-01 00:00:00')->isStartOfMonth()); + $this->assertTrue(Carbon::parse('2024-02-01 23:59:59.999999')->isStartOfMonth()); + $this->assertTrue(Carbon::parse('2024-02-01 00:00:00')->isStartOfMonth(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2024-02-01 00:00:00.000001')->isStartOfMonth(Unit::Microsecond)); + $this->assertTrue(Carbon::parse('2024-02-01 00:00:59')->isStartOfMonth(Unit::Minute)); + $this->assertFalse(Carbon::parse('2024-02-01 00:01:00')->isStartOfMonth(Unit::Minute)); + $this->assertFalse(Carbon::parse('2024-02-02 23:59:59.999999')->isStartOfMonth()); + $this->assertFalse(Carbon::parse('2024-02-29 23:59:59.999999')->isStartOfMonth()); + } + + public function testIsEndOfMonth() + { + $this->assertTrue(Carbon::parse('2024-01-31 23:59:59.999999')->isEndOfMonth()); + $this->assertFalse(Carbon::parse('2024-02-01 00:00:00')->isEndOfMonth()); + $this->assertFalse(Carbon::parse('2024-02-28 23:59:59.999999')->isEndOfMonth()); + $this->assertTrue(Carbon::parse('2024-02-29 00:00:00')->isEndOfMonth()); + $this->assertTrue(Carbon::parse('2024-02-29 23:59:59.999999')->isEndOfMonth()); + $this->assertTrue(Carbon::parse('2024-02-29 23:59:59.999999')->isEndOfMonth(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2024-02-29 23:59:59.999998')->isEndOfMonth(Unit::Microsecond)); + } + + public function testIsStartOfQuarter() + { + $this->assertFalse(Carbon::parse('2024-01-31 23:59:59.999999')->isStartOfQuarter()); + $this->assertTrue(Carbon::parse('2024-01-01 00:00:00')->isStartOfQuarter()); + $this->assertTrue(Carbon::parse('2024-01-01 23:59:59.999999')->isStartOfQuarter()); + $this->assertTrue(Carbon::parse('2024-01-01 00:00:00')->isStartOfQuarter(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2024-01-01 00:00:00.000001')->isStartOfQuarter(Unit::Microsecond)); + $this->assertTrue(Carbon::parse('2024-01-01 00:00:59')->isStartOfQuarter(Unit::Minute)); + $this->assertFalse(Carbon::parse('2024-01-01 00:01:00')->isStartOfQuarter(Unit::Minute)); + $this->assertFalse(Carbon::parse('2024-01-02 23:59:59.999999')->isStartOfQuarter()); + $this->assertFalse(Carbon::parse('2024-03-31 23:59:59.999999')->isStartOfQuarter()); + } + + public function testIsEndOfQuarter() + { + $this->assertTrue(Carbon::parse('2024-12-31 23:59:59.999999')->isEndOfQuarter()); + $this->assertFalse(Carbon::parse('2024-03-01 00:00:00')->isEndOfQuarter()); + $this->assertFalse(Carbon::parse('2024-03-30 23:59:59.999999')->isEndOfQuarter()); + $this->assertTrue(Carbon::parse('2024-03-31 00:00:00')->isEndOfQuarter()); + $this->assertTrue(Carbon::parse('2024-03-31 23:59:59.999999')->isEndOfQuarter()); + $this->assertTrue(Carbon::parse('2024-03-31 23:59:59.999999')->isEndOfQuarter(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2024-03-31 23:59:59.999998')->isEndOfQuarter(Unit::Microsecond)); + } + + public function testIsStartOfYear() + { + $this->assertFalse(Carbon::parse('2024-01-31 23:59:59.999999')->isStartOfYear()); + $this->assertTrue(Carbon::parse('2024-01-01 00:00:00')->isStartOfYear()); + $this->assertTrue(Carbon::parse('2024-01-01 23:59:59.999999')->isStartOfYear()); + $this->assertTrue(Carbon::parse('2024-01-31 00:00:00')->isStartOfYear(Unit::Month)); + $this->assertFalse(Carbon::parse('2024-02-01 00:00:00')->isStartOfYear(Unit::Month)); + $this->assertFalse(Carbon::parse('2024-01-02 23:59:59.999999')->isStartOfYear()); + $this->assertFalse(Carbon::parse('2024-12-31 23:59:59.999999')->isStartOfYear()); + } + + public function testIsEndOfYear() + { + $this->assertTrue(Carbon::parse('2024-12-31 23:59:59.999999')->isEndOfYear()); + $this->assertFalse(Carbon::parse('2024-03-01 00:00:00')->isEndOfYear()); + $this->assertFalse(Carbon::parse('2024-03-30 23:59:59.999999')->isEndOfYear()); + $this->assertTrue(Carbon::parse('2024-12-31 00:00:00')->isEndOfYear()); + $this->assertTrue(Carbon::parse('2024-12-31 23:59:59.999999')->isEndOfYear()); + $this->assertTrue(Carbon::parse('2024-12-31 23:59:59.999999')->isEndOfYear(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2024-12-31 23:59:59.999998')->isEndOfYear(Unit::Microsecond)); + } + + public function testIsStartOfDecade() + { + $this->assertFalse(Carbon::parse('2019-01-31 23:59:59.999999')->isStartOfDecade()); + $this->assertTrue(Carbon::parse('2020-01-01 00:00:00')->isStartOfDecade()); + $this->assertTrue(Carbon::parse('2020-01-01 23:59:59.999999')->isStartOfDecade()); + $this->assertTrue(Carbon::parse('2020-01-31 00:00:00')->isStartOfDecade(Unit::Month)); + $this->assertFalse(Carbon::parse('2020-02-01 00:00:00')->isStartOfDecade(Unit::Month)); + $this->assertFalse(Carbon::parse('2020-01-02 23:59:59.999999')->isStartOfDecade()); + $this->assertFalse(Carbon::parse('2029-12-31 23:59:59.999999')->isStartOfDecade()); + } + + public function testIsEndOfDecade() + { + $this->assertTrue(Carbon::parse('2019-12-31 23:59:59.999999')->isEndOfDecade()); + $this->assertFalse(Carbon::parse('2020-01-01 00:00:00')->isEndOfDecade()); + $this->assertFalse(Carbon::parse('2020-01-30 23:59:59.999999')->isEndOfDecade()); + $this->assertTrue(Carbon::parse('2029-12-31 00:00:00')->isEndOfDecade()); + $this->assertTrue(Carbon::parse('2029-12-31 23:59:59.999999')->isEndOfDecade()); + $this->assertTrue(Carbon::parse('2029-12-31 23:59:59.999999')->isEndOfDecade(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2029-12-31 23:59:59.999998')->isEndOfDecade(Unit::Microsecond)); + } + + public function testIsStartOfCentury() + { + $this->assertFalse(Carbon::parse('2000-01-31 23:59:59.999999')->isStartOfCentury()); + $this->assertTrue(Carbon::parse('2001-01-01 00:00:00')->isStartOfCentury()); + $this->assertTrue(Carbon::parse('2001-01-01 23:59:59.999999')->isStartOfCentury()); + $this->assertTrue(Carbon::parse('2001-01-31 00:00:00')->isStartOfCentury(Unit::Month)); + $this->assertFalse(Carbon::parse('2001-02-01 00:00:00')->isStartOfCentury(Unit::Month)); + $this->assertFalse(Carbon::parse('2001-01-02 23:59:59.999999')->isStartOfCentury()); + $this->assertFalse(Carbon::parse('2100-12-31 23:59:59.999999')->isStartOfCentury()); + } + + public function testIsEndOfCentury() + { + $this->assertTrue(Carbon::parse('2000-12-31 23:59:59.999999')->isEndOfCentury()); + $this->assertFalse(Carbon::parse('2001-01-01 00:00:00')->isEndOfCentury()); + $this->assertFalse(Carbon::parse('2001-01-30 23:59:59.999999')->isEndOfCentury()); + $this->assertTrue(Carbon::parse('2100-12-31 00:00:00')->isEndOfCentury()); + $this->assertTrue(Carbon::parse('2100-12-31 23:59:59.999999')->isEndOfCentury()); + $this->assertTrue(Carbon::parse('2100-12-31 23:59:59.999999')->isEndOfCentury(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('2100-12-31 23:59:59.999998')->isEndOfCentury(Unit::Microsecond)); + } + + public function testIsStartOfMillennium() + { + $this->assertFalse(Carbon::parse('2000-01-31 23:59:59.999999')->isStartOfMillennium()); + $this->assertTrue(Carbon::parse('2001-01-01 00:00:00')->isStartOfMillennium()); + $this->assertTrue(Carbon::parse('2001-01-01 23:59:59.999999')->isStartOfMillennium()); + $this->assertTrue(Carbon::parse('2001-01-31 00:00:00')->isStartOfMillennium(Unit::Month)); + $this->assertFalse(Carbon::parse('2001-02-01 00:00:00')->isStartOfMillennium(Unit::Month)); + $this->assertFalse(Carbon::parse('2001-01-02 23:59:59.999999')->isStartOfMillennium()); + $this->assertFalse(Carbon::parse('3000-12-31 23:59:59.999999')->isStartOfMillennium()); + } + + public function testIsEndOfMillennium() + { + $this->assertTrue(Carbon::parse('2000-12-31 23:59:59.999999')->isEndOfMillennium()); + $this->assertFalse(Carbon::parse('2001-01-01 00:00:00')->isEndOfMillennium()); + $this->assertFalse(Carbon::parse('2001-01-30 23:59:59.999999')->isEndOfMillennium()); + $this->assertTrue(Carbon::parse('3000-12-31 00:00:00')->isEndOfMillennium()); + $this->assertTrue(Carbon::parse('3000-12-31 23:59:59.999999')->isEndOfMillennium()); + $this->assertTrue(Carbon::parse('3000-12-31 23:59:59.999999')->isEndOfMillennium(Unit::Microsecond)); + $this->assertFalse(Carbon::parse('3000-12-31 23:59:59.999998')->isEndOfMillennium(Unit::Microsecond)); + } + public function testIsMidnight() { $this->assertTrue(Carbon::parse('00:00:00')->isMidnight()); From fc61820c232424f9bf7358ff4e426c1469608437 Mon Sep 17 00:00:00 2001 From: kylekatarnls Date: Wed, 24 Jul 2024 00:00:12 +0200 Subject: [PATCH 5/7] Add startOfMillisecond and endOfMillisecond --- src/Carbon/Traits/Boundaries.php | 34 +++++++++++++ tests/Carbon/IsTest.php | 84 ++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/src/Carbon/Traits/Boundaries.php b/src/Carbon/Traits/Boundaries.php index e4f0abc603..b21b9c54ef 100644 --- a/src/Carbon/Traits/Boundaries.php +++ b/src/Carbon/Traits/Boundaries.php @@ -391,6 +391,40 @@ public function endOfSecond(): static return $this->setTime($this->hour, $this->minute, $this->second, static::MICROSECONDS_PER_SECOND - 1); } + /** + * Modify to start of current millisecond, microseconds such as 12345 become 123000 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function startOfMillisecond(): static + { + $millisecond = (int) floor($this->micro / 1000); + + return $this->setTime($this->hour, $this->minute, $this->second, $millisecond * 1000); + } + + /** + * Modify to end of current millisecond, microseconds such as 12345 become 123999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function endOfMillisecond(): static + { + $millisecond = (int) floor($this->micro / 1000); + + return $this->setTime($this->hour, $this->minute, $this->second, $millisecond * 1000 + 999); + } + /** * Modify to start of current given unit. * diff --git a/tests/Carbon/IsTest.php b/tests/Carbon/IsTest.php index ca26ef185d..11515edac5 100644 --- a/tests/Carbon/IsTest.php +++ b/tests/Carbon/IsTest.php @@ -815,6 +815,90 @@ public function testIsSaturday() $this->assertFalse(Carbon::now()->addMonth()->previous(Carbon::SUNDAY)->isSaturday()); } + public function testIsStartOfMillisecond() + { + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.999999')->isStartOfMillisecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45')->isStartOfMillisecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.123')->isStartOfMillisecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.000001')->isStartOfMillisecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.999999')->isStartOfMillisecond()); + } + + public function testIsEndOfMillisecond() + { + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.999999')->isEndOfMillisecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45')->isEndOfMillisecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.999999')->isEndOfMillisecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.999998')->isEndOfMillisecond()); + } + + public function testIsStartOfSecond() + { + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.999999')->isStartOfSecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45')->isStartOfSecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.000001')->isStartOfSecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.999999')->isStartOfSecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45')->isStartOfSecond(Unit::Millisecond)); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.000999')->isStartOfSecond(Unit::Millisecond)); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.001')->isStartOfSecond(Unit::Millisecond)); + } + + public function testIsEndOfSecond() + { + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.999999')->isEndOfSecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45')->isEndOfSecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.999999')->isEndOfSecond()); + $this->assertFalse(Carbon::parse('2025-01-30 21:33:45.999998')->isEndOfSecond()); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.999998')->isEndOfSecond(Unit::Second)); + $this->assertTrue(Carbon::parse('2025-01-30 21:33:45.123456')->isEndOfSecond(Unit::Second)); + } + + public function testIsStartOfMinute() + { + $this->assertFalse(Carbon::parse('2025-01-30 21:33:59.999999')->isStartOfMinute()); + $this->assertTrue(Carbon::parse('2025-01-01 22:34:00')->isStartOfMinute()); + $this->assertFalse(Carbon::parse('2025-01-01 22:34:00.000001')->isStartOfMinute()); + $this->assertFalse(Carbon::parse('2025-01-01 21:33:59.999999')->isStartOfMinute()); + $this->assertTrue(Carbon::parse('2025-01-31 22:34:00')->isStartOfMinute(Unit::Second)); + $this->assertTrue(Carbon::parse('2025-01-31 22:34:00.999999')->isStartOfMinute(Unit::Second)); + $this->assertFalse(Carbon::parse('2025-02-01 22:34:01')->isStartOfMinute(Unit::Second)); + $this->assertFalse(Carbon::parse('2025-01-02 21:33:59.999999')->isStartOfMinute()); + $this->assertFalse(Carbon::parse('2025-12-31 21:33:59.999999')->isStartOfMinute()); + } + + public function testIsEndOfMinute() + { + $this->assertTrue(Carbon::parse('2024-05-16 12:34:59.999999')->isEndOfMinute()); + $this->assertFalse(Carbon::parse('2024-05-16 12:35:00')->isEndOfMinute()); + $this->assertTrue(Carbon::parse('2024-05-16 12:34:59.999999')->isEndOfMinute()); + $this->assertFalse(Carbon::parse('2024-05-16 12:34:59.999998')->isEndOfMinute()); + $this->assertTrue(Carbon::parse('2024-05-16 12:34:59.999998')->isEndOfMinute(Unit::Second)); + $this->assertTrue(Carbon::parse('2024-05-16 12:34:59.123456')->isEndOfMinute(Unit::Second)); + } + + public function testIsStartOfHour() + { + $this->assertFalse(Carbon::parse('2025-01-30 21:59:59.999999')->isStartOfHour()); + $this->assertTrue(Carbon::parse('2025-01-01 22:00:00')->isStartOfHour()); + $this->assertFalse(Carbon::parse('2025-01-01 22:00:00.000001')->isStartOfHour()); + $this->assertFalse(Carbon::parse('2025-01-01 21:59:59.999999')->isStartOfHour()); + $this->assertTrue(Carbon::parse('2025-01-31 22:00:00')->isStartOfHour(Unit::Second)); + $this->assertTrue(Carbon::parse('2025-01-31 22:00:00.999999')->isStartOfHour(Unit::Second)); + $this->assertFalse(Carbon::parse('2025-02-01 22:00:01')->isStartOfHour(Unit::Second)); + $this->assertFalse(Carbon::parse('2025-01-02 21:59:59.999999')->isStartOfHour()); + $this->assertFalse(Carbon::parse('2025-12-31 21:59:59.999999')->isStartOfHour()); + } + + public function testIsEndOfHour() + { + $this->assertTrue(Carbon::parse('2024-05-16 23:59:59.999999')->isEndOfHour()); + $this->assertFalse(Carbon::parse('2024-05-16 00:00:00')->isEndOfHour()); + $this->assertTrue(Carbon::parse('2024-05-16 23:59:59.999999')->isEndOfHour()); + $this->assertFalse(Carbon::parse('2024-05-16 23:59:59.999998')->isEndOfHour()); + $this->assertTrue(Carbon::parse('2024-05-16 23:59:59.999998')->isEndOfHour(Unit::Second)); + $this->assertTrue(Carbon::parse('2024-05-16 23:59:59.123456')->isEndOfHour(Unit::Second)); + } + public function testIsStartOfDay() { $this->assertTrue(Carbon::parse('00:00:00')->isStartOfDay(false)); From 213f08e569431ce4b31a35849d67405f09a6c48e Mon Sep 17 00:00:00 2001 From: kylekatarnls Date: Wed, 24 Jul 2024 10:04:29 +0200 Subject: [PATCH 6/7] Update documentation --- src/Carbon/CarbonInterface.php | 44 ++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Carbon/CarbonInterface.php b/src/Carbon/CarbonInterface.php index 03ce865861..2d7726936b 100644 --- a/src/Carbon/CarbonInterface.php +++ b/src/Carbon/CarbonInterface.php @@ -1892,6 +1892,18 @@ public function endOfHour(): static; */ public function endOfMillennium(); + /** + * Modify to end of current millisecond, microseconds such as 12345 become 123999 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->endOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function endOfMillisecond(): static; + /** * Modify to end of current minute, seconds become 59 * @@ -2663,7 +2675,7 @@ public function isCurrentUnit(string $unit): bool; public function isDayOfWeek($dayOfWeek): bool; /** - * Determines if the instance is end of century (first day by default but interval can be customized). + * Determines if the instance is end of century (last day by default but interval can be customized). */ public function isEndOfCentury(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; @@ -2690,42 +2702,42 @@ public function isEndOfCentury(Unit|DateInterval|Closure|CarbonConverterInterfac public function isEndOfDay(Unit|DateInterval|Closure|CarbonConverterInterface|string|bool $checkMicroseconds = false, Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of decade (first day by default but interval can be customized). + * Determines if the instance is end of decade (last day by default but interval can be customized). */ public function isEndOfDecade(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of hour (first microsecond by default but interval can be customized). + * Determines if the instance is end of hour (last microsecond by default but interval can be customized). */ public function isEndOfHour(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of millennium (first day by default but interval can be customized). + * Determines if the instance is end of millennium (last day by default but interval can be customized). */ public function isEndOfMillennium(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of millisecond (first microsecond by default but interval can be customized). + * Determines if the instance is end of millisecond (last microsecond by default but interval can be customized). */ public function isEndOfMillisecond(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of minute (first microsecond by default but interval can be customized). + * Determines if the instance is end of minute (last microsecond by default but interval can be customized). */ public function isEndOfMinute(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of month (first day by default but interval can be customized). + * Determines if the instance is end of month (last day by default but interval can be customized). */ public function isEndOfMonth(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of quarter (first day by default but interval can be customized). + * Determines if the instance is end of quarter (last day by default but interval can be customized). */ public function isEndOfQuarter(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; /** - * Determines if the instance is end of second (first microsecond by default but interval can be customized). + * Determines if the instance is end of second (last microsecond by default but interval can be customized). */ public function isEndOfSecond(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; @@ -2759,7 +2771,7 @@ public function isEndOfUnit(Unit $unit, Unit|DateInterval|Closure|CarbonConverte public function isEndOfWeek(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null, WeekDay|int|null $weekEndsAt = null): bool; /** - * Determines if the instance is end of year (first day by default but interval can be customized). + * Determines if the instance is end of year (last day by default but interval can be customized). */ public function isEndOfYear(Unit|DateInterval|Closure|CarbonConverterInterface|string|null $interval = null): bool; @@ -4101,6 +4113,18 @@ public function startOfHour(): static; */ public function startOfMillennium(); + /** + * Modify to start of current millisecond, microseconds such as 12345 become 123000 + * + * @example + * ``` + * echo Carbon::parse('2018-07-25 12:45:16.334455') + * ->startOfSecond() + * ->format('H:i:s.u'); + * ``` + */ + public function startOfMillisecond(): static; + /** * Modify to start of current minute, seconds become 0 * From 8559755f1053c74caf0de5b52e8737c2916030f2 Mon Sep 17 00:00:00 2001 From: kylekatarnls Date: Wed, 24 Jul 2024 10:11:40 +0200 Subject: [PATCH 7/7] Fix PHPStan exception --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 90bf85054e..d4f083fb97 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -30,7 +30,7 @@ parameters: - '#should return (\S*)static\(Carbon\\CarbonInterval\)(\|null)? but returns Carbon\\CarbonInterval(\|null)?\.$#' - '#^Call to an undefined method DateInterval::(spec|optimize)\(\)\.$#' - '#^Property Carbon\\Carbon::\$timezone \(Carbon\\CarbonTimeZone\) does not accept string\.$#' - - '#^Method class@anonymous/tests/Carbon/TestingAidsTest\.php:\d+::modify\(\) should return class@anonymous/tests/Carbon/TestingAidsTest\.php:\d+ but returns \(DateTimeImmutable\|false\)\.$#' + - '#^Method class@anonymous/tests/Carbon/TestingAidsTest\.php:\d+::modify\(\) should return class@anonymous/tests/Carbon/TestingAidsTest\.php:\d+ but returns \(?DateTimeImmutable#' - message: '#^Undefined variable: \$this$#' paths: