diff --git a/README.md b/README.md index d34788a..e57240e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ Naming used here is the same as in ClickHouse docs. - [Setup](#setup) - [Logging](#logging) - - [Time Zones](#time-zones) - [PSR Factories who?](#psr-factories-who) - [Sync API](#sync-api) - [Select](#select) @@ -64,7 +63,6 @@ $clickHouseClient = new PsrClickHouseClient( ), new LoggerChain(), [], - new DateTimeZone('UTC') ); ``` @@ -87,25 +85,6 @@ framework: database: '%clickhouse.database%' ``` -### Time Zones - -ClickHouse does not have date times with timezones. -Therefore you need to normalize DateTimes' timezones passed as parameters to ensure proper input format. - -Following would be inserted as `2020-01-31 01:00:00` into ClickHouse. - -```php -new DateTimeImmutable('2020-01-31 01:00:00', new DateTimeZone('Europe/Prague')); -``` - -If your server uses `UTC`, the value is incorrect for you actually need to insert `2020-01-31 00:00:00`. - -Time zone normalization is enabled by passing `DateTimeZone` into `PsrClickHouseClient` constructor. - -```php -new PsrClickHouseClient(..., new DateTimeZone('UTC')); -``` - ### PSR Factories who? _The library does not implement it's own HTTP. @@ -218,7 +197,7 @@ This produces `SELECT 'value'` and it can be passed to `ClickHouseClient::select Supported types are: - scalars -- DateTimeImmutable (`\DateTime` is not supported because `ValueFormatter` might modify its timezone so it's not considered safe) +- DateTimeInterface - [Expression](#expression) - objects implementing `__toString()` diff --git a/src/Client/PsrClickHouseAsyncClient.php b/src/Client/PsrClickHouseAsyncClient.php index b4e3ea7..356c2bc 100644 --- a/src/Client/PsrClickHouseAsyncClient.php +++ b/src/Client/PsrClickHouseAsyncClient.php @@ -4,7 +4,6 @@ namespace SimPod\ClickHouseClient\Client; -use DateTimeZone; use Exception; use GuzzleHttp\Promise\Create; use GuzzleHttp\Promise\PromiseInterface; @@ -32,9 +31,8 @@ public function __construct( private RequestFactory $requestFactory, private SqlLogger|null $sqlLogger = null, private array $defaultSettings = [], - DateTimeZone|null $clickHouseTimeZone = null, ) { - $this->sqlFactory = new SqlFactory(new ValueFormatter($clickHouseTimeZone)); + $this->sqlFactory = new SqlFactory(new ValueFormatter()); } /** diff --git a/src/Client/PsrClickHouseClient.php b/src/Client/PsrClickHouseClient.php index 59125de..b5bb102 100644 --- a/src/Client/PsrClickHouseClient.php +++ b/src/Client/PsrClickHouseClient.php @@ -4,7 +4,6 @@ namespace SimPod\ClickHouseClient\Client; -use DateTimeZone; use InvalidArgumentException; use Psr\Http\Client\ClientExceptionInterface; use Psr\Http\Client\ClientInterface; @@ -49,9 +48,8 @@ public function __construct( private RequestFactory $requestFactory, private SqlLogger|null $sqlLogger = null, private array $defaultSettings = [], - DateTimeZone|null $clickHouseTimeZone = null, ) { - $this->valueFormatter = new ValueFormatter($clickHouseTimeZone); + $this->valueFormatter = new ValueFormatter(); $this->sqlFactory = new SqlFactory($this->valueFormatter); } diff --git a/src/Param/ParamValueConverterRegistry.php b/src/Param/ParamValueConverterRegistry.php index 70fdcd3..baa4a5e 100644 --- a/src/Param/ParamValueConverterRegistry.php +++ b/src/Param/ParamValueConverterRegistry.php @@ -95,8 +95,8 @@ public function __construct(array $registry = []) 'datetime' => self::dateTimeConverter(), 'datetime32' => self::dateTimeConverter(), 'datetime64' => static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface - ? $value->format('Y-m-d H:i:s.u') - : $value, + ? $value->format('U.u') + : $value, 'Dynamic' => self::noopConverter(), 'Variant' => self::noopConverter(), @@ -272,6 +272,7 @@ private static function decimalConverter(): Closure private static function dateConverter(): Closure { return static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface + // We cannot convert to timestamp yet https://github.com/ClickHouse/ClickHouse/issues/75217 ? $value->format('Y-m-d') : $value; } @@ -279,7 +280,7 @@ private static function dateConverter(): Closure private static function dateTimeConverter(): Closure { return static fn (DateTimeInterface|string|int|float $value) => $value instanceof DateTimeInterface - ? $value->format('Y-m-d H:i:s') + ? $value->getTimestamp() : $value; } diff --git a/src/Sql/ValueFormatter.php b/src/Sql/ValueFormatter.php index b68caaf..3afa4ad 100644 --- a/src/Sql/ValueFormatter.php +++ b/src/Sql/ValueFormatter.php @@ -5,8 +5,7 @@ namespace SimPod\ClickHouseClient\Sql; use BackedEnum; -use DateTimeImmutable; -use DateTimeZone; +use DateTimeInterface; use SimPod\ClickHouseClient\Exception\UnsupportedParamValue; use function array_key_first; @@ -26,10 +25,6 @@ /** @internal */ final readonly class ValueFormatter { - public function __construct(private DateTimeZone|null $dateTimeZone = null) - { - } - /** @throws UnsupportedParamValue */ public function format(mixed $value, string|null $paramName = null, string|null $sql = null): string { @@ -66,12 +61,8 @@ public function format(mixed $value, string|null $paramName = null, string|null : (string) $value->value; } - if ($value instanceof DateTimeImmutable) { - if ($this->dateTimeZone !== null) { - $value = $value->setTimezone($this->dateTimeZone); - } - - return "'" . $value->format('Y-m-d H:i:s') . "'"; + if ($value instanceof DateTimeInterface) { + return (string) $value->getTimestamp(); } if ($value instanceof Expression) { diff --git a/tests/Client/Http/RequestFactoryTest.php b/tests/Client/Http/RequestFactoryTest.php index 4f987e8..b45eda8 100644 --- a/tests/Client/Http/RequestFactoryTest.php +++ b/tests/Client/Http/RequestFactoryTest.php @@ -78,11 +78,10 @@ public function testParamParsed(): void new Psr17Factory(), ); - $now = new DateTimeImmutable(); - $nowDate = $now->format('Y-m-d'); + $now = new DateTimeImmutable(); $request = $requestFactory->prepareSqlRequest( - 'SELECT {p1:String}, {p_2:Date}', + 'SELECT {p1:String}, {p_2:DateTime}', new RequestSettings( [], [], @@ -104,7 +103,7 @@ public function testParamParsed(): void 'Content-Disposition: form-data; name="param_p_2"', 'Content-Length: 10', '', - $nowDate, + $now->getTimestamp(), ], ), $body, diff --git a/tests/Param/ParamValueConverterRegistryTest.php b/tests/Param/ParamValueConverterRegistryTest.php index f02e28e..2c93aa2 100644 --- a/tests/Param/ParamValueConverterRegistryTest.php +++ b/tests/Param/ParamValueConverterRegistryTest.php @@ -182,6 +182,11 @@ public static function providerConvert(): Generator yield 'DateTime64(9)' => ['DateTime64(9)', 1675213323123456789, '2023-02-01 01:02:03.123456789']; yield 'DateTime64(9) (float)' => ['DateTime64(9)', 1675213323.1235, '2023-02-01 01:02:03.123500000']; yield 'DateTime64(9) (string)' => ['DateTime64(9)', '1675213323.123456789', '2023-02-01 01:02:03.123456789']; + yield 'DateTime64(9) (DateTime)' => [ + 'DateTime64(9)', + new DateTimeImmutable('2023-02-01 01:02:03.123456789'), + '2023-02-01 01:02:03.123456000', + ]; yield 'Bool' => ['Bool', true, 'true']; diff --git a/tests/Sql/ExpressionFactoryTest.php b/tests/Sql/ExpressionFactoryTest.php index a2f3851..9a98962 100644 --- a/tests/Sql/ExpressionFactoryTest.php +++ b/tests/Sql/ExpressionFactoryTest.php @@ -4,7 +4,6 @@ namespace SimPod\ClickHouseClient\Tests\Sql; -use DateTimeZone; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use SimPod\ClickHouseClient\Sql\ExpressionFactory; @@ -18,7 +17,7 @@ final class ExpressionFactoryTest extends TestCaseBase #[DataProvider('providerTemplateAndValues')] public function testTemplateAndValues(string $expectedExpressionString, string $template, array $values): void { - $expressionFactory = new ExpressionFactory(new ValueFormatter(new DateTimeZone('UTC'))); + $expressionFactory = new ExpressionFactory(new ValueFormatter()); self::assertSame( $expectedExpressionString, diff --git a/tests/Sql/ValueFormatterTest.php b/tests/Sql/ValueFormatterTest.php index d1c29bb..a3dbe14 100644 --- a/tests/Sql/ValueFormatterTest.php +++ b/tests/Sql/ValueFormatterTest.php @@ -28,7 +28,7 @@ public function testFormat( ): void { self::assertSame( $expectedValue, - (new ValueFormatter(new DateTimeZone('UTC')))->format($value, $paramName, $sql), + (new ValueFormatter())->format($value, $paramName, $sql), ); } @@ -68,9 +68,9 @@ public static function providerFormat(): iterable 'SELECT * FROM table WHERE (a,b) IN (:tuples)', ]; - yield 'DateTimeImmutable' => ["'2020-01-31 01:23:45'", new DateTimeImmutable('2020-01-31 01:23:45')]; + yield 'DateTimeImmutable' => ['1580433825', new DateTimeImmutable('2020-01-31 01:23:45')]; yield 'DateTimeImmutable different PHP and ClickHouse timezones' => [ - "'2020-01-31 01:23:45'", + '1580433825', new DateTimeImmutable('2020-01-31 02:23:45', new DateTimeZone('Europe/Prague')), ];