From c4c7829749b0667c35e417a8ad67ffcafeba9955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 8 Aug 2019 19:35:41 +0200 Subject: [PATCH] Fix negative DateInterval --- Normalizer/DateIntervalNormalizer.php | 28 ++++++++++++++++-- .../Normalizer/DateIntervalNormalizerTest.php | 29 ++++++++++++++++--- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 56527a8eb4ecf..bd43091bf1ac4 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -29,7 +29,7 @@ class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterfa /** * @param string $format */ - public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS') + public function __construct($format = '%rP%yY%mM%dDT%hH%iM%sS') { $this->format = $format; } @@ -76,12 +76,34 @@ public function denormalize($data, $class, $format = null, array $context = []) $dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format; - $valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/'; + $signPattern = ''; + switch (substr($dateIntervalFormat, 0, 2)) { + case '%R': + $signPattern = '[-+]'; + $dateIntervalFormat = substr($dateIntervalFormat, 2); + break; + case '%r': + $signPattern = '-?'; + $dateIntervalFormat = substr($dateIntervalFormat, 2); + break; + } + $valuePattern = '/^'.$signPattern.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/'; if (!preg_match($valuePattern, $data)) { throw new UnexpectedValueException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat)); } try { + if ('-' === $data[0]) { + $interval = new \DateInterval(substr($data, 1)); + $interval->invert = 1; + + return $interval; + } + + if ('+' === $data[0]) { + return new \DateInterval(substr($data, 1)); + } + return new \DateInterval($data); } catch (\Exception $e) { throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e); @@ -98,6 +120,6 @@ public function supportsDenormalization($data, $type, $format = null) private function isISO8601($string) { - return preg_match('/^P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string); + return preg_match('/^[\-+]?P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string); } } diff --git a/Tests/Normalizer/DateIntervalNormalizerTest.php b/Tests/Normalizer/DateIntervalNormalizerTest.php index efe34c6e9510e..f0bcdef161da3 100644 --- a/Tests/Normalizer/DateIntervalNormalizerTest.php +++ b/Tests/Normalizer/DateIntervalNormalizerTest.php @@ -29,6 +29,11 @@ public function dataProviderISO() ['P%yY%mM%dDT%hH%iM', 'P10Y2M3DT16H5M', 'P10Y2M3DT16H5M'], ['P%yY%mM%dDT%hH', 'P10Y2M3DT16H', 'P10Y2M3DT16H'], ['P%yY%mM%dD', 'P10Y2M3D', 'P10Y2M3DT0H'], + ['%RP%yY%mM%dD', '-P10Y2M3D', '-P10Y2M3DT0H'], + ['%RP%yY%mM%dD', '+P10Y2M3D', '+P10Y2M3DT0H'], + ['%RP%yY%mM%dD', '+P10Y2M3D', 'P10Y2M3DT0H'], + ['%rP%yY%mM%dD', '-P10Y2M3D', '-P10Y2M3DT0H'], + ['%rP%yY%mM%dD', 'P10Y2M3D', 'P10Y2M3DT0H'], ]; return $data; @@ -50,7 +55,7 @@ public function testNormalize() */ public function testNormalizeUsingFormatPassedInContext($format, $output, $input) { - $this->assertEquals($output, $this->normalizer->normalize(new \DateInterval($input), null, [DateIntervalNormalizer::FORMAT_KEY => $format])); + $this->assertEquals($output, $this->normalizer->normalize($this->getInterval($input), null, [DateIntervalNormalizer::FORMAT_KEY => $format])); } /** @@ -58,7 +63,7 @@ public function testNormalizeUsingFormatPassedInContext($format, $output, $input */ public function testNormalizeUsingFormatPassedInConstructor($format, $output, $input) { - $this->assertEquals($output, (new DateIntervalNormalizer($format))->normalize(new \DateInterval($input))); + $this->assertEquals($output, (new DateIntervalNormalizer($format))->normalize($this->getInterval($input))); } public function testNormalizeInvalidObjectThrowsException() @@ -84,7 +89,7 @@ public function testDenormalize() */ public function testDenormalizeUsingFormatPassedInContext($format, $input, $output) { - $this->assertDateIntervalEquals(new \DateInterval($output), $this->normalizer->denormalize($input, \DateInterval::class, null, [DateIntervalNormalizer::FORMAT_KEY => $format])); + $this->assertDateIntervalEquals($this->getInterval($input), $this->normalizer->denormalize($input, \DateInterval::class, null, [DateIntervalNormalizer::FORMAT_KEY => $format])); } /** @@ -92,7 +97,7 @@ public function testDenormalizeUsingFormatPassedInContext($format, $input, $outp */ public function testDenormalizeUsingFormatPassedInConstructor($format, $input, $output) { - $this->assertDateIntervalEquals(new \DateInterval($output), (new DateIntervalNormalizer($format))->denormalize($input, \DateInterval::class)); + $this->assertDateIntervalEquals($this->getInterval($input), (new DateIntervalNormalizer($format))->denormalize($input, \DateInterval::class)); } public function testDenormalizeExpectsString() @@ -124,4 +129,20 @@ private function assertDateIntervalEquals(\DateInterval $expected, \DateInterval { $this->assertEquals($expected->format('%RP%yY%mM%dDT%hH%iM%sS'), $actual->format('%RP%yY%mM%dDT%hH%iM%sS')); } + + private function getInterval($data) + { + if ('-' === $data[0]) { + $interval = new \DateInterval(substr($data, 1)); + $interval->invert = 1; + + return $interval; + } + + if ('+' === $data[0]) { + return new \DateInterval(substr($data, 1)); + } + + return new \DateInterval($data); + } }