Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Serializer\Normalizer; Chris@0: Chris@0: use Symfony\Component\Serializer\Exception\InvalidArgumentException; Chris@14: use Symfony\Component\Serializer\Exception\NotNormalizableValueException; Chris@0: Chris@0: /** Chris@0: * Normalizes an object implementing the {@see \DateTimeInterface} to a date string. Chris@0: * Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}. Chris@0: * Chris@0: * @author Kévin Dunglas Chris@0: */ Chris@0: class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface Chris@0: { Chris@0: const FORMAT_KEY = 'datetime_format'; Chris@14: const TIMEZONE_KEY = 'datetime_timezone'; Chris@14: Chris@14: private $format; Chris@14: private $timezone; Chris@14: Chris@17: private static $supportedTypes = [ Chris@14: \DateTimeInterface::class => true, Chris@14: \DateTimeImmutable::class => true, Chris@14: \DateTime::class => true, Chris@17: ]; Chris@0: Chris@0: /** Chris@14: * @param string $format Chris@14: * @param \DateTimeZone|null $timezone Chris@0: */ Chris@14: public function __construct($format = \DateTime::RFC3339, \DateTimeZone $timezone = null) Chris@0: { Chris@0: $this->format = $format; Chris@14: $this->timezone = $timezone; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@0: * @throws InvalidArgumentException Chris@0: */ Chris@17: public function normalize($object, $format = null, array $context = []) Chris@0: { Chris@0: if (!$object instanceof \DateTimeInterface) { Chris@0: throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".'); Chris@0: } Chris@0: Chris@0: $format = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format; Chris@14: $timezone = $this->getTimezone($context); Chris@14: Chris@14: if (null !== $timezone) { Chris@17: $object = clone $object; Chris@17: $object = $object->setTimezone($timezone); Chris@14: } Chris@0: Chris@0: return $object->format($format); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supportsNormalization($data, $format = null) Chris@0: { Chris@0: return $data instanceof \DateTimeInterface; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: * Chris@14: * @throws NotNormalizableValueException Chris@0: */ Chris@17: public function denormalize($data, $class, $format = null, array $context = []) Chris@0: { Chris@0: $dateTimeFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : null; Chris@14: $timezone = $this->getTimezone($context); Chris@14: Chris@14: if ('' === $data || null === $data) { Chris@14: throw new NotNormalizableValueException('The data is either an empty string or null, you should pass a string that can be parsed with the passed format or a valid DateTime string.'); Chris@14: } Chris@0: Chris@0: if (null !== $dateTimeFormat) { Chris@17: if (null === $timezone && \PHP_VERSION_ID < 70000) { Chris@14: // https://bugs.php.net/bug.php?id=68669 Chris@14: $object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data); Chris@14: } else { Chris@14: $object = \DateTime::class === $class ? \DateTime::createFromFormat($dateTimeFormat, $data, $timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data, $timezone); Chris@14: } Chris@0: Chris@0: if (false !== $object) { Chris@0: return $object; Chris@0: } Chris@0: Chris@0: $dateTimeErrors = \DateTime::class === $class ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors(); Chris@0: Chris@14: throw new NotNormalizableValueException(sprintf( Chris@0: 'Parsing datetime string "%s" using format "%s" resulted in %d errors:'."\n".'%s', Chris@0: $data, Chris@0: $dateTimeFormat, Chris@0: $dateTimeErrors['error_count'], Chris@0: implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])) Chris@0: )); Chris@0: } Chris@0: Chris@0: try { Chris@14: return \DateTime::class === $class ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone); Chris@0: } catch (\Exception $e) { Chris@14: throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supportsDenormalization($data, $type, $format = null) Chris@0: { Chris@14: return isset(self::$supportedTypes[$type]); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Formats datetime errors. Chris@0: * Chris@0: * @return string[] Chris@0: */ Chris@0: private function formatDateTimeErrors(array $errors) Chris@0: { Chris@17: $formattedErrors = []; Chris@0: Chris@0: foreach ($errors as $pos => $message) { Chris@0: $formattedErrors[] = sprintf('at position %d: %s', $pos, $message); Chris@0: } Chris@0: Chris@0: return $formattedErrors; Chris@0: } Chris@14: Chris@14: private function getTimezone(array $context) Chris@14: { Chris@18: $dateTimeZone = \array_key_exists(self::TIMEZONE_KEY, $context) ? $context[self::TIMEZONE_KEY] : $this->timezone; Chris@14: Chris@14: if (null === $dateTimeZone) { Chris@14: return null; Chris@14: } Chris@14: Chris@14: return $dateTimeZone instanceof \DateTimeZone ? $dateTimeZone : new \DateTimeZone($dateTimeZone); Chris@14: } Chris@0: }