From 737388053f3aa776ff64d3159f61fa9b9e4c3f31 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Wed, 15 Feb 2023 21:30:41 +0100 Subject: [PATCH] PHP 8.1: Fix FunctionExpression::Evaluate() "TO_DAYS" misalignment due to PHP 8.1 bug fix --- core/oql/expression.class.inc.php | 21 +++++++++++++++++-- .../core/ExpressionEvaluateTest.php | 15 +++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/core/oql/expression.class.inc.php b/core/oql/expression.class.inc.php index 84cb6ac72..099ecdb18 100644 --- a/core/oql/expression.class.inc.php +++ b/core/oql/expression.class.inc.php @@ -2860,9 +2860,26 @@ class FunctionExpression extends Expression { throw new \Exception("Function {$this->m_sVerb} requires 1 argument"); } + + // N°5985 - Since PHP 8.1+, a bug fix on \DateTimeInterval for a date anterior to "1937-05-23" now returns a different number of days. The workaround below aim at making the code work with PHP 7.4 => 8.2+ + // + // $oDate = new DateTimeImmutable('2020-01-02'); + // $oZero = new DateTimeImmutable('1937-05-22'); + // $iRet = (int) $oDate->diff($oZero)->format('%a'); + // echo $iRet."\n"; // 30174 (PHP 8.0) vs 30175 (PHP 8.1+) + // + // $oDate = new DateTimeImmutable('2020-01-02'); + // $oZero = new DateTimeImmutable('1937-05-23'); + // $iRet = (int) $oDate->diff($oZero)->format('%a'); + // echo $iRet."\n"; // 30174 (PHP 8.0) vs 30174 (PHP 8.1+) + // + // To work around that we take 1970-01-01 as "zero date" and we offset it with the number of days between 1582-01-01 and 1970-01-01. + // Note that as the "target" date could be between 1582-01-01 and 1970-01-01 we have to format the interval with the "-"/"+" sign in order to correct the number of days. + $oDate = new DateTime($this->m_aArgs[0]->Evaluate($aArgs)); - $oZero = new DateTime('1582-01-01'); - $iRet = (int) $oDate->diff($oZero)->format('%a') + 577815; + $oZero = new DateTime('1970-01-01'); + $iDaysBetween19700101And15800101 = 141713; + $iRet = (int) $oZero->diff($oDate)->format('%R%a') + 577815 + $iDaysBetween19700101And15800101; return $iRet; case 'FROM_DAYS': diff --git a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php index 8df6d632a..88e744277 100644 --- a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php +++ b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php @@ -104,17 +104,21 @@ class ExpressionEvaluateTest extends iTopDataTestCase // The bare minimum array('"blah"', 'blah'), array('"\\\\"', '\\'), + // Arithmetics array('2+2', 4), array('2+2-2', 2), array('2*(3+4)', 14), array('(2*3)+4', 10), array('2*3+4', 10), + // Strings array("CONCAT('hello', 'world')", 'helloworld'), + // Not yet parsed - array("CONCAT_WS(' ', 'hello', 'world')", 'hello world'), array("SUBSTR('abcdef', 2, 3)", 'bcd'), array("TRIM(' Sin dolor ')", 'Sin dolor'), + // Comparison operators array('1 = 1', 1), array('1 != 1', 0), @@ -141,6 +145,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase array('"2020-06-12 17:35:13" > "2020-06-12"', 1), array('"2020-06-12 17:35:13" < "2020-06-12"', 0), array('"2020-06-12 00:00:00" = "2020-06-12"', 0), + // Logical operators array('0 AND 0', 0), array('1 AND 0', 0), @@ -151,12 +156,14 @@ class ExpressionEvaluateTest extends iTopDataTestCase array('1 OR 0', 1), array('1 OR 1', 1), array('1 AND 0 OR 1', 1), + // Casting array('1 AND "blah"', 0), array('1 AND "1"', 1), array('1 AND "2"', 1), array('1 AND "0"', 0), array('1 AND "-1"', 1), + // Null array('NULL', null), array('1 AND NULL', null), @@ -165,12 +172,14 @@ class ExpressionEvaluateTest extends iTopDataTestCase array('COALESCE(321, 123)', 321), array('ISNULL(NULL)', 1), array('ISNULL(123)', 0), + // Date functions array("DATE('2020-03-12 13:18:30')", '2020-03-12'), array("DATE_FORMAT('2009-10-04 22:23:00', '%Y %m %d %H %i %s')", '2009 10 04 22 23 00'), array("DATE(NOW()) = CURRENT_DATE()", 1), // Could fail if executed around midnight! array("TO_DAYS('2020-01-02')", 737791), array("FROM_DAYS(737791)", '2020-01-02'), + array("FROM_DAYS(TO_DAYS('2020-01-02'))", '2020-01-02'), // Back and forth conversion to ensure it returns the same array("YEAR('2020-05-03')", 2020), array("MONTH('2020-05-03')", 5), array("DAY('2020-05-03')", 3), @@ -178,6 +187,7 @@ class ExpressionEvaluateTest extends iTopDataTestCase array("DATE_ADD('2020-02-28 18:00:00', INTERVAL 1 DAY)", '2020-02-29 18:00:00'), array("DATE_SUB('2020-03-01 18:00:00', INTERVAL 1 HOUR)", '2020-03-01 17:00:00'), array("DATE_SUB('2020-03-01 18:00:00', INTERVAL 1 DAY)", '2020-02-29 18:00:00'), + // Misc. functions array('IF(1, 123, 567)', 123), array('IF(0, 123, 567)', 567), @@ -187,6 +197,11 @@ class ExpressionEvaluateTest extends iTopDataTestCase array('INET_ATON("128.0.0.1")', 2147483649), array('INET_NTOA(2147483649)', '128.0.0.1'), ); + + // N°5985 - Test bidirectional conversion across the centuries to ensure that it works on PHP 7.4 => 8.2+ even though the bug has been fixed in PHP 8.1 but still exists in PHP 7.4 => 8.1 + for ($iUpperYearBound = 1600; $iUpperYearBound <= 2100; $iUpperYearBound = $iUpperYearBound + 25) { + $aExpressions[] = array("FROM_DAYS(TO_DAYS('$iUpperYearBound-01-02'))", "$iUpperYearBound-01-02"); + } } // Build a comprehensive index