fix: carry the sign on the interval instead of on each unit

This commit is contained in:
dualfroz committed 2026-09-24 09:02:10 +02:00
1 parent 23696d9b4c
commit a449056f05
2 files changed
+21 -14

No files matched your search

+6 -7
View File
@@ -188,14 +188,13 @@ final class Duration
{ {
$units += ['hour' => '0', 'minute' => '0', 'second' => '0', 'fraction' => '0', 'sign' => '+']; $units += ['hour' => '0', 'minute' => '0', 'second' => '0', 'fraction' => '0', 'sign' => '+'];
$units['fraction'] = str_pad($units['fraction'] ?? '000000', 6, '0'); $units['fraction'] = str_pad($units['fraction'] ?? '000000', 6, '0');
$sign = '-' === $units['sign'] ? '-' : '';
return self::fromDateString( $instance = self::fromDateString($units['hour'].' hours '.$units['minute'].' minutes '.$units['second'].' seconds '.$units['fraction'].' microseconds');
$sign.$units['hour'].' hours ' if ('-' === $units['sign']) {
.$sign.$units['minute'].' minutes ' $instance->dateInterval->invert = 1;
.$sign.$units['second'].' seconds ' }
.$sign.$units['fraction'].' microseconds'
); return $instance;
} }
/** /**
+15 -7
View File
@@ -24,6 +24,7 @@ namespace League\Period;
use DateInterval; use DateInterval;
use DateTime; use DateTime;
use DateTimeImmutable;
use DateTimeInterface; use DateTimeInterface;
use DateTimeZone; use DateTimeZone;
use InvalidArgumentException; use InvalidArgumentException;
@@ -49,7 +50,8 @@ final class DurationTest extends TestCase
{ {
$interval = $duration->dateInterval; $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) { if (0 !== $interval->y) {
$date[] = '%yY'; $date[] = '%yY';
} }
@@ -242,7 +244,7 @@ final class DurationTest extends TestCase
], ],
'negative chrono' => [ 'negative chrono' => [
'chronometer' => '-12:28', 'chronometer' => '-12:28',
'expected' => 'PT-12M-28S', 'expected' => '-PT12M28S',
], ],
]; ];
} }
@@ -282,11 +284,11 @@ final class DurationTest extends TestCase
], ],
'negative chrono' => [ 'negative chrono' => [
'chronometer' => '-12:28', 'chronometer' => '-12:28',
'expected' => 'PT-12H-28M', 'expected' => '-PT12H28M',
], ],
'negative chrono with seconds' => [ 'negative chrono with seconds' => [
'chronometer' => '-01:02:28', '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 * 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. * lost when the hour was zero, and to negate only the hour otherwise.
*/ */
/**
* @param 'fromChronoString'|'fromTimeString' $method
*/
#[DataProvider('signedDurationProvider')] #[DataProvider('signedDurationProvider')]
public function testSignedStringsApplySignToWholeDuration(string $method, string $input, string $reference, string $expected): void public function testSignedStringsApplySignToWholeDuration(string $method, string $input, string $reference, string $expected): void
{ {
$duration = Duration::$method($input); $duration = match ($method) {
$date = new \DateTimeImmutable($reference); 'fromChronoString' => Duration::fromChronoString($input),
'fromTimeString' => Duration::fromTimeString($input),
};
$date = new DateTimeImmutable($reference);
self::assertSame( self::assertSame(
$expected, $expected,
@@ -308,7 +316,7 @@ final class DurationTest extends TestCase
} }
/** /**
* @return iterable<string, array{method:string, input:string, reference:string, expected:string}> * @return iterable<string, array{method:'fromChronoString'|'fromTimeString', input:string, reference:string, expected:string}>
*/ */
public static function signedDurationProvider(): iterable public static function signedDurationProvider(): iterable
{ {