From 5e077c86883a420f454f22a31ae66a9f9a162e1c Mon Sep 17 00:00:00 2001 From: upstream Date: Thu, 3 Sep 2026 14:39:22 +0200 Subject: [PATCH] Upstream spatie/period state of the touched files before b1e3380fb0 --- src/PeriodTraits/PeriodGetters.php | 142 ++++++++++++++++++++++++++ tests/BoundaryTest.php | 58 +++++++++++ tests/Datasets/Dates.php | 155 +++++++++++++++++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 src/PeriodTraits/PeriodGetters.php create mode 100644 tests/BoundaryTest.php create mode 100644 tests/Datasets/Dates.php diff --git a/src/PeriodTraits/PeriodGetters.php b/src/PeriodTraits/PeriodGetters.php new file mode 100644 index 0000000..7ec6a7e --- /dev/null +++ b/src/PeriodTraits/PeriodGetters.php @@ -0,0 +1,142 @@ +boundaries->startIncluded(); + } + + public function isStartExcluded(): bool + { + return $this->boundaries->startExcluded(); + } + + public function isEndIncluded(): bool + { + return $this->boundaries->endIncluded(); + } + + public function isEndExcluded(): bool + { + return $this->boundaries->endExcluded(); + } + + public function start(): DateTimeImmutable + { + return $this->start; + } + + public function includedStart(): DateTimeImmutable + { + return $this->includedStart; + } + + public function end(): DateTimeImmutable + { + return $this->end; + } + + public function includedEnd(): DateTimeImmutable + { + return $this->includedEnd; + } + + public function ceilingEnd(?Precision $precision = null): DateTimeImmutable + { + $precision ??= $this->precision; + + if ($precision->higherThan($this->precision)) { + throw CannotCeilLowerPrecision::precisionIsLower($this->precision, $precision); + } + + return $this->precision->ceilDate($this->includedEnd, $precision); + } + + public function length(): int + { + // Length of month and year are not fixed, so we can't predict the length without iterate + // TODO: maybe we can use cal_days_in_month ? + if ($this->precision->equals(Precision::MONTH(), Precision::YEAR())) { + return iterator_count($this); + } + + if ($this->precision->equals(Precision::HOUR(), Precision::MINUTE(), Precision::SECOND())) { + $length = abs($this->includedEnd()->getTimestamp() - $this->includedStart()->getTimestamp()); + + if ($this->precision->equals(Precision::SECOND())) { + return $length + 1; + } + + $length = floor($length / 60); + + if ($this->precision->equals(Precision::MINUTE())) { + return $length + 1; + } + + return floor($length / 60) + 1; + } + + return $this->includedStart()->diff($this->includedEnd())->days + 1; + } + + public function duration(): PeriodDuration + { + return $this->duration; + } + + public function precision(): Precision + { + return $this->precision; + } + + public function boundaries(): Boundaries + { + return $this->boundaries; + } + + public function asString(): string + { + if (! isset($this->asString)) { + $this->asString = $this->resolveString(); + } + + return $this->asString; + } + + private function resolveString(): string + { + $string = ''; + + if ($this->isStartIncluded()) { + $string .= '['; + } else { + $string .= '('; + } + + $string .= $this->start()->format($this->precision->dateFormat()); + + $string .= ','; + + $string .= $this->end()->format($this->precision->dateFormat()); + + if ($this->isEndIncluded()) { + $string .= ']'; + } else { + $string .= ')'; + } + + return $string; + } +} diff --git a/tests/BoundaryTest.php b/tests/BoundaryTest.php new file mode 100644 index 0000000..2160dfd --- /dev/null +++ b/tests/BoundaryTest.php @@ -0,0 +1,58 @@ +isStartExcluded())->toBeFalse(); + expect($period->isEndExcluded())->toBeFalse(); +}); + +it('exclude start', function () { + $period = Period::make('2018-01-01', '2018-01-31', boundaries: Boundaries::EXCLUDE_START()); + + expect($period->isStartExcluded())->toBeTrue(); + expect($period->isEndExcluded())->toBeFalse(); +}); + +it('exclude end', function () { + $period = Period::make('2018-01-01', '2018-01-31', boundaries: Boundaries::EXCLUDE_END()); + + expect($period->isStartExcluded())->toBeFalse(); + expect($period->isEndExcluded())->toBeTrue(); +}); + +it('exclude all', function () { + $period = Period::make('2018-01-01', '2018-01-31', boundaries: Boundaries::EXCLUDE_ALL()); + + expect($period->isStartExcluded())->toBeTrue(); + expect($period->isEndExcluded())->toBeTrue(); +}); + +it('length with boundaries', function ($expectedAmount, Period $period) { + expect($period->length())->toEqual($expectedAmount); +})->with('periods_with_amounts_of_included_dates'); + +it('overlap with excluded boundaries', function () { + $a = Period::make('2018-01-01', '2018-01-05', boundaries: Boundaries::EXCLUDE_END()); + $b = Period::make('2018-01-05', '2018-01-10'); + expect($a->overlapsWith($b))->toBeFalse(); + + $a = Period::make('2018-01-01', '2018-01-05'); + $b = Period::make('2018-01-05', '2018-01-10', boundaries: Boundaries::EXCLUDE_START()); + expect($a->overlapsWith($b))->toBeFalse(); + + $a = Period::make('2018-01-01', '2018-01-05'); + $b = Period::make('2018-01-05', '2018-01-10'); + expect($a->overlapsWith($b))->toBeTrue(); +}); + +it('subtract with boundaries', function (string $period, string $subtract, string $result) { + expect(Period::fromString($period)->subtract(Period::fromString($subtract))[0]->asString())->toEqual($result); +})->with('boundaries_for_subtract'); + +it('overlap with boundaries', function (string $period, string $overlap, string $result) { + expect(Period::fromString($period)->overlap(Period::fromString($overlap))->asString())->toEqual($result); +})->with('boundaries_for_overlap'); diff --git a/tests/Datasets/Dates.php b/tests/Datasets/Dates.php new file mode 100644 index 0000000..ef9154a --- /dev/null +++ b/tests/Datasets/Dates.php @@ -0,0 +1,155 @@ +endOfSecond(), + ]; + + yield [ + Period::make('2018-01-01 11:30:15', '2018-01-15 11:30:15', Precision::MINUTE()), + Precision::MINUTE(), + Carbon::make('2018-01-15 11:30:15')->endOfMinute(), + ]; + + yield [ + Period::make('2018-01-01 11:30:15', '2018-01-15 11:30:15', Precision::HOUR()), + Precision::HOUR(), + Carbon::make('2018-01-15 11:30:15')->endOfHour(), + ]; + + yield [ + Period::make('2018-01-01', '2018-01-15', Precision::DAY()), + Precision::DAY(), + Carbon::make('2018-01-15')->endOfDay(), + ]; + + yield [ + Period::make('2018-01-01', '2018-01-15', Precision::MONTH()), + Precision::MONTH(), + Carbon::make('2018-01-15')->endOfMonth(), + ]; + + yield [ + Period::make('2018-01-01', '2018-01-15', Precision::YEAR()), + Precision::YEAR(), + Carbon::make('2018-01-15')->endOfYear(), + ]; + + // Test defaults to same precision as period + yield [ + Period::make('2018-01-01', '2018-01-15', Precision::MONTH()), + null, + Carbon::make('2018-01-15')->endOfMonth(), + ]; + + // Test higher precision + yield [ + Period::make('2018-01-01', '2018-01-15', Precision::DAY()), + Precision::MONTH(), + Carbon::make('2018-01-15')->endOfMonth(), + ]; + + // Test exclusive period + yield [ + Period::make('2018-01-01', '2018-01-15', Precision::DAY(), Boundaries::EXCLUDE_END()), + Precision::DAY(), + Carbon::make('2018-01-15')->subDay()->endOfDay(), + ]; +}); + +dataset('rounding_dates', function () { + return [ + [Precision::YEAR(), '2018-01-01 00:00:00', '2018-01-01 00:00:00'], + [Precision::MONTH(), '2018-02-01 00:00:00', '2018-03-01 00:00:00'], + [Precision::DAY(), '2018-02-05 00:00:00', '2018-03-05 00:00:00'], + [Precision::HOUR(), '2018-02-05 11:00:00', '2018-03-05 11:00:00'], + [Precision::MINUTE(), '2018-02-05 11:11:00', '2018-03-05 11:11:00'], + [Precision::SECOND(), '2018-02-05 11:11:11', '2018-03-05 11:11:11'], + ]; +});