Skip to content

Commit 1a7930c

Browse files
committed
Fix handling timestamp with time zone in Oracle
1 parent 47589fd commit 1a7930c

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

src/Driver/OCI8/Middleware/InitializeSession.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function connect(
2828
. " NLS_TIME_FORMAT = 'HH24:MI:SS'"
2929
. " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
3030
. " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
31-
. " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'"
31+
. " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSTZH:TZM'"
3232
. " NLS_NUMERIC_CHARACTERS = '.,'",
3333
);
3434

src/Platforms/OraclePlatform.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ public function getNowExpression($type = 'timestamp')
8282
);
8383

8484
switch ($type) {
85+
case 'timestamptz':
86+
return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SSTZH:TZM\')';
8587
case 'date':
8688
case 'time':
8789
case 'timestamp':
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Tests\Functional\Types;
6+
7+
use DateTimeImmutable;
8+
use DateTimeZone;
9+
use Doctrine\DBAL\Schema\Table;
10+
use Doctrine\DBAL\Tests\FunctionalTestCase;
11+
use Doctrine\DBAL\Tests\TestUtil;
12+
use Doctrine\DBAL\Types\Type;
13+
use Doctrine\DBAL\Types\Types;
14+
15+
use function sprintf;
16+
17+
class DateTimeTzImmutableTypeTest extends FunctionalTestCase
18+
{
19+
private const TEST_TABLE = 'datetimetz_test';
20+
21+
protected function setUp(): void
22+
{
23+
if (! TestUtil::isDriverOneOf('pdo_oci', 'oci8')) {
24+
self::markTestSkipped('This test is only allowed for "pdo_oci" and "oci8" by now.');
25+
}
26+
27+
$table = new Table(self::TEST_TABLE);
28+
$table->addColumn('id', Types::INTEGER);
29+
30+
$table->addColumn('val', Types::DATETIMETZ_IMMUTABLE);
31+
$table->setPrimaryKey(['id']);
32+
33+
$this->dropAndCreateTable($table);
34+
}
35+
36+
public function testInsertAndSelect(): void
37+
{
38+
$platform = $this->connection->getDatabasePlatform();
39+
$dateTimeTzImmutableType = Type::getType(Types::DATETIMETZ_IMMUTABLE);
40+
41+
$id1 = 1;
42+
$value1 = new DateTimeImmutable('1986-03-22 19:45:30', new DateTimeZone('America/Argentina/Buenos_Aires'));
43+
44+
$this->insert($id1, $value1);
45+
46+
$res1 = $this->select($id1);
47+
48+
$resultDateTimeTzValue = $dateTimeTzImmutableType
49+
->convertToPHPValue($res1, $platform)
50+
->setTimezone(new DateTimeZone('UTC'));
51+
52+
self::assertInstanceOf(DateTimeImmutable::class, $resultDateTimeTzValue);
53+
self::assertSame($value1->getTimestamp(), $resultDateTimeTzValue->getTimestamp());
54+
self::assertSame($value1->getTimestamp(), $resultDateTimeTzValue->getTimestamp());
55+
self::assertSame('UTC', $resultDateTimeTzValue->format('T'));
56+
self::assertSame('1986-03-22T22:45:30+00:00', $resultDateTimeTzValue->format(DateTimeImmutable::ATOM));
57+
}
58+
59+
private function insert(int $id, DateTimeImmutable $value): void
60+
{
61+
$result = $this->connection->insert(self::TEST_TABLE, [
62+
'id' => $id,
63+
'val' => $value,
64+
], [
65+
Types::INTEGER,
66+
Type::getType(Types::DATETIMETZ_IMMUTABLE),
67+
]);
68+
69+
self::assertSame(1, $result);
70+
}
71+
72+
private function select(int $id): string
73+
{
74+
$value = $this->connection->fetchOne(
75+
sprintf('SELECT val FROM %s WHERE id = ?', self::TEST_TABLE),
76+
[$id],
77+
[Types::INTEGER],
78+
);
79+
80+
self::assertIsString($value);
81+
82+
return $value;
83+
}
84+
}

tests/Platforms/OraclePlatformTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,23 @@ public function asciiStringSqlDeclarationDataProvider(): array
926926
];
927927
}
928928

929+
/** @psalm-return iterable<int, array{string, string}> */
930+
public function getNowExpressionCases(): iterable
931+
{
932+
yield ['TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SSTZH:TZM\')', 'timestamptz'];
933+
yield ['TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')', 'date'];
934+
yield ['TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')', 'time'];
935+
yield ['TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')', 'timestamp'];
936+
yield ['TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')', 'unknown_unsupported_type'];
937+
}
938+
939+
/** @dataProvider getNowExpressionCases */
940+
public function testGetNowExpression(string $expected, string $type): void
941+
{
942+
/** @psalm-suppress DeprecatedMethod */
943+
self::assertSame($expected, $this->platform->getNowExpression($type));
944+
}
945+
929946
protected function getLimitOffsetCastToIntExpectedQuery(): string
930947
{
931948
return 'SELECT * FROM (SELECT a.*, ROWNUM AS doctrine_rownum FROM (SELECT * FROM user) a WHERE ROWNUM <= 3)'

0 commit comments

Comments
 (0)