diff --git a/src/TimeSpan.php b/src/TimeSpan.php index d48103f..edcabb7 100644 --- a/src/TimeSpan.php +++ b/src/TimeSpan.php @@ -48,25 +48,20 @@ public static function fromNanoseconds(int|float $nanoseconds): self return new self($nanoseconds); } - $nanoseconds = \sprintf('%.0f', round($nanoseconds)); + $nanoseconds = round($nanoseconds); - if ($nanoseconds > 0 && self::comparePositiveNumericStrings($nanoseconds, (string) PHP_INT_MAX) > 0 - || $nanoseconds < 0 && self::compareNegativeNumericStrings($nanoseconds, (string) PHP_INT_MIN) < 0 - ) { + if (self::isOutOfBounds($nanoseconds)) { throw new \OutOfBoundsException('The specified time span cannot be expressed as integer nanoseconds due to overflow.'); } return new self((int) $nanoseconds); } - private static function comparePositiveNumericStrings(string $a, string $b): int + private static function isOutOfBounds(float $nanoseconds): bool { - return \strlen($a) <=> \strlen($b) ?: strcmp($a, $b); - } - - private static function compareNegativeNumericStrings(string $a, string $b): int - { - return -(\strlen($a) <=> \strlen($b) ?: strcmp($a, $b)); + return !is_finite($nanoseconds) + || $nanoseconds >= ($bound = 2 ** 63) + || $nanoseconds < -$bound; } public static function fromMicroseconds(int|float $microseconds): self diff --git a/tests/TimeSpanTest.php b/tests/TimeSpanTest.php index e105c63..e1b7d5a 100644 --- a/tests/TimeSpanTest.php +++ b/tests/TimeSpanTest.php @@ -208,6 +208,7 @@ public function testConstructorWithoutArgs(): void #[TestWith([100.999_99, 101])] #[TestWith([PHP_INT_MAX, PHP_INT_MAX])] #[TestWith([PHP_INT_MIN, PHP_INT_MIN])] + #[TestWith([PHP_INT_MIN - 1_024, PHP_INT_MIN])] #[TestWith([9_223_372_036_854_775_000.0, 9_223_372_036_854_774_784])] #[TestWith([-9_223_372_036_854_775_000.0, -9_223_372_036_854_774_784])] public function testFromNanoseconds(int|float $nanoseconds, int $expected): void @@ -322,6 +323,8 @@ public function testFromDaysPHPSince84(float $days, int $expected): void #[TestWith([106_751.991_2])] #[TestWith([-106_752])] #[TestWith([-106_751.991_2])] + #[TestWith([NAN])] + #[TestWith([INF])] public function testFromDaysThrowsOutOfBounds(int|float $days): void { $this->expectException(\OutOfBoundsException::class); @@ -330,6 +333,16 @@ public function testFromDaysThrowsOutOfBounds(int|float $days): void TimeSpan::fromDays($days); } + #[TestWith([PHP_INT_MAX + 1])] + #[TestWith([PHP_INT_MIN - 1_025])] + public function testFromNanosecondsThrowsOutOfBounds(float $nanoseconds): void + { + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage('The specified time span cannot be expressed as integer nanoseconds due to overflow.'); + + TimeSpan::fromNanoseconds($nanoseconds); + } + #[TestWith([100_000, 100.0, 100])] #[TestWith([100_100, 100.1, 100])] #[TestWith([100_500, 100.5, 101])]