Skip to content

Commit e5b28a6

Browse files
authored
Merge pull request #65 from tarasom/convert_tz
feat: Add support for CONVERT_TZ() function
2 parents 68d846a + 9c21b41 commit e5b28a6

File tree

2 files changed

+86
-0
lines changed

2 files changed

+86
-0
lines changed

src/Processor/Expression/FunctionEvaluator.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ public static function evaluate(
9999
return self::sqlCeiling($conn, $scope, $expr, $row, $result);
100100
case 'FLOOR':
101101
return self::sqlFloor($conn, $scope, $expr, $row, $result);
102+
case 'CONVERT_TZ':
103+
return self::sqlConvertTz($conn, $scope, $expr, $row, $result);
102104
case 'TIMESTAMPDIFF':
103105
return self::sqlTimestampdiff($conn, $scope, $expr, $row, $result);
104106
case 'DATEDIFF':
@@ -1550,6 +1552,49 @@ private static function getPhpIntervalFromExpression(
15501552
}
15511553
}
15521554

1555+
/**
1556+
* @param FakePdoInterface $conn
1557+
* @param Scope $scope
1558+
* @param FunctionExpression $expr
1559+
* @param array<string, mixed> $row
1560+
* @param QueryResult $result
1561+
*
1562+
* @return string|null
1563+
* @throws ProcessorException
1564+
*/
1565+
private static function sqlConvertTz(
1566+
FakePdoInterface $conn,
1567+
Scope $scope,
1568+
FunctionExpression $expr,
1569+
array $row,
1570+
QueryResult $result)
1571+
{
1572+
$args = $expr->args;
1573+
1574+
if (count($args) !== 3) {
1575+
throw new \InvalidArgumentException("CONVERT_TZ() requires exactly 3 arguments");
1576+
}
1577+
1578+
/** @var string|null $dtValue */
1579+
$dtValue = Evaluator::evaluate($conn, $scope, $args[0], $row, $result);
1580+
/** @var string|null $fromTzValue */
1581+
$fromTzValue = Evaluator::evaluate($conn, $scope, $args[1], $row, $result);
1582+
/** @var string|null $toTzValue */
1583+
$toTzValue = Evaluator::evaluate($conn, $scope, $args[2], $row, $result);
1584+
1585+
if ($dtValue === null || $fromTzValue === null || $toTzValue === null) {
1586+
return null;
1587+
}
1588+
1589+
try {
1590+
$dt = new \DateTime($dtValue, new \DateTimeZone($fromTzValue));
1591+
$dt->setTimezone(new \DateTimeZone($toTzValue));
1592+
return $dt->format('Y-m-d H:i:s');
1593+
} catch (\Exception $e) {
1594+
return null;
1595+
}
1596+
}
1597+
15531598
/**
15541599
* @param FakePdoInterface $conn
15551600
* @param Scope $scope

tests/FunctionEvaluatorTest.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,47 @@ private static function getPdo(string $connection_string, bool $strict_mode = fa
103103
return new \Vimeo\MysqlEngine\Php7\FakePdo($connection_string, '', '', $options);
104104
}
105105

106+
/**
107+
* @dataProvider convertTzProvider
108+
*/
109+
public function testConvertTz(string $sql, ?string $expected)
110+
{
111+
$query = self::getConnectionToFullDB()->prepare($sql);
112+
$query->execute();
113+
114+
$this->assertSame($expected, $query->fetch(\PDO::FETCH_COLUMN));
115+
}
116+
117+
private static function convertTzProvider(): array
118+
{
119+
return [
120+
'normal conversion' => [
121+
'sql' => "SELECT CONVERT_TZ('2025-09-23 02:30:00', 'UTC', 'Europe/Kyiv');",
122+
'expected' => "2025-09-23 05:30:00",
123+
],
124+
'same tz' => [
125+
'sql' => "SELECT CONVERT_TZ('2025-12-31 23:59:59', 'Europe/Kyiv', 'Europe/Kyiv');",
126+
'expected' => "2025-12-31 23:59:59",
127+
],
128+
'crossing DST' => [
129+
'sql' => "SELECT CONVERT_TZ('2025-07-01 12:00:00', 'America/New_York', 'UTC');",
130+
'expected' => "2025-07-01 16:00:00",
131+
],
132+
'null date' => [
133+
'sql' => "SELECT CONVERT_TZ(NULL, 'UTC', 'Europe/Kyiv');",
134+
'expected' => null,
135+
],
136+
'invalid timezone' => [
137+
'sql' => "SELECT CONVERT_TZ('2025-09-23 02:30:00', 'Invalid/Zone', 'UTC');",
138+
'expected' => null,
139+
],
140+
'invalid date' => [
141+
'sql' => "SELECT CONVERT_TZ('not-a-date', 'UTC', 'UTC');",
142+
'expected' => null,
143+
]
144+
];
145+
}
146+
106147
private static function getConnectionToFullDB(bool $emulate_prepares = true, bool $strict_mode = false) : \PDO
107148
{
108149
$pdo = self::getPdo('mysql:foo;dbname=test;', $strict_mode);

0 commit comments

Comments
 (0)