Procházet zdrojové kódy

[Form] Improved test coverage of MonthChoiceList and fixed timezone used in it. Fixes https://github.com/symfony/symfony/pull/433

Based on dfb93b1bcebf1f34d3a880d00f36acb2bcca0f08:

[FORM] Fixed DateField Month Choices

The month choices were calculated using the current day of the month with
gmmktime rather than the 1st of the month. Additionally, this provides a
UTC timestamp which is passed to the formatter (IntlDateFormatter) which
converts the timestamp using the current timezone. This means that the UTC
timestamp for 1st March was being converted for my timezone (EST) and giving
a date of 28th February, leading to Feb appearing again in the popup form
instead of Mar.
Bernhard Schussek před 14 roky
rodič
revize
c9c8b5c98e

+ 7 - 1
src/Symfony/Component/Form/ChoiceList/MonthChoiceList.php

@@ -34,17 +34,23 @@ class MonthChoiceList extends PaddedChoiceList
         parent::load();
 
         $pattern = $this->formatter->getPattern();
+        $timezone = $this->formatter->getTimezoneId();
+
+        $this->formatter->setTimezoneId(\DateTimeZone::UTC);
 
         if (preg_match('/M+/', $pattern, $matches)) {
             $this->formatter->setPattern($matches[0]);
 
             foreach ($this->choices as $choice => $value) {
-                $this->choices[$choice] = $this->formatter->format(gmmktime(0, 0, 0, $choice));
+                // It's important to specify the first day of the month here!
+                $this->choices[$choice] = $this->formatter->format(gmmktime(0, 0, 0, $choice, 1));
             }
 
             // I'd like to clone the formatter above, but then we get a
             // segmentation fault, so let's restore the old state instead
             $this->formatter->setPattern($pattern);
         }
+
+        $this->formatter->setTimezoneId($timezone);
     }
 }

+ 4 - 1
src/Symfony/Component/Form/ChoiceList/PaddedChoiceList.php

@@ -43,7 +43,10 @@ class PaddedChoiceList extends ArrayChoiceList
     {
         parent::load();
 
-        foreach ($this->choices as $value) {
+        $choices = $this->choices;
+        $this->choices = array();
+
+        foreach ($choices as $value) {
             $this->choices[$value] = str_pad($value, $this->padLength, $this->padString, $this->padType);
         }
     }

+ 2 - 1
src/Symfony/Component/Form/Type/DateType.php

@@ -29,7 +29,8 @@ class DateType extends AbstractType
         $formatter = new \IntlDateFormatter(
             \Locale::getDefault(),
             $options['format'],
-            \IntlDateFormatter::NONE
+            \IntlDateFormatter::NONE,
+            \DateTimeZone::UTC
         );
 
         if ($options['widget'] === 'text') {

+ 90 - 0
tests/Symfony/Tests/Component/Form/ChoiceList/MonthChoiceListTest.php

@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Tests\Component\Form\ChoiceList;
+
+use Symfony\Component\Form\ChoiceList\MonthChoiceList;
+
+class MonthChoiceListTest extends \PHPUnit_Framework_TestCase
+{
+    private $formatter;
+
+    protected function setUp()
+    {
+        if (!extension_loaded('intl')) {
+            $this->markTestSkipped('The "intl" extension is not available');
+        }
+
+        \Locale::setDefault('en');
+
+        // I would prefer to mock the formatter, but this leads to weird bugs
+        // with the current version of PHPUnit
+        $this->formatter = new \IntlDateFormatter(
+            \Locale::getDefault(),
+            \IntlDateFormatter::SHORT,
+            \IntlDateFormatter::NONE,
+            \DateTimeZone::UTC
+        );
+    }
+
+    public function testNumericMonthsIfPatternContainsNoMonth()
+    {
+        $this->formatter->setPattern('yy');
+
+        $months = array(1, 4);
+        $list = new MonthChoiceList($this->formatter, $months);
+
+        $names = array(1 => '01', 4 => '04');
+        $this->assertSame($names, $list->getChoices());
+    }
+
+    public function testFormattedMonthsShort()
+    {
+        $this->formatter->setPattern('dd.MMM.yy');
+
+        $months = array(1, 4);
+        $list = new MonthChoiceList($this->formatter, $months);
+
+        $names = array(1 => 'Jan', 4 => 'Apr');
+        $this->assertSame($names, $list->getChoices());
+    }
+
+    public function testFormattedMonthsLong()
+    {
+        $this->formatter->setPattern('dd.MMMM.yy');
+
+        $months = array(1, 4);
+        $list = new MonthChoiceList($this->formatter, $months);
+
+        $names = array(1 => 'January', 4 => 'April');
+        $this->assertSame($names, $list->getChoices());
+    }
+
+    public function testFormattedMonthsLongWithDifferentTimezone()
+    {
+        $this->formatter = new \IntlDateFormatter(
+            \Locale::getDefault(),
+            \IntlDateFormatter::SHORT,
+            \IntlDateFormatter::NONE,
+            'PST'
+        );
+
+        $this->formatter->setPattern('dd.MMMM.yy');
+
+        $months = array(1, 4);
+        $list = new MonthChoiceList($this->formatter, $months);
+
+        $names = array(1 => 'January', 4 => 'April');
+        // uses UTC internally
+        $this->assertSame($names, $list->getChoices());
+        $this->assertSame('PST', $this->formatter->getTimezoneId());
+    }
+}