From a449056f05652c839d70b72490858570d1c46b12 Mon Sep 17 00:00:00 2001 From: dualfroz Date: Thu, 24 Sep 2026 09:02:10 +0200 Subject: [PATCH] fix: carry the sign on the interval instead of on each unit --- src/Duration.php | 13 ++++++------- src/DurationTest.php | 22 +++++++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/Duration.php b/src/Duration.php index d0b2f32..f6931d2 100644 --- a/src/Duration.php +++ b/src/Duration.php @@ -188,14 +188,13 @@ final class Duration { $units += ['hour' => '0', 'minute' => '0', 'second' => '0', 'fraction' => '0', 'sign' => '+']; $units['fraction'] = str_pad($units['fraction'] ?? '000000', 6, '0'); - $sign = '-' === $units['sign'] ? '-' : ''; - return self::fromDateString( - $sign.$units['hour'].' hours ' - .$sign.$units['minute'].' minutes ' - .$sign.$units['second'].' seconds ' - .$sign.$units['fraction'].' microseconds' - ); + $instance = self::fromDateString($units['hour'].' hours '.$units['minute'].' minutes '.$units['second'].' seconds '.$units['fraction'].' microseconds'); + if ('-' === $units['sign']) { + $instance->dateInterval->invert = 1; + } + + return $instance; } /** diff --git a/src/DurationTest.php b/src/DurationTest.php index 12c03db..74e92ac 100644 --- a/src/DurationTest.php +++ b/src/DurationTest.php @@ -24,6 +24,7 @@ namespace League\Period; use DateInterval; use DateTime; +use DateTimeImmutable; use DateTimeInterface; use DateTimeZone; use InvalidArgumentException; @@ -49,7 +50,8 @@ final class DurationTest extends TestCase { $interval = $duration->dateInterval; - $date = ['P']; + // the sign belongs to the whole duration: -PT12M28S, not PT-12M-28S + $date = [1 === $interval->invert ? '-P' : 'P']; if (0 !== $interval->y) { $date[] = '%yY'; } @@ -242,7 +244,7 @@ final class DurationTest extends TestCase ], 'negative chrono' => [ 'chronometer' => '-12:28', - 'expected' => 'PT-12M-28S', + 'expected' => '-PT12M28S', ], ]; } @@ -282,11 +284,11 @@ final class DurationTest extends TestCase ], 'negative chrono' => [ 'chronometer' => '-12:28', - 'expected' => 'PT-12H-28M', + 'expected' => '-PT12H28M', ], 'negative chrono with seconds' => [ 'chronometer' => '-01:02:28', - 'expected' => 'PT-1H-2M-28S', + 'expected' => '-PT1H2M28S', ], ]; } @@ -295,11 +297,17 @@ final class DurationTest extends TestCase * A sign applies to the whole duration, not just the hour. It used to be * lost when the hour was zero, and to negate only the hour otherwise. */ + /** + * @param 'fromChronoString'|'fromTimeString' $method + */ #[DataProvider('signedDurationProvider')] public function testSignedStringsApplySignToWholeDuration(string $method, string $input, string $reference, string $expected): void { - $duration = Duration::$method($input); - $date = new \DateTimeImmutable($reference); + $duration = match ($method) { + 'fromChronoString' => Duration::fromChronoString($input), + 'fromTimeString' => Duration::fromTimeString($input), + }; + $date = new DateTimeImmutable($reference); self::assertSame( $expected, @@ -308,7 +316,7 @@ final class DurationTest extends TestCase } /** - * @return iterable + * @return iterable */ public static function signedDurationProvider(): iterable {