Fix #19531 [Form] DateType fails parsing when midnight is not a valid time

This commit is contained in:
Matteo Beccati 2016-08-05 12:25:39 +02:00
parent aae8e3da57
commit c951bb6e97
2 changed files with 57 additions and 6 deletions

View File

@ -117,20 +117,29 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
return; return;
} }
$timestamp = $this->getIntlDateFormatter()->parse($value); // 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 (intl_get_error_code() != 0) { if (intl_get_error_code() != 0) {
throw new TransformationFailedException(intl_get_error_message()); throw new TransformationFailedException(intl_get_error_message());
} }
try { try {
// read timestamp into DateTime object - the formatter delivers in UTC if ($dateOnly) {
$dateTime = new \DateTime(sprintf('@%s', $timestamp)); // we only care about year-month-date, which has been delivered as a timestamp pointing to UTC midnight
return new \DateTime(gmdate('Y-m-d', $timestamp), new \DateTimeZone($this->inputTimezone));
}
// read timestamp into DateTime object - the formatter delivers a timestamp
$dateTime = new \DateTime(sprintf('@%s', $timestamp), new \DateTimeZone($this->outputTimezone));
} catch (\Exception $e) { } catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
} }
if ('UTC' !== $this->inputTimezone) { if ($this->outputTimezone !== $this->inputTimezone) {
$dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone));
} }
@ -140,15 +149,17 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
/** /**
* Returns a preconfigured IntlDateFormatter instance. * Returns a preconfigured IntlDateFormatter instance.
* *
* @param bool $ignoreTimezone Use UTC regardless of the configured timezone.
*
* @return \IntlDateFormatter * @return \IntlDateFormatter
* *
* @throws TransformationFailedException in case the date formatter can not be constructed. * @throws TransformationFailedException in case the date formatter can not be constructed.
*/ */
protected function getIntlDateFormatter() protected function getIntlDateFormatter($ignoreTimezone = false)
{ {
$dateFormat = $this->dateFormat; $dateFormat = $this->dateFormat;
$timeFormat = $this->timeFormat; $timeFormat = $this->timeFormat;
$timezone = $this->outputTimezone; $timezone = $ignoreTimezone ? 'UTC' : $this->outputTimezone;
$calendar = $this->calendar; $calendar = $this->calendar;
$pattern = $this->pattern; $pattern = $this->pattern;
@ -163,4 +174,24 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
return $intlDateFormatter; return $intlDateFormatter;
} }
/**
* Checks if the pattern contains only a date.
*
* @param string $pattern The input pattern
*
* @return bool
*/
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);
}
} }

View File

@ -230,6 +230,26 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase
$this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06')); $this->assertDateTimeEquals($this->dateTime, $transformer->reverseTransform('02*2010*03 04|05|06'));
} }
public function testReverseTransformDateOnlyWithDstIssue()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'dd/MM/yyyy');
$this->assertDateTimeEquals(
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
$transformer->reverseTransform('28/05/1978')
);
}
public function testReverseTransformDateOnlyWithDstIssueAndEscapedText()
{
$transformer = new DateTimeToLocalizedStringTransformer('Europe/Rome', 'Europe/Rome', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, "'day': dd 'month': MM 'year': yyyy");
$this->assertDateTimeEquals(
new \DateTime('1978-05-28', new \DateTimeZone('Europe/Rome')),
$transformer->reverseTransform('day: 28 month: 05 year: 1978')
);
}
public function testReverseTransformEmpty() public function testReverseTransformEmpty()
{ {
$transformer = new DateTimeToLocalizedStringTransformer(); $transformer = new DateTimeToLocalizedStringTransformer();