From 3564118ad1a0a91e3f3c8217ab3fb8381e70208b Mon Sep 17 00:00:00 2001 From: Valentin Date: Sat, 2 Dec 2017 19:49:22 +0300 Subject: [PATCH 1/4] DateTimeImmutable norm data in DateTime form types --- .../DateTimeImmutableToArrayTransformer.php | 184 ++++++++++++++++ ...eImmutableToLocalizedStringTransformer.php | 202 ++++++++++++++++++ .../DateTimeImmutableToRfc3339Transformer.php | 90 ++++++++ .../DateTimeImmutableToStringTransformer.php | 139 ++++++++++++ ...ateTimeImmutableToTimestampTransformer.php | 80 +++++++ ...TimeImmutableTransformerDecoratorTrait.php | 60 ++++++ .../DateTimeToArrayTransformer.php | 160 +------------- .../DateTimeToImmutableTransformer.php | 77 +++++++ .../DateTimeToLocalizedStringTransformer.php | 145 +------------ .../DateTimeToRfc3339Transformer.php | 78 +------ .../DateTimeToStringTransformer.php | 111 +--------- .../DateTimeToTimestampTransformer.php | 61 +----- .../Form/Extension/Core/Type/DateTimeType.php | 38 ++-- .../Form/Extension/Core/Type/DateType.php | 30 +-- .../Form/Extension/Core/Type/TimeType.php | 27 +-- 15 files changed, 913 insertions(+), 569 deletions(-) create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339Transformer.php create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformer.php create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformer.php create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php create mode 100644 src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToImmutableTransformer.php diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php new file mode 100644 index 0000000000000..dce82158a55f2 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a normalized time and a localized time string/array. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + */ +class DateTimeImmutableToArrayTransformer extends BaseDateTimeTransformer +{ + private $pad; + + private $fields; + + /** + * @param string $inputTimezone The input timezone + * @param string $outputTimezone The output timezone + * @param array $fields The date fields + * @param bool $pad Whether to use padding + */ + public function __construct(string $inputTimezone = null, string $outputTimezone = null, array $fields = null, bool $pad = false) + { + parent::__construct($inputTimezone, $outputTimezone); + + if (null === $fields) { + $fields = array('year', 'month', 'day', 'hour', 'minute', 'second'); + } + + $this->fields = $fields; + $this->pad = $pad; + } + + /** + * Transforms a normalized date into a localized date. + * + * @param \DateTimeImmutable $dateTime A DateTimeImmutable object + * + * @return array Localized date + * + * @throws TransformationFailedException If the given value is not a \DateTimeImmutable + */ + public function transform($dateTime): array + { + if (null === $dateTime) { + return array_intersect_key(array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ), array_flip($this->fields)); + } + + if (!$dateTime instanceof \DateTimeImmutable) { + throw new TransformationFailedException('Expected a \DateTimeImmutable.'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + $result = array_intersect_key(array( + 'year' => $dateTime->format('Y'), + 'month' => $dateTime->format('m'), + 'day' => $dateTime->format('d'), + 'hour' => $dateTime->format('H'), + 'minute' => $dateTime->format('i'), + 'second' => $dateTime->format('s'), + ), array_flip($this->fields)); + + if (!$this->pad) { + foreach ($result as &$entry) { + // remove leading zeros + $entry = (string)(int)$entry; + } + // unset reference to keep scope clear + unset($entry); + } + + return $result; + } + + /** + * Transforms a localized date into a normalized date. + * + * @param array $value Localized date + * + * @return \DateTimeImmutable Normalized date + * + * @throws TransformationFailedException If the given value is not an array, + * if the value could not be transformed + */ + public function reverseTransform($value): ?\DateTimeImmutable + { + if (null === $value) { + return null; + } + + if (!is_array($value)) { + throw new TransformationFailedException('Expected an array.'); + } + + if ('' === implode('', $value)) { + return null; + } + + $emptyFields = array(); + + foreach ($this->fields as $field) { + if (!isset($value[$field])) { + $emptyFields[] = $field; + } + } + + if (count($emptyFields) > 0) { + throw new TransformationFailedException(sprintf('The fields "%s" should not be empty', implode('", "', $emptyFields))); + } + + if (isset($value['month']) && !ctype_digit((string)$value['month'])) { + throw new TransformationFailedException('This month is invalid'); + } + + if (isset($value['day']) && !ctype_digit((string)$value['day'])) { + throw new TransformationFailedException('This day is invalid'); + } + + if (isset($value['year']) && !ctype_digit((string)$value['year'])) { + throw new TransformationFailedException('This year is invalid'); + } + + if (!empty($value['month']) && !empty($value['day']) && !empty($value['year']) && false === checkdate($value['month'], $value['day'], $value['year'])) { + throw new TransformationFailedException('This is an invalid date'); + } + + if (isset($value['hour']) && !ctype_digit((string)$value['hour'])) { + throw new TransformationFailedException('This hour is invalid'); + } + + if (isset($value['minute']) && !ctype_digit((string)$value['minute'])) { + throw new TransformationFailedException('This minute is invalid'); + } + + if (isset($value['second']) && !ctype_digit((string)$value['second'])) { + throw new TransformationFailedException('This second is invalid'); + } + + try { + $dateTime = new \DateTimeImmutable( + sprintf( + '%s-%s-%s %s:%s:%s', + empty($value['year']) ? '1970' : $value['year'], + empty($value['month']) ? '1' : $value['month'], + empty($value['day']) ? '1' : $value['day'], + empty($value['hour']) ? '0' : $value['hour'], + empty($value['minute']) ? '0' : $value['minute'], + empty($value['second']) ? '0' : $value['second'] + ), + new \DateTimeZone($this->outputTimezone) + ); + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateTime; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php new file mode 100644 index 0000000000000..9d5f41a324670 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Transforms between a normalized time and a localized time string. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + */ +class DateTimeImmutableToLocalizedStringTransformer extends BaseDateTimeTransformer +{ + private $dateFormat; + + private $timeFormat; + + private $pattern; + + private $calendar; + + /** + * @see BaseDateTimeTransformer::formats for available format options + * + * @param string $inputTimezone The name of the input timezone + * @param string $outputTimezone The name of the output timezone + * @param int $dateFormat The date format + * @param int $timeFormat The time format + * @param int $calendar One of the \IntlDateFormatter calendar constants + * @param string $pattern A pattern to pass to \IntlDateFormatter + * + * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string + */ + public function __construct(string $inputTimezone = null, string $outputTimezone = null, int $dateFormat = null, int $timeFormat = null, int $calendar = \IntlDateFormatter::GREGORIAN, string $pattern = null) + { + parent::__construct($inputTimezone, $outputTimezone); + + if (null === $dateFormat) { + $dateFormat = \IntlDateFormatter::MEDIUM; + } + + if (null === $timeFormat) { + $timeFormat = \IntlDateFormatter::SHORT; + } + + if (!in_array($dateFormat, self::$formats, true)) { + throw new UnexpectedTypeException($dateFormat, implode('", "', self::$formats)); + } + + if (!in_array($timeFormat, self::$formats, true)) { + throw new UnexpectedTypeException($timeFormat, implode('", "', self::$formats)); + } + + $this->dateFormat = $dateFormat; + $this->timeFormat = $timeFormat; + $this->calendar = $calendar; + $this->pattern = $pattern; + } + + /** + * Transforms a normalized date into a localized date string/array. + * + * @param \DateTimeImmutable $dateTime A DateTimeImmutable object + * + * @return string Localized date string + * + * @throws TransformationFailedException if the given value is not a \DateTimeImmutable + * or if the date could not be transformed + */ + public function transform($dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeImmutable) { + throw new TransformationFailedException('Expected a \DateTimeImmutable.'); + } + + $value = $this->getIntlDateFormatter()->format($dateTime->getTimestamp()); + + if (0 != intl_get_error_code()) { + throw new TransformationFailedException(intl_get_error_message()); + } + + return $value; + } + + /** + * Transforms a localized date string/array into a normalized date. + * + * @param string|array $value Localized date string/array + * + * @return \DateTimeImmutable Normalized date + * + * @throws TransformationFailedException if the given value is not a string, + * if the date could not be parsed + */ + public function reverseTransform($value): ?\DateTimeImmutable + { + if (!is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $value) { + return null; + } + + // date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due + // to DST changes + $dateOnly = $this->isPatternDateOnly(); + + $timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value); + + if (0 != intl_get_error_code()) { + throw new TransformationFailedException(intl_get_error_message()); + } + + try { + if ($dateOnly) { + // we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight + $dateTime = new \DateTimeImmutable(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->outputTimezone)); + } else { + // read timestamp into DateTimeImmutable object - the formatter delivers a timestamp + $dateTime = new \DateTimeImmutable(sprintf('@%s', $timestamp)); + } + // set timezone separately, as it would be ignored if set via the constructor, + // see http://php.net/manual/en/datetime.construct.php + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if ($this->outputTimezone !== $this->inputTimezone) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + return $dateTime; + } + + /** + * Returns a preconfigured IntlDateFormatter instance. + * + * @param bool $ignoreTimezone Use UTC regardless of the configured timezone + * + * @return \IntlDateFormatter + * + * @throws TransformationFailedException in case the date formatter can not be constructed + */ + protected function getIntlDateFormatter(bool $ignoreTimezone = false): \IntlDateFormatter + { + $dateFormat = $this->dateFormat; + $timeFormat = $this->timeFormat; + $timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone; + if (class_exists('IntlTimeZone', false)) { + // see https://bugs.php.net/bug.php?id=66323 + $timezone = \IntlTimeZone::createTimeZone($timezone); + } + $calendar = $this->calendar; + $pattern = $this->pattern; + + $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern); + + // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323 + if (!$intlDateFormatter) { + throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code()); + } + + $intlDateFormatter->setLenient(false); + + return $intlDateFormatter; + } + + /** + * Checks if the pattern contains only a date. + * + * @return bool + */ + protected function isPatternDateOnly(): bool + { + if (null === $this->pattern) { + return false; + } + + // strip escaped text + $pattern = preg_replace("#'(.*?)'#", '', $this->pattern); + + // check for the absence of time-related placeholders + return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern); + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339Transformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339Transformer.php new file mode 100644 index 0000000000000..ded31c6fd8da5 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339Transformer.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * @author Bernhard Schussek + */ +class DateTimeImmutableToRfc3339Transformer extends BaseDateTimeTransformer +{ + /** + * Transforms a normalized date into a localized date. + * + * @param \DateTimeImmutable $dateTime A DateTimeImmutable object + * + * @return string The formatted date + * + * @throws TransformationFailedException If the given value is not a \DateTimeImmutable + */ + public function transform($dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeImmutable) { + throw new TransformationFailedException('Expected a \DateTimeImmutable.'); + } + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + } + + return preg_replace('/\+00:00$/', 'Z', $dateTime->format('c')); + } + + /** + * Transforms a formatted string following RFC 3339 into a normalized date. + * + * @param string $rfc3339 Formatted string + * + * @return \DateTimeImmutable Normalized date + * + * @throws TransformationFailedException If the given value is not a string, + * if the value could not be transformed + */ + public function reverseTransform($rfc3339): ?\DateTimeImmutable + { + if (!is_string($rfc3339)) { + throw new TransformationFailedException('Expected a string.'); + } + + if ('' === $rfc3339) { + return null; + } + + try { + $dateTime = new \DateTimeImmutable($rfc3339); + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + + if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $rfc3339, $matches)) { + if (!checkdate($matches[2], $matches[3], $matches[1])) { + throw new TransformationFailedException(sprintf( + 'The date "%s-%s-%s" is not a valid date.', + $matches[1], + $matches[2], + $matches[3] + )); + } + } + + return $dateTime; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformer.php new file mode 100644 index 0000000000000..bd93ccd2f920b --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformer.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a date string and a DateTimeImmutable object. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + */ +class DateTimeImmutableToStringTransformer extends BaseDateTimeTransformer +{ + /** + * Format used for generating strings. + * + * @var string + */ + private $generateFormat; + + /** + * Format used for parsing strings. + * + * Different than the {@link $generateFormat} because formats for parsing + * support additional characters in PHP that are not supported for + * generating strings. + * + * @var string + */ + private $parseFormat; + + /** + * Transforms a \DateTimeImmutable instance to a string. + * + * @see \DateTimeImmutable::format() for supported formats + * + * @param string $inputTimezone The name of the input timezone + * @param string $outputTimezone The name of the output timezone + * @param string $format The date format + */ + public function __construct(string $inputTimezone = null, string $outputTimezone = null, string $format = 'Y-m-d H:i:s') + { + parent::__construct($inputTimezone, $outputTimezone); + + $this->generateFormat = $this->parseFormat = $format; + + // See http://php.net/manual/en/datetime.createfromformat.php + // The character "|" in the format makes sure that the parts of a date + // that are *not* specified in the format are reset to the corresponding + // values from 1970-01-01 00:00:00 instead of the current time. + // Without "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 12:32:47", + // where the time corresponds to the current server time. + // With "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 00:00:00", + // which is at least deterministic and thus used here. + if (false === strpos($this->parseFormat, '|')) { + $this->parseFormat .= '|'; + } + } + + /** + * Transforms a DateTimeImmutable object into a date string with the configured format + * and timezone. + * + * @param \DateTimeImmutable $dateTime A DateTimeImmutable object + * + * @return string A value as produced by PHP's date() function + * + * @throws TransformationFailedException If the given value is not a \DateTimeInterface + */ + public function transform($dateTime): string + { + if (null === $dateTime) { + return ''; + } + + if (!$dateTime instanceof \DateTimeImmutable) { + throw new TransformationFailedException('Expected a \DateTimeImmutable.'); + } + + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); + + return $dateTime->format($this->generateFormat); + } + + /** + * Transforms a date string in the configured timezone into a DateTimeImmutable object. + * + * @param string $value A value as produced by PHP's date() function + * + * @return \DateTimeImmutable An instance of \DateTimeImmutable + * + * @throws TransformationFailedException If the given value is not a string, + * or could not be transformed + */ + public function reverseTransform($value): ?\DateTimeImmutable + { + if (empty($value)) { + return null; + } + + if (!is_string($value)) { + throw new TransformationFailedException('Expected a string.'); + } + + $outputTz = new \DateTimeZone($this->outputTimezone); + $dateTime = \DateTimeImmutable::createFromFormat($this->parseFormat, $value, $outputTz); + + $lastErrors = \DateTimeImmutable::getLastErrors(); + + if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { + throw new TransformationFailedException( + implode(', ', array_merge( + array_values($lastErrors['warnings']), + array_values($lastErrors['errors']) + )) + ); + } + + try { + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateTime; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformer.php new file mode 100644 index 0000000000000..c306befdb37ac --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformer.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transforms between a timestamp and a DateTimeImmutable object. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + */ +class DateTimeImmutableToTimestampTransformer extends BaseDateTimeTransformer +{ + /** + * Transforms a DateTimeImmutable object into a timestamp in the configured timezone. + * + * @param \DateTimeImmutable $dateTime A DateTimeImmutable object + * + * @return int A timestamp + * + * @throws TransformationFailedException If the given value is not a \DateTimeImmutable + */ + public function transform($dateTime): ?int + { + if (null === $dateTime) { + return null; + } + + if (!$dateTime instanceof \DateTimeImmutable) { + throw new TransformationFailedException('Expected a \DateTimeImmutable.'); + } + + return $dateTime->getTimestamp(); + } + + /** + * Transforms a timestamp in the configured timezone into a DateTime object. + * + * @param string $value A timestamp + * + * @return \DateTimeImmutable A \DateTimeImmutable object + * + * @throws TransformationFailedException If the given value is not a timestamp + * or if the given timestamp is invalid + */ + public function reverseTransform($value): ?\DateTimeImmutable + { + if (null === $value) { + return null; + } + + if (!is_numeric($value)) { + throw new TransformationFailedException('Expected a numeric.'); + } + + try { + $dateTime = (new \DateTimeImmutable()) + ->setTimezone(new \DateTimeZone($this->outputTimezone)) + ->setTimestamp($value); + + if ($this->inputTimezone !== $this->outputTimezone) { + $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); + } + } catch (\Exception $e) { + throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); + } + + return $dateTime; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php new file mode 100644 index 0000000000000..9e6bb21512a8d --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; + +/** + * A BC-layer trait for DateTimeTo*Transformers + * + * @author Valentin Udaltsov + * + * @internal + */ +trait DateTimeImmutableTransformerDecoratorTrait +{ + /** + * @var DataTransformerInterface + */ + private $decorated; + + /** + * Converts \DateTime values to \DateTimeImmutable and forwards the transform() call + * to the decorated transformer. + */ + public function transform($value) + { + if ($value instanceof \DateTime) { + $value = \DateTimeImmutable::createFromMutable($value); + } + + return $this->decorated->transform($value); + } + + /** + * Forwards the reverseTransform() call to the decorated transformer and + * converts \DateTimeImmutable return values to \DateTime. + */ + public function reverseTransform($value) + { + $value = $this->decorated->reverseTransform($value); + + if ($value instanceof \DateTimeImmutable) { + $dateTime = new \DateTime(null, $value->getTimezone()); + $dateTime->setTimestamp($value->getTimestamp()); + + return $dateTime; + } + + return $value; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php index 8edc501fca277..a1454d45c231f 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php @@ -11,19 +11,17 @@ namespace Symfony\Component\Form\Extension\Core\DataTransformer; -use Symfony\Component\Form\Exception\TransformationFailedException; - /** * Transforms between a normalized time and a localized time string/array. * * @author Bernhard Schussek * @author Florian Eckerstorfer + * + * @deprecated The Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer class is deprecated since version 4.1 and will be removed in 5.0. Use the Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToArrayTransformer class instead. */ class DateTimeToArrayTransformer extends BaseDateTimeTransformer { - private $pad; - - private $fields; + use DateTimeImmutableTransformerDecoratorTrait; /** * @param string $inputTimezone The input timezone @@ -34,156 +32,6 @@ class DateTimeToArrayTransformer extends BaseDateTimeTransformer public function __construct(string $inputTimezone = null, string $outputTimezone = null, array $fields = null, bool $pad = false) { parent::__construct($inputTimezone, $outputTimezone); - - if (null === $fields) { - $fields = array('year', 'month', 'day', 'hour', 'minute', 'second'); - } - - $this->fields = $fields; - $this->pad = $pad; - } - - /** - * Transforms a normalized date into a localized date. - * - * @param \DateTimeInterface $dateTime A DateTimeInterface object - * - * @return array Localized date - * - * @throws TransformationFailedException If the given value is not a \DateTimeInterface - */ - public function transform($dateTime) - { - if (null === $dateTime) { - return array_intersect_key(array( - 'year' => '', - 'month' => '', - 'day' => '', - 'hour' => '', - 'minute' => '', - 'second' => '', - ), array_flip($this->fields)); - } - - if (!$dateTime instanceof \DateTimeInterface) { - throw new TransformationFailedException('Expected a \DateTimeInterface.'); - } - - if ($this->inputTimezone !== $this->outputTimezone) { - if (!$dateTime instanceof \DateTimeImmutable) { - $dateTime = clone $dateTime; - } - - $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); - } - - $result = array_intersect_key(array( - 'year' => $dateTime->format('Y'), - 'month' => $dateTime->format('m'), - 'day' => $dateTime->format('d'), - 'hour' => $dateTime->format('H'), - 'minute' => $dateTime->format('i'), - 'second' => $dateTime->format('s'), - ), array_flip($this->fields)); - - if (!$this->pad) { - foreach ($result as &$entry) { - // remove leading zeros - $entry = (string) (int) $entry; - } - // unset reference to keep scope clear - unset($entry); - } - - return $result; - } - - /** - * Transforms a localized date into a normalized date. - * - * @param array $value Localized date - * - * @return \DateTime Normalized date - * - * @throws TransformationFailedException If the given value is not an array, - * if the value could not be transformed - */ - public function reverseTransform($value) - { - if (null === $value) { - return; - } - - if (!is_array($value)) { - throw new TransformationFailedException('Expected an array.'); - } - - if ('' === implode('', $value)) { - return; - } - - $emptyFields = array(); - - foreach ($this->fields as $field) { - if (!isset($value[$field])) { - $emptyFields[] = $field; - } - } - - if (count($emptyFields) > 0) { - throw new TransformationFailedException( - sprintf('The fields "%s" should not be empty', implode('", "', $emptyFields) - )); - } - - if (isset($value['month']) && !ctype_digit((string) $value['month'])) { - throw new TransformationFailedException('This month is invalid'); - } - - if (isset($value['day']) && !ctype_digit((string) $value['day'])) { - throw new TransformationFailedException('This day is invalid'); - } - - if (isset($value['year']) && !ctype_digit((string) $value['year'])) { - throw new TransformationFailedException('This year is invalid'); - } - - if (!empty($value['month']) && !empty($value['day']) && !empty($value['year']) && false === checkdate($value['month'], $value['day'], $value['year'])) { - throw new TransformationFailedException('This is an invalid date'); - } - - if (isset($value['hour']) && !ctype_digit((string) $value['hour'])) { - throw new TransformationFailedException('This hour is invalid'); - } - - if (isset($value['minute']) && !ctype_digit((string) $value['minute'])) { - throw new TransformationFailedException('This minute is invalid'); - } - - if (isset($value['second']) && !ctype_digit((string) $value['second'])) { - throw new TransformationFailedException('This second is invalid'); - } - - try { - $dateTime = new \DateTime(sprintf( - '%s-%s-%s %s:%s:%s', - empty($value['year']) ? '1970' : $value['year'], - empty($value['month']) ? '1' : $value['month'], - empty($value['day']) ? '1' : $value['day'], - empty($value['hour']) ? '0' : $value['hour'], - empty($value['minute']) ? '0' : $value['minute'], - empty($value['second']) ? '0' : $value['second'] - ), - new \DateTimeZone($this->outputTimezone) - ); - - if ($this->inputTimezone !== $this->outputTimezone) { - $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); - } - } catch (\Exception $e) { - throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); - } - - return $dateTime; + $this->decorated = new DateTimeImmutableToArrayTransformer($inputTimezone, $outputTimezone, $fields, $pad); } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToImmutableTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToImmutableTransformer.php new file mode 100644 index 0000000000000..021da585514c1 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToImmutableTransformer.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Core\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * A BC-layer transformer for DateTimeImmutableTo* transformers. + * + * @author Valentin Udaltsov + * + * @internal + */ +class DateTimeToImmutableTransformer implements DataTransformerInterface +{ + private $reverseTransform; + + public function __construct(bool $reverseTransform = true) + { + $this->reverseTransform = $reverseTransform; + } + + /** + * Transforms \DateTime values to \DateTimeImmutable and triggers a deprecation. + */ + public function transform($value) + { + if (null === $value) { + return null; + } + + if (!$value instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + if ($value instanceof \DateTime) { + // deprecation + + return \DateTimeImmutable::createFromMutable($value); + } + + return $value; + } + + /** + * Transforms \DateTimeImmutable values to \DateTime when needed. + */ + public function reverseTransform($value) + { + if (null === $value) { + return null; + } + + if (!$value instanceof \DateTimeInterface) { + throw new TransformationFailedException('Expected a \DateTimeInterface.'); + } + + if ($this->reverseTransform && $value instanceof \DateTimeImmutable) { + $dateTime = new \DateTime(null, $value->getTimezone()); + $dateTime->setTimestamp($value->getTimestamp()); + + return $dateTime; + } + + return $value; + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index 12af9dc11a764..773cfd7242715 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -19,13 +19,12 @@ * * @author Bernhard Schussek * @author Florian Eckerstorfer + * + * @deprecated The Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer class is deprecated since version 4.1 and will be removed in 5.0. Use the Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToLocalizedStringTransformer class instead. */ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer { - private $dateFormat; - private $timeFormat; - private $pattern; - private $calendar; + use DateTimeImmutableTransformerDecoratorTrait; /** * @see BaseDateTimeTransformer::formats for available format options @@ -42,108 +41,7 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer public function __construct(string $inputTimezone = null, string $outputTimezone = null, int $dateFormat = null, int $timeFormat = null, int $calendar = \IntlDateFormatter::GREGORIAN, string $pattern = null) { parent::__construct($inputTimezone, $outputTimezone); - - if (null === $dateFormat) { - $dateFormat = \IntlDateFormatter::MEDIUM; - } - - if (null === $timeFormat) { - $timeFormat = \IntlDateFormatter::SHORT; - } - - if (!in_array($dateFormat, self::$formats, true)) { - throw new UnexpectedTypeException($dateFormat, implode('", "', self::$formats)); - } - - if (!in_array($timeFormat, self::$formats, true)) { - throw new UnexpectedTypeException($timeFormat, implode('", "', self::$formats)); - } - - $this->dateFormat = $dateFormat; - $this->timeFormat = $timeFormat; - $this->calendar = $calendar; - $this->pattern = $pattern; - } - - /** - * Transforms a normalized date into a localized date string/array. - * - * @param \DateTimeInterface $dateTime A DateTimeInterface object - * - * @return string|array Localized date string/array - * - * @throws TransformationFailedException if the given value is not a \DateTimeInterface - * or if the date could not be transformed - */ - public function transform($dateTime) - { - if (null === $dateTime) { - return ''; - } - - if (!$dateTime instanceof \DateTimeInterface) { - throw new TransformationFailedException('Expected a \DateTimeInterface.'); - } - - $value = $this->getIntlDateFormatter()->format($dateTime->getTimestamp()); - - if (0 != intl_get_error_code()) { - throw new TransformationFailedException(intl_get_error_message()); - } - - return $value; - } - - /** - * Transforms a localized date string/array into a normalized date. - * - * @param string|array $value Localized date string/array - * - * @return \DateTime Normalized date - * - * @throws TransformationFailedException if the given value is not a string, - * if the date could not be parsed - */ - public function reverseTransform($value) - { - if (!is_string($value)) { - throw new TransformationFailedException('Expected a string.'); - } - - if ('' === $value) { - return; - } - - // date-only patterns require parsing to be done in UTC, as midnight might not exist in the local timezone due - // to DST changes - $dateOnly = $this->isPatternDateOnly(); - - $timestamp = $this->getIntlDateFormatter($dateOnly)->parse($value); - - if (0 != intl_get_error_code()) { - throw new TransformationFailedException(intl_get_error_message()); - } - - try { - if ($dateOnly) { - // we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight - $dateTime = new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->outputTimezone)); - } else { - // read timestamp into DateTime object - the formatter delivers a timestamp - $dateTime = new \DateTime(sprintf('@%s', $timestamp)); - } - // set timezone separately, as it would be ignored if set via the constructor, - // see http://php.net/manual/en/datetime.construct.php - $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); - } catch (\Exception $e) { - throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); - } - - if ($this->outputTimezone !== $this->inputTimezone) { - $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); - } - - return $dateTime; + $this->decorated = new DateTimeImmutableToLocalizedStringTransformer($inputTimezone, $outputTimezone, $dateFormat, $timeFormat, $calendar, $pattern); } /** @@ -157,26 +55,9 @@ public function reverseTransform($value) */ protected function getIntlDateFormatter($ignoreTimezone = false) { - $dateFormat = $this->dateFormat; - $timeFormat = $this->timeFormat; - $timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone; - if (class_exists('IntlTimeZone', false)) { - // see https://bugs.php.net/bug.php?id=66323 - $timezone = \IntlTimeZone::createTimeZone($timezone); - } - $calendar = $this->calendar; - $pattern = $this->pattern; - - $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern); - - // new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323 - if (!$intlDateFormatter) { - throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code()); - } - - $intlDateFormatter->setLenient(false); - - return $intlDateFormatter; + return \Closure::bind(function ($ignoreTimezone) { + return $this->getIntlDateFormatter($ignoreTimezone); + }, $this->decorated, DateTimeImmutableToLocalizedStringTransformer::class)($ignoreTimezone); } /** @@ -186,14 +67,8 @@ protected function getIntlDateFormatter($ignoreTimezone = false) */ protected function isPatternDateOnly() { - if (null === $this->pattern) { - return false; - } - - // strip escaped text - $pattern = preg_replace("#'(.*?)'#", '', $this->pattern); - - // check for the absence of time-related placeholders - return 0 === preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern); + return \Closure::bind(function () { + return $this->getIntlDateFormatter(); + }, $this->decorated, DateTimeImmutableToLocalizedStringTransformer::class)(); } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php index 550ea9b50f67e..8df589d4f0ae8 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php @@ -11,84 +11,18 @@ namespace Symfony\Component\Form\Extension\Core\DataTransformer; -use Symfony\Component\Form\Exception\TransformationFailedException; - /** * @author Bernhard Schussek + * + * @deprecated The Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer class is deprecated since version 4.1 and will be removed in 5.0. Use the Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToRfc3339Transformer class instead. */ class DateTimeToRfc3339Transformer extends BaseDateTimeTransformer { - /** - * Transforms a normalized date into a localized date. - * - * @param \DateTimeInterface $dateTime A DateTimeInterface object - * - * @return string The formatted date - * - * @throws TransformationFailedException If the given value is not a \DateTimeInterface - */ - public function transform($dateTime) - { - if (null === $dateTime) { - return ''; - } - - if (!$dateTime instanceof \DateTimeInterface) { - throw new TransformationFailedException('Expected a \DateTimeInterface.'); - } + use DateTimeImmutableTransformerDecoratorTrait; - if ($this->inputTimezone !== $this->outputTimezone) { - if (!$dateTime instanceof \DateTimeImmutable) { - $dateTime = clone $dateTime; - } - - $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); - } - - return preg_replace('/\+00:00$/', 'Z', $dateTime->format('c')); - } - - /** - * Transforms a formatted string following RFC 3339 into a normalized date. - * - * @param string $rfc3339 Formatted string - * - * @return \DateTime Normalized date - * - * @throws TransformationFailedException If the given value is not a string, - * if the value could not be transformed - */ - public function reverseTransform($rfc3339) + public function __construct($inputTimezone = null, $outputTimezone = null) { - if (!is_string($rfc3339)) { - throw new TransformationFailedException('Expected a string.'); - } - - if ('' === $rfc3339) { - return; - } - - try { - $dateTime = new \DateTime($rfc3339); - } catch (\Exception $e) { - throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); - } - - if ($this->inputTimezone !== $dateTime->getTimezone()->getName()) { - $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); - } - - if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $rfc3339, $matches)) { - if (!checkdate($matches[2], $matches[3], $matches[1])) { - throw new TransformationFailedException(sprintf( - 'The date "%s-%s-%s" is not a valid date.', - $matches[1], - $matches[2], - $matches[3] - )); - } - } - - return $dateTime; + parent::__construct($inputTimezone, $outputTimezone); + $this->decorated = new DateTimeImmutableToRfc3339Transformer($inputTimezone, $outputTimezone); } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php index c93d2c995d965..2055cb8a1258b 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -11,33 +11,17 @@ namespace Symfony\Component\Form\Extension\Core\DataTransformer; -use Symfony\Component\Form\Exception\TransformationFailedException; - /** * Transforms between a date string and a DateTime object. * * @author Bernhard Schussek * @author Florian Eckerstorfer + * + * @deprecated The Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer class is deprecated since version 4.1 and will be removed in 5.0. Use the Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToStringTransformer class instead. */ class DateTimeToStringTransformer extends BaseDateTimeTransformer { - /** - * Format used for generating strings. - * - * @var string - */ - private $generateFormat; - - /** - * Format used for parsing strings. - * - * Different than the {@link $generateFormat} because formats for parsing - * support additional characters in PHP that are not supported for - * generating strings. - * - * @var string - */ - private $parseFormat; + use DateTimeImmutableTransformerDecoratorTrait; /** * Transforms a \DateTime instance to a string. @@ -51,93 +35,6 @@ class DateTimeToStringTransformer extends BaseDateTimeTransformer public function __construct(string $inputTimezone = null, string $outputTimezone = null, string $format = 'Y-m-d H:i:s') { parent::__construct($inputTimezone, $outputTimezone); - - $this->generateFormat = $this->parseFormat = $format; - - // See http://php.net/manual/en/datetime.createfromformat.php - // The character "|" in the format makes sure that the parts of a date - // that are *not* specified in the format are reset to the corresponding - // values from 1970-01-01 00:00:00 instead of the current time. - // Without "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 12:32:47", - // where the time corresponds to the current server time. - // With "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 00:00:00", - // which is at least deterministic and thus used here. - if (false === strpos($this->parseFormat, '|')) { - $this->parseFormat .= '|'; - } - } - - /** - * Transforms a DateTime object into a date string with the configured format - * and timezone. - * - * @param \DateTimeInterface $dateTime A DateTimeInterface object - * - * @return string A value as produced by PHP's date() function - * - * @throws TransformationFailedException If the given value is not a \DateTimeInterface - */ - public function transform($dateTime) - { - if (null === $dateTime) { - return ''; - } - - if (!$dateTime instanceof \DateTimeInterface) { - throw new TransformationFailedException('Expected a \DateTimeInterface.'); - } - - if (!$dateTime instanceof \DateTimeImmutable) { - $dateTime = clone $dateTime; - } - - $dateTime = $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); - - return $dateTime->format($this->generateFormat); - } - - /** - * Transforms a date string in the configured timezone into a DateTime object. - * - * @param string $value A value as produced by PHP's date() function - * - * @return \DateTime An instance of \DateTime - * - * @throws TransformationFailedException If the given value is not a string, - * or could not be transformed - */ - public function reverseTransform($value) - { - if (empty($value)) { - return; - } - - if (!is_string($value)) { - throw new TransformationFailedException('Expected a string.'); - } - - $outputTz = new \DateTimeZone($this->outputTimezone); - $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); - - $lastErrors = \DateTime::getLastErrors(); - - if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { - throw new TransformationFailedException( - implode(', ', array_merge( - array_values($lastErrors['warnings']), - array_values($lastErrors['errors']) - )) - ); - } - - try { - if ($this->inputTimezone !== $this->outputTimezone) { - $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); - } - } catch (\Exception $e) { - throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); - } - - return $dateTime; + $this->decorated = new DateTimeImmutableToStringTransformer($inputTimezone, $outputTimezone, $format); } } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php index d6091589c4326..497e1ce63a33f 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php @@ -11,70 +11,21 @@ namespace Symfony\Component\Form\Extension\Core\DataTransformer; -use Symfony\Component\Form\Exception\TransformationFailedException; - /** * Transforms between a timestamp and a DateTime object. * * @author Bernhard Schussek * @author Florian Eckerstorfer + * + * @deprecated The Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer class is deprecated since version 4.1 and will be removed in 5.0. Use the Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToTimestampTransformer class instead. */ class DateTimeToTimestampTransformer extends BaseDateTimeTransformer { - /** - * Transforms a DateTime object into a timestamp in the configured timezone. - * - * @param \DateTimeInterface $dateTime A DateTimeInterface object - * - * @return int A timestamp - * - * @throws TransformationFailedException If the given value is not a \DateTimeInterface - */ - public function transform($dateTime) - { - if (null === $dateTime) { - return; - } - - if (!$dateTime instanceof \DateTimeInterface) { - throw new TransformationFailedException('Expected a \DateTimeInterface.'); - } - - return $dateTime->getTimestamp(); - } + use DateTimeImmutableTransformerDecoratorTrait; - /** - * Transforms a timestamp in the configured timezone into a DateTime object. - * - * @param string $value A timestamp - * - * @return \DateTime A \DateTime object - * - * @throws TransformationFailedException If the given value is not a timestamp - * or if the given timestamp is invalid - */ - public function reverseTransform($value) + public function __construct($inputTimezone = null, $outputTimezone = null) { - if (null === $value) { - return; - } - - if (!is_numeric($value)) { - throw new TransformationFailedException('Expected a numeric.'); - } - - try { - $dateTime = new \DateTime(); - $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); - $dateTime->setTimestamp($value); - - if ($this->inputTimezone !== $this->outputTimezone) { - $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); - } - } catch (\Exception $e) { - throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); - } - - return $dateTime; + parent::__construct($inputTimezone, $outputTimezone); + $this->decorated = new DateTimeImmutableToTimestampTransformer($inputTimezone, $outputTimezone); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index d34d1c210044c..e08e94b620d49 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -12,18 +12,19 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; -use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; -use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToLocalizedStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToRfc3339Transformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToTimestampTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToImmutableTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\ReversedTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -93,12 +94,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) if ('single_text' === $options['widget']) { if (self::HTML5_FORMAT === $pattern) { - $builder->addViewTransformer(new DateTimeToRfc3339Transformer( + $builder->addViewTransformer(new DateTimeImmutableToRfc3339Transformer( $options['model_timezone'], $options['view_timezone'] )); } else { - $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( + $builder->addViewTransformer(new DateTimeImmutableToLocalizedStringTransformer( $options['model_timezone'], $options['view_timezone'], $dateFormat, @@ -154,28 +155,29 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder ->addViewTransformer(new DataTransformerChain(array( - new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts), + new DateTimeImmutableToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts), new ArrayToPartsTransformer(array( 'date' => $dateParts, 'time' => $timeParts, )), ))) ->add('date', __NAMESPACE__.'\DateType', $dateOptions) - ->add('time', __NAMESPACE__.'\TimeType', $timeOptions) - ; + ->add('time', __NAMESPACE__.'\TimeType', $timeOptions); } - if ('string' === $options['input']) { + if ('datetime' === $options['input']) { + $builder->addModelTransformer(new DateTimeToImmutableTransformer()); + } elseif ('string' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone']) + new DateTimeImmutableToStringTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('timestamp' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) + new DateTimeImmutableToTimestampTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('array' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) + new DateTimeImmutableToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) )); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index bf51b15b6a4dc..796a2d1181fbd 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -12,17 +12,18 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToLocalizedStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToTimestampTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToImmutableTransformer; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\ReversedTransformer; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; class DateType extends AbstractType { @@ -60,7 +61,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" or "d". Its current value is "%s".', $pattern)); } - $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( + $builder->addViewTransformer(new DateTimeImmutableToLocalizedStringTransformer( $options['model_timezone'], $options['view_timezone'], $dateFormat, @@ -116,24 +117,25 @@ class_exists('IntlTimeZone', false) ? \IntlTimeZone::createDefault() : null, ->add('year', self::$widgets[$options['widget']], $yearOptions) ->add('month', self::$widgets[$options['widget']], $monthOptions) ->add('day', self::$widgets[$options['widget']], $dayOptions) - ->addViewTransformer(new DateTimeToArrayTransformer( + ->addViewTransformer(new DateTimeImmutableToArrayTransformer( $options['model_timezone'], $options['view_timezone'], array('year', 'month', 'day') )) - ->setAttribute('formatter', $formatter) - ; + ->setAttribute('formatter', $formatter); } - if ('string' === $options['input']) { + if ('datetime' === $options['input']) { + $builder->addModelTransformer(new DateTimeToImmutableTransformer()); + } elseif ('string' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], 'Y-m-d') + new DateTimeImmutableToStringTransformer($options['model_timezone'], $options['model_timezone'], 'Y-m-d') )); } elseif ('timestamp' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) + new DateTimeImmutableToTimestampTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('array' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], array('year', 'month', 'day')) + new DateTimeImmutableToArrayTransformer($options['model_timezone'], $options['model_timezone'], array('year', 'month', 'day')) )); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index d1c04f73d9278..fa000bce52c58 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -12,16 +12,17 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Exception\InvalidConfigurationException; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToArrayTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToStringTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToTimestampTransformer; +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToImmutableTransformer; +use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; -use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\ReversedTransformer; -use Symfony\Component\Form\Exception\InvalidConfigurationException; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; -use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\FormView; +use Symfony\Component\Form\ReversedTransformer; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -55,7 +56,7 @@ public function buildForm(FormBuilderInterface $builder, array $options) } if ('single_text' === $options['widget']) { - $builder->addViewTransformer(new DateTimeToStringTransformer($options['model_timezone'], $options['view_timezone'], $format)); + $builder->addViewTransformer(new DateTimeImmutableToStringTransformer($options['model_timezone'], $options['view_timezone'], $format)); // handle seconds ignored by user's browser when with_seconds enabled // https://codereview.chromium.org/450533009/ @@ -130,20 +131,22 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder->add('second', self::$widgets[$options['widget']], $secondOptions); } - $builder->addViewTransformer(new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget'])); + $builder->addViewTransformer(new DateTimeImmutableToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget'])); } - if ('string' === $options['input']) { + if ('datetime' === $options['input']) { + $builder->addModelTransformer(new DateTimeToImmutableTransformer()); + } elseif ('string' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], 'H:i:s') + new DateTimeImmutableToStringTransformer($options['model_timezone'], $options['model_timezone'], 'H:i:s') )); } elseif ('timestamp' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) + new DateTimeImmutableToTimestampTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('array' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( - new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) + new DateTimeImmutableToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) )); } } From db757d25f985b3fe5b3c9e363a74377b87f974b9 Mon Sep 17 00:00:00 2001 From: Valentin Date: Sat, 2 Dec 2017 20:01:30 +0300 Subject: [PATCH 2/4] Fixed the codestyle issues --- .../DateTimeImmutableToArrayTransformer.php | 14 +++++++------- .../DateTimeImmutableTransformerDecoratorTrait.php | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php index dce82158a55f2..d3f61b1df120c 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php @@ -85,7 +85,7 @@ public function transform($dateTime): array if (!$this->pad) { foreach ($result as &$entry) { // remove leading zeros - $entry = (string)(int)$entry; + $entry = (string) (int) $entry; } // unset reference to keep scope clear unset($entry); @@ -130,15 +130,15 @@ public function reverseTransform($value): ?\DateTimeImmutable throw new TransformationFailedException(sprintf('The fields "%s" should not be empty', implode('", "', $emptyFields))); } - if (isset($value['month']) && !ctype_digit((string)$value['month'])) { + if (isset($value['month']) && !ctype_digit((string) $value['month'])) { throw new TransformationFailedException('This month is invalid'); } - if (isset($value['day']) && !ctype_digit((string)$value['day'])) { + if (isset($value['day']) && !ctype_digit((string) $value['day'])) { throw new TransformationFailedException('This day is invalid'); } - if (isset($value['year']) && !ctype_digit((string)$value['year'])) { + if (isset($value['year']) && !ctype_digit((string) $value['year'])) { throw new TransformationFailedException('This year is invalid'); } @@ -146,15 +146,15 @@ public function reverseTransform($value): ?\DateTimeImmutable throw new TransformationFailedException('This is an invalid date'); } - if (isset($value['hour']) && !ctype_digit((string)$value['hour'])) { + if (isset($value['hour']) && !ctype_digit((string) $value['hour'])) { throw new TransformationFailedException('This hour is invalid'); } - if (isset($value['minute']) && !ctype_digit((string)$value['minute'])) { + if (isset($value['minute']) && !ctype_digit((string) $value['minute'])) { throw new TransformationFailedException('This minute is invalid'); } - if (isset($value['second']) && !ctype_digit((string)$value['second'])) { + if (isset($value['second']) && !ctype_digit((string) $value['second'])) { throw new TransformationFailedException('This second is invalid'); } diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php index 9e6bb21512a8d..e07f76a35270f 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableTransformerDecoratorTrait.php @@ -14,7 +14,7 @@ use Symfony\Component\Form\DataTransformerInterface; /** - * A BC-layer trait for DateTimeTo*Transformers + * A BC-layer trait for DateTimeTo*Transformers. * * @author Valentin Udaltsov * From 37f542926b522521e5074df97baf0c88905d6d09 Mon Sep 17 00:00:00 2001 From: Valentin Date: Sun, 3 Dec 2017 17:37:42 +0300 Subject: [PATCH 3/4] Removed blank lines in property declarations. --- .../DataTransformer/DateTimeImmutableToArrayTransformer.php | 1 - .../DateTimeImmutableToLocalizedStringTransformer.php | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php index d3f61b1df120c..e50abe468902b 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformer.php @@ -22,7 +22,6 @@ class DateTimeImmutableToArrayTransformer extends BaseDateTimeTransformer { private $pad; - private $fields; /** diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php index 9d5f41a324670..b5b2f9a86caba 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformer.php @@ -23,11 +23,8 @@ class DateTimeImmutableToLocalizedStringTransformer extends BaseDateTimeTransformer { private $dateFormat; - private $timeFormat; - private $pattern; - private $calendar; /** From c0e49815ed59aa5500dcd265ee3fa544da5c088a Mon Sep 17 00:00:00 2001 From: Valentin Date: Sun, 3 Dec 2017 17:53:53 +0300 Subject: [PATCH 4/4] Tests for transformers --- .../DateTimeImmutableTestCase.php | 22 + ...ateTimeImmutableToArrayTransformerTest.php | 560 ++++++++++++++++++ ...utableToLocalizedStringTransformerTest.php | 304 ++++++++++ ...eTimeImmutableToRfc3339TransformerTest.php | 123 ++++ ...teTimeImmutableToStringTransformerTest.php | 163 +++++ ...imeImmutableToTimestampTransformerTest.php | 103 ++++ 6 files changed, 1275 insertions(+) create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableTestCase.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformerTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformerTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339TransformerTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformerTest.php create mode 100644 src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformerTest.php diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableTestCase.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableTestCase.php new file mode 100644 index 0000000000000..9cddeb47c6d32 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableTestCase.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use PHPUnit\Framework\TestCase; + +abstract class DateTimeImmutableTestCase extends TestCase +{ + public static function assertDateTimeImmutableEquals(\DateTimeImmutable $expected, \DateTimeImmutable $actual) + { + self::assertEquals($expected->format('U'), $actual->format('U')); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformerTest.php new file mode 100644 index 0000000000000..305a588f306a7 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToArrayTransformerTest.php @@ -0,0 +1,560 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToArrayTransformer; + +class DateTimeImmutableToArrayTransformerTest extends DateTimeImmutableTestCase +{ + public function testTransform() + { + $transformer = new DateTimeImmutableToArrayTransformer('UTC', 'UTC'); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + + $output = array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + ); + + $this->assertSame($output, $transformer->transform($input)); + } + + public function testTransformEmpty() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + + $output = array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); + + $this->assertSame($output, $transformer->transform(null)); + } + + public function testTransformEmptyWithFields() + { + $transformer = new DateTimeImmutableToArrayTransformer(null, null, array('year', 'minute', 'second')); + + $output = array( + 'year' => '', + 'minute' => '', + 'second' => '', + ); + + $this->assertSame($output, $transformer->transform(null)); + } + + public function testTransformWithFields() + { + $transformer = new DateTimeImmutableToArrayTransformer('UTC', 'UTC', array('year', 'month', 'minute', 'second')); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + + $output = array( + 'year' => '2010', + 'month' => '2', + 'minute' => '5', + 'second' => '6', + ); + + $this->assertSame($output, $transformer->transform($input)); + } + + public function testTransformWithPadding() + { + $transformer = new DateTimeImmutableToArrayTransformer('UTC', 'UTC', null, true); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + + $output = array( + 'year' => '2010', + 'month' => '02', + 'day' => '03', + 'hour' => '04', + 'minute' => '05', + 'second' => '06', + ); + + $this->assertSame($output, $transformer->transform($input)); + } + + public function testTransformDifferentTimezones() + { + $transformer = new DateTimeImmutableToArrayTransformer('America/New_York', 'Asia/Hong_Kong'); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 America/New_York'); + + $dateTime = new \DateTimeImmutable('2010-02-03 04:05:06 America/New_York'); + $dateTime = $dateTime->setTimezone(new \DateTimeZone('Asia/Hong_Kong')); + $output = array( + 'year' => (string) (int) $dateTime->format('Y'), + 'month' => (string) (int) $dateTime->format('m'), + 'day' => (string) (int) $dateTime->format('d'), + 'hour' => (string) (int) $dateTime->format('H'), + 'minute' => (string) (int) $dateTime->format('i'), + 'second' => (string) (int) $dateTime->format('s'), + ); + + $this->assertSame($output, $transformer->transform($input)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testTransformRequiresDateTime() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform('12345'); + } + + public function testReverseTransform() + { + $transformer = new DateTimeImmutableToArrayTransformer('UTC', 'UTC'); + + $input = array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + ); + + $output = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + + $this->assertDateTimeImmutableEquals($output, $transformer->reverseTransform($input)); + } + + public function testReverseTransformWithSomeZero() + { + $transformer = new DateTimeImmutableToArrayTransformer('UTC', 'UTC'); + + $input = array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '0', + 'second' => '0', + ); + + $output = new \DateTimeImmutable('2010-02-03 04:00:00 UTC'); + + $this->assertDateTimeImmutableEquals($output, $transformer->reverseTransform($input)); + } + + public function testReverseTransformCompletelyEmpty() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + + $input = array( + 'year' => '', + 'month' => '', + 'day' => '', + 'hour' => '', + 'minute' => '', + 'second' => '', + ); + + $this->assertNull($transformer->reverseTransform($input)); + } + + public function testReverseTransformCompletelyEmptySubsetOfFields() + { + $transformer = new DateTimeImmutableToArrayTransformer(null, null, array('year', 'month', 'day')); + + $input = array( + 'year' => '', + 'month' => '', + 'day' => '', + ); + + $this->assertNull($transformer->reverseTransform($input)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformPartiallyEmptyYear() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformPartiallyEmptyMonth() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformPartiallyEmptyDay() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformPartiallyEmptyHour() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformPartiallyEmptyMinute() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformPartiallyEmptySecond() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + )); + } + + public function testReverseTransformNull() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + + $this->assertNull($transformer->reverseTransform(null)); + } + + public function testReverseTransformDifferentTimezones() + { + $transformer = new DateTimeImmutableToArrayTransformer('America/New_York', 'Asia/Hong_Kong'); + + $input = array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + ); + + $output = new \DateTimeImmutable('2010-02-03 04:05:06 Asia/Hong_Kong'); + $output->setTimezone(new \DateTimeZone('America/New_York')); + + $this->assertDateTimeImmutableEquals($output, $transformer->reverseTransform($input)); + } + + public function testReverseTransformToDifferentTimezone() + { + $transformer = new DateTimeImmutableToArrayTransformer('Asia/Hong_Kong', 'UTC'); + + $input = array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + ); + + $output = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + $output->setTimezone(new \DateTimeZone('Asia/Hong_Kong')); + + $this->assertDateTimeImmutableEquals($output, $transformer->reverseTransform($input)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformRequiresArray() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform('12345'); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNegativeYear() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '-1', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNegativeMonth() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '-1', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNegativeDay() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '-1', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNegativeHour() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '-1', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNegativeMinute() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '-1', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNegativeSecond() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '-1', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithInvalidMonth() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '13', + 'day' => '3', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithInvalidDay() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '31', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithStringDay() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => 'bazinga', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithStringMonth() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => 'bazinga', + 'day' => '31', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithStringYear() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => 'bazinga', + 'month' => '2', + 'day' => '31', + 'hour' => '4', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithEmptyStringHour() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '31', + 'hour' => '', + 'minute' => '5', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithEmptyStringMinute() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '31', + 'hour' => '4', + 'minute' => '', + 'second' => '6', + )); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithEmptyStringSecond() + { + $transformer = new DateTimeImmutableToArrayTransformer(); + $transformer->reverseTransform(array( + 'year' => '2010', + 'month' => '2', + 'day' => '31', + 'hour' => '4', + 'minute' => '5', + 'second' => '', + )); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformerTest.php new file mode 100644 index 0000000000000..33619c77c398f --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToLocalizedStringTransformerTest.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToLocalizedStringTransformer; +use Symfony\Component\Intl\Util\IntlTestHelper; + +class DateTimeImmutableToLocalizedStringTransformerTest extends DateTimeImmutableTestCase +{ + protected $dateTime; + protected $dateTimeWithoutSeconds; + + protected function setUp() + { + parent::setUp(); + + // Since we test against "de_AT", we need the full implementation + IntlTestHelper::requireFullIntl($this, '57.1'); + + \Locale::setDefault('de_AT'); + + $this->dateTime = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + $this->dateTimeWithoutSeconds = new \DateTimeImmutable('2010-02-03 04:05:00 UTC'); + } + + protected function tearDown() + { + $this->dateTime = null; + $this->dateTimeWithoutSeconds = null; + } + + public function dataProvider() + { + return array( + array(\IntlDateFormatter::SHORT, null, null, '03.02.10, 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::MEDIUM, null, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::LONG, null, null, '3. Februar 2010 um 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::FULL, null, null, 'Mittwoch, 3. Februar 2010 um 04:05', '2010-02-03 04:05:00 UTC'), + array(\IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, null, '03.02.10', '2010-02-03 00:00:00 UTC'), + array(\IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE, null, '03.02.2010', '2010-02-03 00:00:00 UTC'), + array(\IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, '3. Februar 2010', '2010-02-03 00:00:00 UTC'), + array(\IntlDateFormatter::FULL, \IntlDateFormatter::NONE, null, 'Mittwoch, 3. Februar 2010', '2010-02-03 00:00:00 UTC'), + array(null, \IntlDateFormatter::SHORT, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'), + array(null, \IntlDateFormatter::MEDIUM, null, '03.02.2010, 04:05:06', '2010-02-03 04:05:06 UTC'), + array(null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 UTC', '2010-02-03 04:05:06 UTC'), + array(null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 UTC', '2010-02-03 04:05:06 GMT'), + // see below for extra test case for time format FULL + array(\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, null, '04:05', '1970-01-01 04:05:00 UTC'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, null, '04:05:06', '1970-01-01 04:05:06 UTC'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 UTC', '1970-01-01 04:05:06 GMT'), + array(\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 UTC', '1970-01-01 04:05:06 UTC'), + array(null, null, 'yyyy-MM-dd HH:mm:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'), + array(null, null, 'yyyy-MM-dd HH:mm', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'), + array(null, null, 'yyyy-MM-dd HH', '2010-02-03 04', '2010-02-03 04:00:00 UTC'), + array(null, null, 'yyyy-MM-dd', '2010-02-03', '2010-02-03 00:00:00 UTC'), + array(null, null, 'yyyy-MM', '2010-02', '2010-02-01 00:00:00 UTC'), + array(null, null, 'yyyy', '2010', '2010-01-01 00:00:00 UTC'), + array(null, null, 'dd-MM-yyyy', '03-02-2010', '2010-02-03 00:00:00 UTC'), + array(null, null, 'HH:mm:ss', '04:05:06', '1970-01-01 04:05:06 UTC'), + array(null, null, 'HH:mm:00', '04:05:00', '1970-01-01 04:05:00 UTC'), + array(null, null, 'HH:mm', '04:05', '1970-01-01 04:05:00 UTC'), + array(null, null, 'HH', '04', '1970-01-01 04:00:00 UTC'), + ); + } + + /** + * @dataProvider dataProvider + */ + public function testTransform($dateFormat, $timeFormat, $pattern, $output, $input) + { + IntlTestHelper::requireFullIntl($this, '59.1'); + \Locale::setDefault('de_AT'); + + $transformer = new DateTimeImmutableToLocalizedStringTransformer( + 'UTC', + 'UTC', + $dateFormat, + $timeFormat, + \IntlDateFormatter::GREGORIAN, + $pattern + ); + + $input = new \DateTimeImmutable($input); + + $this->assertEquals($output, $transformer->transform($input)); + } + + public function testTransformFullTime() + { + IntlTestHelper::requireFullIntl($this, '59.1'); + \Locale::setDefault('de_AT'); + + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::FULL); + + $this->assertEquals('03.02.2010, 04:05:06 Koordinierte Weltzeit', $transformer->transform($this->dateTime)); + } + + public function testTransformToDifferentLocale() + { + \Locale::setDefault('en_US'); + + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC'); + + $this->assertEquals('Feb 3, 2010, 4:05 AM', $transformer->transform($this->dateTime)); + } + + public function testTransformEmpty() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + + $this->assertSame('', $transformer->transform(null)); + } + + public function testTransformWithDifferentTimezones() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('America/New_York', 'Asia/Hong_Kong'); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 America/New_York'); + + $dateTime = $input->setTimezone(new \DateTimeZone('Asia/Hong_Kong')); + + $this->assertEquals($dateTime->format('d.m.Y, H:i'), $transformer->transform($input)); + } + + public function testReverseTransformWithNoConstructorParameters() + { + $tz = date_default_timezone_get(); + date_default_timezone_set('Europe/Rome'); + + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + + $dateTime = new \DateTimeImmutable('2010-02-03 04:05'); + + $this->assertEquals( + $dateTime->format('c'), + $transformer->reverseTransform('03.02.2010, 04:05')->format('c') + ); + + date_default_timezone_set($tz); + } + + public function testTransformWithDifferentPatterns() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'MM*yyyy*dd HH|mm|ss'); + + $this->assertEquals('02*2010*03 04|05|06', $transformer->transform($this->dateTime)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testTransformRequiresValidDateTime() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + $transformer->transform('2010-01-01'); + } + + public function testTransformWrapsIntlErrors() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + + $this->markTestIncomplete('Checking for intl errors needs to be reimplemented'); + + // HOW TO REPRODUCE? + + //$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Extension\Core\DataTransformer\TransformationFailedException'); + + //$transformer->transform(1.5); + } + + /** + * @dataProvider dataProvider + */ + public function testReverseTransform($dateFormat, $timeFormat, $pattern, $input, $output) + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer( + 'UTC', + 'UTC', + $dateFormat, + $timeFormat, + \IntlDateFormatter::GREGORIAN, + $pattern + ); + + $output = new \DateTimeImmutable($output); + + $this->assertEquals($output, $transformer->reverseTransform($input)); + } + + public function testReverseTransformFullTime() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::FULL); + + $this->assertDateTimeImmutableEquals($this->dateTime, $transformer->reverseTransform('03.02.2010, 04:05:06 GMT+00:00')); + } + + public function testReverseTransformFromDifferentLocale() + { + \Locale::setDefault('en_US'); + + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC'); + + $this->assertDateTimeImmutableEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('Feb 3, 2010, 04:05 AM')); + } + + public function testReverseTransformWithDifferentTimezones() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('America/New_York', 'Asia/Hong_Kong'); + + $dateTime = new \DateTimeImmutable('2010-02-03 04:05:00 Asia/Hong_Kong'); + $dateTime->setTimezone(new \DateTimeZone('America/New_York')); + + $this->assertDateTimeImmutableEquals($dateTime, $transformer->reverseTransform('03.02.2010, 04:05')); + } + + public function testReverseTransformOnlyDateWithDifferentTimezones() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('Europe/Berlin', 'Pacific/Tahiti', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd'); + + $dateTime = new \DateTimeImmutable('2017-01-10 11:00', new \DateTimeZone('Europe/Berlin')); + + $this->assertDateTimeImmutableEquals($dateTime, $transformer->reverseTransform('2017-01-10')); + } + + public function testReverseTransformWithDifferentPatterns() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'MM*yyyy*dd HH|mm|ss'); + + $this->assertDateTimeImmutableEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06')); + } + + public function testReverseTransformDateOnlyWithDstIssue() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'dd/MM/yyyy'); + + $this->assertDateTimeImmutableEquals( + new \DateTimeImmutable('1978-05-28', new \DateTimeZone('Europe/Rome')), + $transformer->reverseTransform('28/05/1978') + ); + } + + public function testReverseTransformDateOnlyWithDstIssueAndEscapedText() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, "'day': dd 'month': MM 'year': yyyy"); + + $this->assertDateTimeImmutableEquals( + new \DateTimeImmutable('1978-05-28', new \DateTimeZone('Europe/Rome')), + $transformer->reverseTransform('day: 28 month: 05 year: 1978') + ); + } + + public function testReverseTransformEmpty() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + + $this->assertNull($transformer->reverseTransform('')); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformRequiresString() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + $transformer->reverseTransform(12345); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWrapsIntlErrors() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer(); + $transformer->reverseTransform('12345'); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNonExistingDate() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::SHORT); + + $this->assertDateTimeImmutableEquals($this->dateTimeWithoutSeconds, $transformer->reverseTransform('31.04.10 04:05')); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformOutOfTimestampRange() + { + $transformer = new DateTimeImmutableToLocalizedStringTransformer('UTC', 'UTC'); + $transformer->reverseTransform('1789-07-14'); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339TransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339TransformerTest.php new file mode 100644 index 0000000000000..13140ff141746 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToRfc3339TransformerTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToRfc3339Transformer; + +class DateTimeImmutableToRfc3339TransformerTest extends DateTimeImmutableTestCase +{ + protected $dateTime; + protected $dateTimeWithoutSeconds; + + protected function setUp() + { + parent::setUp(); + + $this->dateTime = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + $this->dateTimeWithoutSeconds = new \DateTimeImmutable('2010-02-03 04:05:00 UTC'); + } + + protected function tearDown() + { + $this->dateTime = null; + $this->dateTimeWithoutSeconds = null; + } + + public function allProvider() + { + return array( + array('UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06Z'), + array('UTC', 'UTC', null, ''), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06+08:00'), + array('America/New_York', 'Asia/Hong_Kong', null, ''), + array('UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06+08:00'), + array('America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06Z'), + ); + } + + public function transformProvider() + { + return $this->allProvider(); + } + + public function reverseTransformProvider() + { + return array_merge($this->allProvider(), array( + // format without seconds, as appears in some browsers + array('UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05Z'), + array('America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05+08:00'), + array('Europe/Amsterdam', 'Europe/Amsterdam', '2013-08-21 10:30:00 Europe/Amsterdam', '2013-08-21T08:30:00Z'), + )); + } + + /** + * @dataProvider transformProvider + */ + public function testTransform($fromTz, $toTz, $from, $to) + { + $transformer = new DateTimeImmutableToRfc3339Transformer($fromTz, $toTz); + + $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null)); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testTransformRequiresValidDateTime() + { + $transformer = new DateTimeImmutableToRfc3339Transformer(); + $transformer->transform('2010-01-01'); + } + + /** + * @dataProvider reverseTransformProvider + */ + public function testReverseTransform($toTz, $fromTz, $to, $from) + { + $transformer = new DateTimeImmutableToRfc3339Transformer($toTz, $fromTz); + + if (null !== $to) { + $this->assertDateTimeImmutableEquals(new \DateTimeImmutable($to), $transformer->reverseTransform($from)); + } else { + $this->assertSame($to, $transformer->reverseTransform($from)); + } + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformRequiresString() + { + $transformer = new DateTimeImmutableToRfc3339Transformer(); + $transformer->reverseTransform(12345); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformWithNonExistingDate() + { + $transformer = new DateTimeImmutableToRfc3339Transformer('UTC', 'UTC'); + + $transformer->reverseTransform('2010-04-31T04:05Z'); + } + + /** + * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException + */ + public function testReverseTransformExpectsValidDateString() + { + $transformer = new DateTimeImmutableToRfc3339Transformer('UTC', 'UTC'); + + $transformer->reverseTransform('2010-2010-2010'); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformerTest.php new file mode 100644 index 0000000000000..c8448bb7aa72a --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToStringTransformerTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToStringTransformer; + +class DateTimeImmutableToStringTransformerTest extends DateTimeImmutableTestCase +{ + public function dataProvider() + { + $data = array( + array('Y-m-d H:i:s', '2010-02-03 16:05:06', '2010-02-03 16:05:06 UTC'), + array('Y-m-d H:i:00', '2010-02-03 16:05:00', '2010-02-03 16:05:00 UTC'), + array('Y-m-d H:i', '2010-02-03 16:05', '2010-02-03 16:05:00 UTC'), + array('Y-m-d H', '2010-02-03 16', '2010-02-03 16:00:00 UTC'), + array('Y-m-d', '2010-02-03', '2010-02-03 00:00:00 UTC'), + array('Y-m', '2010-12', '2010-12-01 00:00:00 UTC'), + array('Y', '2010', '2010-01-01 00:00:00 UTC'), + array('d-m-Y', '03-02-2010', '2010-02-03 00:00:00 UTC'), + array('H:i:s', '16:05:06', '1970-01-01 16:05:06 UTC'), + array('H:i:00', '16:05:00', '1970-01-01 16:05:00 UTC'), + array('H:i', '16:05', '1970-01-01 16:05:00 UTC'), + array('H', '16', '1970-01-01 16:00:00 UTC'), + array('Y-z', '2010-33', '2010-02-03 00:00:00 UTC'), + + // different day representations + array('Y-m-j', '2010-02-3', '2010-02-03 00:00:00 UTC'), + array('z', '33', '1970-02-03 00:00:00 UTC'), + + // not bijective + // this will not work as PHP will use actual date to replace missing info + // and after change of date will lookup for closest Wednesday + // i.e. value: 2010-02, PHP value: 2010-02-(today i.e. 20), parsed date: 2010-02-24 + //array('Y-m-D', '2010-02-Wed', '2010-02-03 00:00:00 UTC'), + //array('Y-m-l', '2010-02-Wednesday', '2010-02-03 00:00:00 UTC'), + + // different month representations + array('Y-n-d', '2010-2-03', '2010-02-03 00:00:00 UTC'), + array('Y-M-d', '2010-Feb-03', '2010-02-03 00:00:00 UTC'), + array('Y-F-d', '2010-February-03', '2010-02-03 00:00:00 UTC'), + + // different year representations + array('y-m-d', '10-02-03', '2010-02-03 00:00:00 UTC'), + + // different time representations + array('G:i:s', '16:05:06', '1970-01-01 16:05:06 UTC'), + array('g:i:s a', '4:05:06 pm', '1970-01-01 16:05:06 UTC'), + array('h:i:s a', '04:05:06 pm', '1970-01-01 16:05:06 UTC'), + + // seconds since Unix + array('U', '1265213106', '2010-02-03 16:05:06 UTC'), + + array('Y-z', '2010-33', '2010-02-03 00:00:00 UTC'), + ); + + return $data; + } + + /** + * @dataProvider dataProvider + */ + public function testTransform($format, $output, $input) + { + $transformer = new DateTimeImmutableToStringTransformer('UTC', 'UTC', $format); + + $input = new \DateTimeImmutable($input); + + $this->assertEquals($output, $transformer->transform($input)); + } + + public function testTransformEmpty() + { + $transformer = new DateTimeImmutableToStringTransformer(); + + $this->assertSame('', $transformer->transform(null)); + } + + public function testTransformWithDifferentTimezones() + { + $transformer = new DateTimeImmutableToStringTransformer('Asia/Hong_Kong', 'America/New_York', 'Y-m-d H:i:s'); + + $input = new \DateTimeImmutable('2010-02-03 12:05:06 America/New_York'); + $output = $input->format('Y-m-d H:i:s'); + $input->setTimezone(new \DateTimeZone('Asia/Hong_Kong')); + + $this->assertEquals($output, $transformer->transform($input)); + } + + public function testTransformExpectsDateTime() + { + $transformer = new DateTimeImmutableToStringTransformer(); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Exception\TransformationFailedException'); + + $transformer->transform('1234'); + } + + /** + * @dataProvider dataProvider + */ + public function testReverseTransform($format, $input, $output) + { + $reverseTransformer = new DateTimeImmutableToStringTransformer('UTC', 'UTC', $format); + + $output = new \DateTimeImmutable($output); + + $this->assertDateTimeImmutableEquals($output, $reverseTransformer->reverseTransform($input)); + } + + public function testReverseTransformEmpty() + { + $reverseTransformer = new DateTimeImmutableToStringTransformer(); + + $this->assertNull($reverseTransformer->reverseTransform('')); + } + + public function testReverseTransformWithDifferentTimezones() + { + $reverseTransformer = new DateTimeImmutableToStringTransformer('America/New_York', 'Asia/Hong_Kong', 'Y-m-d H:i:s'); + + $output = new \DateTimeImmutable('2010-02-03 16:05:06 Asia/Hong_Kong'); + $input = $output->format('Y-m-d H:i:s'); + $output->setTimezone(new \DateTimeZone('America/New_York')); + + $this->assertDateTimeImmutableEquals($output, $reverseTransformer->reverseTransform($input)); + } + + public function testReverseTransformExpectsString() + { + $reverseTransformer = new DateTimeImmutableToStringTransformer(); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Exception\TransformationFailedException'); + + $reverseTransformer->reverseTransform(1234); + } + + public function testReverseTransformExpectsValidDateString() + { + $reverseTransformer = new DateTimeImmutableToStringTransformer(); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Exception\TransformationFailedException'); + + $reverseTransformer->reverseTransform('2010-2010-2010'); + } + + public function testReverseTransformWithNonExistingDate() + { + $reverseTransformer = new DateTimeImmutableToStringTransformer(); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Exception\TransformationFailedException'); + + $reverseTransformer->reverseTransform('2010-04-31'); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformerTest.php new file mode 100644 index 0000000000000..d7ed159ebdb89 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeImmutableToTimestampTransformerTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Core\DataTransformer; + +use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeImmutableToTimestampTransformer; + +class DateTimeImmutableToTimestampTransformerTest extends DateTimeImmutableTestCase +{ + public function testTransform() + { + $transformer = new DateTimeImmutableToTimestampTransformer('UTC', 'UTC'); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + $output = $input->format('U'); + + $this->assertEquals($output, $transformer->transform($input)); + } + + public function testTransformEmpty() + { + $transformer = new DateTimeImmutableToTimestampTransformer(); + + $this->assertNull($transformer->transform(null)); + } + + public function testTransformWithDifferentTimezones() + { + $transformer = new DateTimeImmutableToTimestampTransformer('Asia/Hong_Kong', 'America/New_York'); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 America/New_York'); + $output = $input->format('U'); + $input->setTimezone(new \DateTimeZone('Asia/Hong_Kong')); + + $this->assertEquals($output, $transformer->transform($input)); + } + + public function testTransformFromDifferentTimezone() + { + $transformer = new DateTimeImmutableToTimestampTransformer('Asia/Hong_Kong', 'UTC'); + + $input = new \DateTimeImmutable('2010-02-03 04:05:06 Asia/Hong_Kong'); + + $dateTime = $input->setTimezone(new \DateTimeZone('UTC')); + $output = $dateTime->format('U'); + + $this->assertEquals($output, $transformer->transform($input)); + } + + public function testTransformExpectsDateTime() + { + $transformer = new DateTimeImmutableToTimestampTransformer(); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Exception\TransformationFailedException'); + + $transformer->transform('1234'); + } + + public function testReverseTransform() + { + $reverseTransformer = new DateTimeImmutableToTimestampTransformer('UTC', 'UTC'); + + $output = new \DateTimeImmutable('2010-02-03 04:05:06 UTC'); + $input = $output->format('U'); + + $this->assertDateTimeImmutableEquals($output, $reverseTransformer->reverseTransform($input)); + } + + public function testReverseTransformEmpty() + { + $reverseTransformer = new DateTimeImmutableToTimestampTransformer(); + + $this->assertNull($reverseTransformer->reverseTransform(null)); + } + + public function testReverseTransformWithDifferentTimezones() + { + $reverseTransformer = new DateTimeImmutableToTimestampTransformer('Asia/Hong_Kong', 'America/New_York'); + + $output = new \DateTimeImmutable('2010-02-03 04:05:06 America/New_York'); + $input = $output->format('U'); + $output->setTimezone(new \DateTimeZone('Asia/Hong_Kong')); + + $this->assertDateTimeImmutableEquals($output, $reverseTransformer->reverseTransform($input)); + } + + public function testReverseTransformExpectsValidTimestamp() + { + $reverseTransformer = new DateTimeImmutableToTimestampTransformer(); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('Symfony\Component\Form\Exception\TransformationFailedException'); + + $reverseTransformer->reverseTransform('2010-2010-2010'); + } +}