diff --git a/src/Carbon/Traits/Date.php b/src/Carbon/Traits/Date.php index 43ca171b6c..65433bbca8 100644 --- a/src/Carbon/Traits/Date.php +++ b/src/Carbon/Traits/Date.php @@ -2967,6 +2967,6 @@ private function mutateIfMutable(CarbonInterface $date): CarbonInterface { return $this instanceof DateTimeImmutable ? $this - : $this->modify('@' . $date->rawFormat('U.u'))->setTimezone($date->getTimezone()); + : $this->modify('@'.$date->rawFormat('U.u'))->setTimezone($date->getTimezone()); } } diff --git a/tests/Carbon/SettersTest.php b/tests/Carbon/SettersTest.php index 11017205a4..e816339574 100644 --- a/tests/Carbon/SettersTest.php +++ b/tests/Carbon/SettersTest.php @@ -658,7 +658,7 @@ public function testSetUnitNoOverflow() 'microsecond' => 1000000, ]; $valueUnit = array_keys($units)[mt_rand(0, \count($units) - 1)]; - $value = mt_rand() > 0.5 ? + $value = mt_rand(0, 1) === 1 ? mt_rand(-9999, 9999) : mt_rand(-60, 60); @@ -803,7 +803,7 @@ public function testAddUnitNoOverflow() 'microsecond' => 1000000, ]; $valueUnit = array_keys($units)[mt_rand(0, \count($units) - 1)]; - $value = mt_rand() > 0.5 ? + $value = mt_rand(0, 1) === 1 ? mt_rand(-9999, 9999) : mt_rand(-60, 60); @@ -840,8 +840,7 @@ public function testAddUnitNoOverflow() } if ($value === $date->$valueUnit || $modulo === $date->$valueUnit || - (method_exists($date, "diffInReal$unit") && -$date->{"diffInReal$unit"}($original, false) === $value) || - -((int) round($date->{"diffIn$unit"}($original, false))) === $value + (method_exists($date, "diffInReal$unit") && -$date->{"diffInReal$unit"}($original, false) === $value) ) { $results['current']++; @@ -860,6 +859,33 @@ public function testAddUnitNoOverflow() continue; } + $currentDiff = -((int) round($date->{"diffIn$unit"}($original, false))); + + if ($currentDiff === $value) { + $results['current']++; + + continue; + } + + $delta = ($currentDiff - $value); + + if ($valueUnit === 'hour') { + $diff = $this->getOffsetChangeOfTheDay($date) ?: $this->getOffsetChangeOfTheDay($original); + + if ($diff !== 0) { + $sign = $diff < 0 ? -1 : 1; + $diff = abs($diff); + $minutes = $diff % 100; + $hours = (int) ($sign * (floor($diff / 100) + $minutes / 60)); + + if ($delta === -$hours) { + $results['current']++; + + continue; + } + } + } + $this->failOperation( $original, $date, @@ -912,7 +938,7 @@ public function testSubUnitNoOverflow() 'microsecond' => 1000000, ]; $valueUnit = array_keys($units)[mt_rand(0, \count($units) - 1)]; - $value = mt_rand() > 0.5 ? + $value = mt_rand(0, 1) === 1 ? mt_rand(-9999, 9999) : mt_rand(-60, 60); @@ -951,8 +977,7 @@ public function testSubUnitNoOverflow() if ($value === $date->$valueUnit || $modulo === $date->$valueUnit || - (method_exists($date, "diffInReal$unit") && $value === $date->{"diffInReal$unit"}($original, false)) || - ((int) round($date->{"diffIn$unit"}($original, false))) === $value + (method_exists($date, "diffInReal$unit") && $value === $date->{"diffInReal$unit"}($original, false)) ) { $results['current']++; @@ -986,6 +1011,33 @@ public function testSubUnitNoOverflow() continue; } + $currentDiff = (int) round($date->{"diffIn$unit"}($original, false)); + + if ($currentDiff === $value) { + $results['current']++; + + continue; + } + + $delta = ($currentDiff - $value); + + if ($valueUnit === 'hour') { + $diff = $this->getOffsetChangeOfTheDay($date) ?: $this->getOffsetChangeOfTheDay($original); + + if ($diff !== 0) { + $sign = $diff < 0 ? -1 : 1; + $diff = abs($diff); + $minutes = $diff % 100; + $hours = (int) ($sign * (floor($diff / 100) + $minutes / 60)); + + if ($delta === $hours) { + $results['current']++; + + continue; + } + } + } + $this->failOperation( $original, $date, @@ -998,6 +1050,8 @@ public function testSubUnitNoOverflow() $unit, $modulo, $value, + $hours ?? null, + $delta ?? null, ); } @@ -1011,13 +1065,32 @@ public function testSubUnitNoOverflow() public function testOverflowInDst() { - $date = Carbon::create(2335, 11, 3, 1, 30, 50.138159); - $date->subUnitNoOverflow('year', 5668, 'second'); + $date = Carbon::create(2335, 11, 3, 1, 30, 50.138159) + ->subUnitNoOverflow('year', 5668, 'second'); $this->assertSame( '2335-11-03 01:30:50.000000 America/Toronto -0400', $date->format('Y-m-d H:i:s.u e O'), ); + + $date = Carbon::parse('2020-10-15 03:22:57.442989', 'America/Toronto')->hours(-5302); + + $diff = (int) ($date->copy()->startOfDay()->format('O') - $date->copy()->endOfDay()->format('O')); + $sign = $diff < 0 ? -1 : 1; + $diff = abs($diff); + $minutes = $diff % 100; + $hours = $sign * (floor($diff / 100) + $minutes / 60); + + $diffInHours = $date->diffInHours( + Carbon::parse('2020-10-15 03:22:57.442989', 'America/Toronto'), + ); + + $this->assertSame(5305.0 + $hours, $diffInHours); + } + + private function getOffsetChangeOfTheDay(Carbon $date): int + { + return (int) ($date->copy()->startOfDay()->format('O') - $date->copy()->endOfDay()->format('O')); } /** @@ -1034,7 +1107,9 @@ private function failOperation( string $overflowUnit, string $unit, int $modulo, - int $variableValue + int $variableValue, + ?int $hours = null, + ?int $delta = null, ): void { throw new Exception(implode("\n", [ 'Unhandled result for: '. @@ -1049,6 +1124,9 @@ private function failOperation( 'Nor '.$end->$valueUnit." (from $end)", "Nor $value (from value)", "Nor $modulo (from modulo)", + ...($hours !== null ? [ + "Not matching diff (hours = $hours vs delta = " . ($delta ?? 'null') . ')', + ] : []), method_exists($date, "diffInReal$unit") ? "diffInReal$unit() exists and returns ".$date->{"diffInReal$unit"}($original, false) ." while expecting $variableValue"