From 7090d9b67735ba10ca3d2e024cb376b991e26366 Mon Sep 17 00:00:00 2001
From: Daniel Maier <dani-maier@gmx.de>
Date: Sat, 5 Mar 2016 19:57:21 +0100
Subject: [PATCH] [BUGFIX] Fix date conversion of neg timestamps

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.

Resolves: #73871
Releases: master, 7.6
Change-Id: I4de9911dec3de720992da137fe8afcb3ecbfdad7
Reviewed-on: https://review.typo3.org/47115
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Andreas Wolf <andreas.wolf@typo3.org>
Tested-by: Andreas Wolf <andreas.wolf@typo3.org>
---
 .../Classes/Form/Element/InputTextElement.php |   2 +-
 .../Form/Element/InputTextElementTest.php     | 109 ++++++++++++++++++
 .../core/Classes/DataHandling/DataHandler.php |   2 +-
 .../Unit/DataHandling/DataHandlerTest.php     |  38 ++++++
 4 files changed, 149 insertions(+), 2 deletions(-)
 create mode 100644 typo3/sysext/backend/Tests/Unit/Form/Element/InputTextElementTest.php

diff --git a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
index 65a16c4c115e..3744941141e0 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
@@ -79,7 +79,7 @@ class InputTextElement extends AbstractFormElement
             } elseif (in_array('date', $evalList)) {
                 $attributes['data-date-type'] = 'date';
             }
-            if ($parameterArray['itemFormElValue'] > 0) {
+            if (((int)$parameterArray['itemFormElValue']) !== 0) {
                 $parameterArray['itemFormElValue'] += date('Z', $parameterArray['itemFormElValue']);
             }
             if (isset($config['range']['lower'])) {
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
index 000000000000..f144095c1947
--- /dev/null
+++ b/typo3/sysext/backend/Tests/Unit/Form/Element/InputTextElementTest.php
@@ -0,0 +1,109 @@
+<?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\Tests\UnitTestCase;
+
+/**
+ * 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
+            ],
+            // 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']);
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index f6f1b0fefd18..3ec0c8ad4973 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -2663,7 +2663,7 @@ class DataHandler
                 case 'date':
                 case 'datetime':
                     $value = (int)$value;
-                    if ($value > 0 && !$this->dontProcessTransformations) {
+                    if ($value !== 0 && !$this->dontProcessTransformations) {
                         $value -= date('Z', $value);
                     }
                     break;
diff --git a/typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php b/typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php
index 725ad8a8c0a7..b7ce5ca2483b 100644
--- a/typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php
+++ b/typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php
@@ -167,6 +167,44 @@ 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
+            ],
+            // 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
      *
-- 
GitLab