[BUGFIX] Fix date conversion of neg timestamps 25/47025/6
authorDaniel Maier <dani-maier@gmx.de>
Thu, 3 Mar 2016 20:17:21 +0000 (21:17 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sat, 5 Mar 2016 14:10:27 +0000 (15:10 +0100)
Date conversion of TCA fields with eval "date" or "datetime" is now also
handled correctly for dates before 1970, thus having a negative
timestamp. Timezone offset is now also applied for those negative
timestamps, in order to prevent erroneous data for dates before 1970.

Furthermore validation handling for dates with zero timestamp
(01.01.1970 midnight UTC) is fixed.

Resolves: #73871
Releases: master, 7.6
Change-Id: Iffa2f12c6941fe17b956202fe9c49f811b1b0539
Reviewed-on: https://review.typo3.org/47025
Reviewed-by: Andreas Wolf <andreas.wolf@typo3.org>
Tested-by: Andreas Wolf <andreas.wolf@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
typo3/sysext/backend/Resources/Public/JavaScript/FormEngineValidation.js
typo3/sysext/backend/Tests/Unit/Form/Element/InputTextElementTest.php [new file with mode: 0644]
typo3/sysext/core/Classes/DataHandling/DataHandler.php
typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php

index 65a16c4..5a14579 100644 (file)
@@ -79,7 +79,7 @@ class InputTextElement extends AbstractFormElement
             } elseif (in_array('date', $evalList)) {
                 $attributes['data-date-type'] = 'date';
             }
-            if ($parameterArray['itemFormElValue'] > 0) {
+            if (MathUtility::canBeInterpretedAsInteger($parameterArray['itemFormElValue'])) {
                 $parameterArray['itemFormElValue'] += date('Z', $parameterArray['itemFormElValue']);
             }
             if (isset($config['range']['lower'])) {
index 0c6fc46..5871dad 100644 (file)
@@ -153,7 +153,7 @@ define(['jquery', 'TYPO3/CMS/Backend/FormEngine'], function ($, FormEngine) {
                switch (type) {
                        case 'date':
                                var parsedInt = parseInt(value);
-                               if (!parsedInt) {
+                               if (isNaN(parsedInt)) {
                                        return '';
                                }
                                theTime = new Date(parsedInt * 1000);
diff --git a/typo3/sysext/backend/Tests/Unit/Form/Element/InputTextElementTest.php b/typo3/sysext/backend/Tests/Unit/Form/Element/InputTextElementTest.php
new file mode 100644 (file)
index 0000000..18e7541
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+namespace typo3\sysext\backend\Tests\Unit\Form\Element;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Backend\Form\Element\InputTextElement;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Test case
+ */
+class InputTextElementTest extends UnitTestCase
+{
+    /**
+     * @var string Selected timezone backup
+     */
+    protected $timezoneBackup = '';
+
+    /**
+     * We're fiddling with hard timestamps in the tests, but time methods in
+     * the system under test do use timezone settings. Therefore we backup the
+     * current timezone setting, set it to UTC explicitly and reconstitute it
+     * again in tearDown()
+     */
+    protected function setUp()
+    {
+        $this->timezoneBackup = date_default_timezone_get();
+    }
+
+    /**
+     * Tear down
+     */
+    protected function tearDown()
+    {
+        date_default_timezone_set($this->timezoneBackup);
+        parent::tearDown();
+    }
+
+
+    /**
+     * Data provider for renderAppliesCorrectTimestampConversion
+     *
+     * @return array
+     */
+    public function renderAppliesCorrectTimestampConversionDataProvider()
+    {
+        // Three elements: input (UTC), timezone of output, expected output
+        return [
+            // German standard time (without DST) is one hour ahead of UTC
+            'date in 2016 in German timezone' => [
+                1457103519, 'Europe/Berlin', 1457103519 + 3600
+            ],
+            'date in 1969 in German timezone' => [
+                -7200, 'Europe/Berlin', -3600
+            ],
+            'begin of the UTC epoch in German timezone' => [
+                0, 'Europe/Berlin', +3600
+            ],
+            // Los Angeles is 8 hours behind UTC
+            'date in 2016 in Los Angeles timezone' => [
+                1457103519, 'America/Los_Angeles', 1457103519 - 28800
+            ],
+            'date in UTC' => [
+                1457103519, 'UTC', 1457103519
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider renderAppliesCorrectTimestampConversionDataProvider
+     * @param int $input
+     * @param string $serverTimezone
+     * @param int $expectedOutput
+     */
+    public function renderAppliesCorrectTimestampConversion($input, $serverTimezone, $expectedOutput)
+    {
+        date_default_timezone_set($serverTimezone);
+        $data = [
+                'parameterArray' => [
+                    'tableName' => 'table_foo',
+                    'fieldName' => 'field_bar',
+                    'fieldConf' => [
+                        'config' => [
+                            'type' => 'input',
+                            'dbType' => 'datetime',
+                            'eval' => 'datetime',
+                            'default' => '0000-00-00 00:00:00'
+                        ]
+                    ],
+                    'itemFormElValue' => $input
+            ]
+        ];
+        /** @var NodeFactory $nodeFactoryProphecy */
+        $nodeFactoryProphecy = $this->prophesize(NodeFactory::class)->reveal();
+        $subject = new InputTextElement($nodeFactoryProphecy, $data);
+        $result = $subject->render();
+        $this->assertContains('<input type="hidden" name="" value="' . $expectedOutput  . '" />', $result['html']);
+    }
+}
index 56b8ef0..e977bfd 100644 (file)
@@ -2662,9 +2662,10 @@ class DataHandler
                     break;
                 case 'date':
                 case 'datetime':
-                    $value = (int)$value;
-                    if ($value > 0 && !$this->dontProcessTransformations) {
+                    if (MathUtility::canBeInterpretedAsInteger($value) && !$this->dontProcessTransformations) {
                         $value -= date('Z', $value);
+                    } elseif (!MathUtility::canBeInterpretedAsInteger($value)) {
+                        $value = 0;
                     }
                     break;
                 case 'double2':
index 725ad8a..f96dc53 100644 (file)
@@ -167,6 +167,48 @@ class DataHandlerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         }
     }
 
+
+    public function dataProviderDatetime()
+    {
+        // Three elements: input, timezone of input, expected output (UTC)
+        return [
+            // German standard time (without DST) is one hour ahead of UTC
+            'date in 2016 in German timezone' => [
+                1457103519, 'Europe/Berlin', 1457103519 - 3600
+            ],
+            'date in 1969 in German timezone' => [
+                -7200, 'Europe/Berlin', -10800
+            ],
+            'begin of the epoch in German timezone' => [
+                0, 'Europe/Berlin', -3600
+            ],
+            // Los Angeles is 8 hours behind UTC
+            'date in 2016 in Los Angeles timezone' => [
+                1457103519, 'America/Los_Angeles', 1457103519 + 28800
+            ],
+            'date in UTC' => [
+                1457103519, 'UTC', 1457103519
+            ]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider dataProviderDatetime
+     */
+    public function evalCheckValueDatetime($input, $serverTimezone, $expectedOutput)
+    {
+        $oldTimezone = date_default_timezone_get();
+        date_default_timezone_set($serverTimezone);
+
+        $output = $this->subject->checkValue_input_Eval($input, ['datetime'], '');
+
+        // set before the assertion is performed, so it is restored even for failing tests
+        date_default_timezone_set($oldTimezone);
+
+        $this->assertEquals($expectedOutput, $output['value']);
+    }
+
     /**
      * Data provider for inputValueCheckRecognizesStringValuesAsIntegerValuesCorrectly
      *