diff --git a/CHANGELOG.md b/CHANGELOG.md index 116e3c53e..7087c97da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ - Enh #879: Rename `getLastInsertID()` method in `ConnectionInterface` to `getLastInsertId()` (@vjik) - New #967: Add `FOR` clause to query (@vjik) - Chg #972: Change in query "distinct" flag type from `bool|null` to `bool` (@vjik) +- New #968: Add `DateTimeColumn` column class (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index 4088d5cbe..0f9b73f14 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -108,8 +108,9 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - `IntegerColumn` for columns with integer type (tinyint, smallint, integer, bigint); - `BigIntColumn` for columns with integer type with range outside `PHP_INT_MIN` and `PHP_INT_MAX`; - `DoubleColumn` for columns with fractional number type (float, double, decimal, money); -- `StringColumn` for columns with string or datetime type (char, string, text, datetime, timestamp, date, time); +- `StringColumn` for columns with string type (char, string, text); - `BinaryColumn` for columns with binary type; +- `DateTimeColumn` for columns with date and time type (timestamp, datetime, time, date); - `ArrayColumn` for columns with array type; - `StructuredColumn` for columns with structured type (composite type in PostgreSQL); - `JsonColumn` for columns with json type. diff --git a/docs/guide/en/connection/oracle.md b/docs/guide/en/connection/oracle.md index d5bcea1cb..8d13f682e 100644 --- a/docs/guide/en/connection/oracle.md +++ b/docs/guide/en/connection/oracle.md @@ -65,3 +65,19 @@ $pdoDriver = new Driver($dsn, 'user', 'password'); // Connection. $db = new Connection($pdoDriver, $schemaCache); ``` + +## Date and Time Formats + +After opening a connection, the Oracle driver will set the date and time formats to ISO 8601. +This is required for the correct conversion of date and time values retrieved from the database. + +The following SQL statement is executed: + +```SQL +ALTER SESSION SET + NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFF' + NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SSXFFTZH:TZM' + NLS_TIME_FORMAT = 'HH24:MI:SSXFF' + NLS_TIME_TZ_FORMAT = 'HH24:MI:SSXFFTZH:TZM' + NLS_DATE_FORMAT = 'YYYY-MM-DD' +``` diff --git a/docs/guide/en/schema/typecasting.md b/docs/guide/en/schema/typecasting.md index 242be663c..253939cd2 100644 --- a/docs/guide/en/schema/typecasting.md +++ b/docs/guide/en/schema/typecasting.md @@ -119,6 +119,36 @@ $isActive = $row['is_active']; In the examples above, the value of `is_active` is casted to the correct `boolean` type before it is returned. +## DateTime, Date and Time types + +Some databases support date and time types, such as `timestamp`, `datetime`, `date`, `time`, etc. +These types are casted to `DateTimeImmutable` objects using `DateTimeColumn` class when type casting is enabled. + +```php +$query = (new Query($db))->from('customer')->where(['id' => 1]); + +$row = $query->withTypecasting()->one(); +$createdAt = $row['created_at']; // `DateTimeImmutable` object +``` + +```php +$command = $db->createCommand('SELECT * FROM {{customer}} WHERE id = 1'); + +$row = $command->withTypecasting()->queryOne(); +$createdAt = $row['created_at']; // `DateTimeImmutable` object +``` + +In the examples above, the value of `created_at` column is casted to the `DateTimeImmutable` type before it is returned. + +```php +$db->createCommand()->insert('customer', [ + 'name' => 'John Doe', + 'updated_at' => new DateTimeImmutable(), +])->execute(); +``` + +In the example above, the value of `updated_at` is casted to the correct database type before it is saved. + ## Custom type casting To implement custom type casting you need to extend the `AbstractColumn` class and override the `dbTypecast()` diff --git a/src/Connection/ServerInfoInterface.php b/src/Connection/ServerInfoInterface.php index d24c54a1b..667b25552 100644 --- a/src/Connection/ServerInfoInterface.php +++ b/src/Connection/ServerInfoInterface.php @@ -9,6 +9,14 @@ */ interface ServerInfoInterface { + /** + * Returns the server's session timezone. + * + * @param bool $refresh Whether to reload the server's session timezone. If `false`, the timezone fetched before + * will be returned if available. + */ + public function getTimezone(bool $refresh = false): string; + /** * Returns a server version as a string comparable by {@see version_compare()}. */ diff --git a/src/Constant/ColumnType.php b/src/Constant/ColumnType.php index e7b9578a8..9d694dad3 100644 --- a/src/Constant/ColumnType.php +++ b/src/Constant/ColumnType.php @@ -69,22 +69,30 @@ final class ColumnType * Define the abstract column type as `uuid`. */ public const UUID = 'uuid'; - /** - * Define the abstract column type as `datetime`. - */ - public const DATETIME = 'datetime'; /** * Define the abstract column type as `timestamp`. */ public const TIMESTAMP = 'timestamp'; /** - * Define the abstract column type as `date`. + * Define the abstract column type as `datetime`. */ - public const DATE = 'date'; + public const DATETIME = 'datetime'; + /** + * Define the abstract column type as `datetimetz`. + */ + public const DATETIMETZ = 'datetimetz'; /** * Define the abstract column type as `time`. */ public const TIME = 'time'; + /** + * Define the abstract column type as `timetz`. + */ + public const TIMETZ = 'timetz'; + /** + * Define the abstract column type as `date`. + */ + public const DATE = 'date'; /** * Define the abstract column type as `array`. */ diff --git a/src/Driver/Pdo/PdoServerInfo.php b/src/Driver/Pdo/PdoServerInfo.php index b51c4a1c7..3be72d4ac 100644 --- a/src/Driver/Pdo/PdoServerInfo.php +++ b/src/Driver/Pdo/PdoServerInfo.php @@ -6,6 +6,7 @@ use PDO; use Yiisoft\Db\Connection\ServerInfoInterface; +use Yiisoft\Db\Exception\NotSupportedException; class PdoServerInfo implements ServerInfoInterface { @@ -15,6 +16,11 @@ public function __construct(protected PdoConnectionInterface $db) { } + public function getTimezone(bool $refresh = false): string + { + throw new NotSupportedException(__METHOD__ . ' is not supported by this DBMS.'); + } + public function getVersion(): string { if ($this->version === null) { diff --git a/src/Schema/Column/AbstractColumnFactory.php b/src/Schema/Column/AbstractColumnFactory.php index 7dc5102b9..a11fc056f 100644 --- a/src/Schema/Column/AbstractColumnFactory.php +++ b/src/Schema/Column/AbstractColumnFactory.php @@ -208,6 +208,12 @@ protected function getColumnClass(string $type, array $info = []): string ColumnType::FLOAT => DoubleColumn::class, ColumnType::DOUBLE => DoubleColumn::class, ColumnType::BINARY => BinaryColumn::class, + ColumnType::TIMESTAMP => DateTimeColumn::class, + ColumnType::DATETIME => DateTimeColumn::class, + ColumnType::DATETIMETZ => DateTimeColumn::class, + ColumnType::TIME => DateTimeColumn::class, + ColumnType::TIMETZ => DateTimeColumn::class, + ColumnType::DATE => DateTimeColumn::class, ColumnType::ARRAY => ArrayColumn::class, ColumnType::STRUCTURED => StructuredColumn::class, ColumnType::JSON => JsonColumn::class, @@ -284,10 +290,12 @@ protected function isType(string $type): bool ColumnType::TEXT, ColumnType::BINARY, ColumnType::UUID, - ColumnType::DATETIME, ColumnType::TIMESTAMP, - ColumnType::DATE, + ColumnType::DATETIME, + ColumnType::DATETIMETZ, ColumnType::TIME, + ColumnType::TIMETZ, + ColumnType::DATE, ColumnType::ARRAY, ColumnType::STRUCTURED, ColumnType::JSON => true, diff --git a/src/Schema/Column/ColumnBuilder.php b/src/Schema/Column/ColumnBuilder.php index 04c6cf973..905762c60 100644 --- a/src/Schema/Column/ColumnBuilder.php +++ b/src/Schema/Column/ColumnBuilder.php @@ -192,27 +192,27 @@ public static function uuid(): ColumnInterface } /** - * Builds a column with the abstract type `datetime`. + * Builds a column with the abstract type `timestamp`. */ - public static function datetime(int|null $size = 0): ColumnInterface + public static function timestamp(int|null $size = 0): ColumnInterface { - return new StringColumn(ColumnType::DATETIME, size: $size); + return new DateTimeColumn(ColumnType::TIMESTAMP, size: $size); } /** - * Builds a column with the abstract type `timestamp`. + * Builds a column with the abstract type `datetime`. */ - public static function timestamp(int|null $size = 0): ColumnInterface + public static function datetime(int|null $size = 0): ColumnInterface { - return new StringColumn(ColumnType::TIMESTAMP, size: $size); + return new DateTimeColumn(ColumnType::DATETIME, size: $size); } /** - * Builds a column with the abstract type `date`. + * Builds a column with the abstract type `datetimetz`. */ - public static function date(): ColumnInterface + public static function datetimeWithTimezone(int|null $size = 0): ColumnInterface { - return new StringColumn(ColumnType::DATE); + return new DateTimeColumn(ColumnType::DATETIMETZ, size: $size); } /** @@ -220,7 +220,23 @@ public static function date(): ColumnInterface */ public static function time(int|null $size = 0): ColumnInterface { - return new StringColumn(ColumnType::TIME, size: $size); + return new DateTimeColumn(ColumnType::TIME, size: $size); + } + + /** + * Builds a column with the abstract type `timetz`. + */ + public static function timeWithTimezone(int|null $size = 0): ColumnInterface + { + return new DateTimeColumn(ColumnType::TIMETZ, size: $size); + } + + /** + * Builds a column with the abstract type `date`. + */ + public static function date(): ColumnInterface + { + return new DateTimeColumn(ColumnType::DATE); } /** diff --git a/src/Schema/Column/ColumnFactoryInterface.php b/src/Schema/Column/ColumnFactoryInterface.php index 73d658067..5a46a1978 100644 --- a/src/Schema/Column/ColumnFactoryInterface.php +++ b/src/Schema/Column/ColumnFactoryInterface.php @@ -19,6 +19,7 @@ * columns?: array, * comment?: string|null, * computed?: bool, + * dbTimezone?: string, * dbType?: string|null, * defaultValue?: mixed, * defaultValueRaw?: string|null, diff --git a/src/Schema/Column/ColumnInterface.php b/src/Schema/Column/ColumnInterface.php index 96cea7c7e..4b8f0b07d 100644 --- a/src/Schema/Column/ColumnInterface.php +++ b/src/Schema/Column/ColumnInterface.php @@ -216,7 +216,6 @@ public function getPrecision(): int|null; * Returns the PHP type of the column. Used for generating properties of a related model class. * * @return string The PHP type of the column. - * @psalm-return PhpType::* * @psalm-mutation-free */ public function getPhpType(): string; diff --git a/src/Schema/Column/DateTimeColumn.php b/src/Schema/Column/DateTimeColumn.php new file mode 100644 index 000000000..6e4cd64a8 --- /dev/null +++ b/src/Schema/Column/DateTimeColumn.php @@ -0,0 +1,221 @@ + null, + GettypeResult::STRING => $this->dbTypecastString($value), + GettypeResult::INTEGER => $this->dbTypecastDateTime(DateTimeImmutable::createFromFormat('U', (string) $value)), + GettypeResult::DOUBLE => $this->dbTypecastDateTime(DateTimeImmutable::createFromFormat('U.u', (string) $value)), + GettypeResult::OBJECT => match (true) { + $value instanceof DateTimeImmutable => $this->dbTypecastDateTime($value), + $value instanceof DateTimeInterface => $this->dbTypecastDateTime(DateTimeImmutable::createFromInterface($value)), + $value instanceof ExpressionInterface => $value, + default => $this->dbTypecastString((string) $value), + }, + default => throw new InvalidArgumentException('Wrong ' . gettype($value) . ' value for ' . $this->getType() . ' column.'), + }; + } + + /** @psalm-mutation-free */ + public function getPhpType(): string + { + return DateTimeImmutable::class; + } + + /** + * Converts the value from the database format to PHP `DateTimeImmutable` object. + * If the database type does not have time zone information, the time zone will be set to the current PHP time zone. + * + * @param string|null $value + * @psalm-suppress MoreSpecificImplementedParamType + */ + public function phpTypecast(mixed $value): DateTimeImmutable|null + { + if (is_string($value)) { + $phpTimezone = $this->getPhpTimezone(); + + if ($this->shouldConvertTimezone()) { + /** @psalm-suppress ArgumentTypeCoercion */ + $datetime = new DateTimeImmutable($value, new DateTimeZone($this->dbTimezone)); + + if ($phpTimezone !== $this->dbTimezone) { + return $datetime->setTimezone(new DateTimeZone($phpTimezone)); + } + + return $datetime; + } + + return new DateTimeImmutable($value, new DateTimeZone($phpTimezone)); + } + + return $value; + } + + protected function shouldConvertTimezone(): bool + { + return $this->shouldConvertTimezone ??= $this->dbTimezone !== '' && match ($this->getType()) { + ColumnType::DATETIMETZ, + ColumnType::TIMETZ, + ColumnType::DATE => false, + default => true, + }; + } + + /** @psalm-return non-empty-string */ + protected function getFormat(): string + { + return $this->format ??= match ($this->getType()) { + ColumnType::TIMESTAMP, + ColumnType::DATETIME => 'Y-m-d H:i:s' . $this->getMillisecondsFormat(), + ColumnType::DATETIMETZ => 'Y-m-d H:i:s' . $this->getMillisecondsFormat() . 'P', + ColumnType::TIME => 'H:i:s' . $this->getMillisecondsFormat(), + ColumnType::TIMETZ => 'H:i:s' . $this->getMillisecondsFormat() . 'P', + ColumnType::DATE => 'Y-m-d', + ColumnType::INTEGER, + ColumnType::BIGINT => 'U', + ColumnType::FLOAT => 'U.u', + default => throw new UnexpectedValueException( + 'Unsupported abstract column type "' . $this->getType() . '" for ' . static::class . ' class.', + ), + }; + } + + protected function getMillisecondsFormat(): string + { + return match ($this->getSize()) { + 0 => '', + 1, 2, 3 => '.v', + default => '.u', + }; + } + + /** + * Returns the PHP time zone for the `string` datetime values when converting them to `DateTimeImmutable` objects + * before inserting them into the database or after retrieving them from the database. + * + * @psalm-return non-empty-string + */ + protected function getPhpTimezone(): string + { + return empty($this->phpTimezone) + ? date_default_timezone_get() + : $this->phpTimezone; + } + + private function dbTypecastDateTime(DateTimeImmutable $value): string + { + if ($this->shouldConvertTimezone()) { + /** @psalm-suppress ArgumentTypeCoercion */ + $value = $value->setTimezone(new DateTimeZone($this->dbTimezone)); + } + + return $value->format($this->getFormat()); + } + + private function dbTypecastString(string $value): string|null + { + /** @psalm-suppress PossiblyFalseArgument */ + return match ($value) { + '' => null, + (string)(int) $value => $this->dbTypecastDateTime(DateTimeImmutable::createFromFormat('U', $value)), + (string)(float) $value => $this->dbTypecastDateTime(DateTimeImmutable::createFromFormat('U.u', $value)), + default => ($datetime = date_create_immutable($value, new DateTimeZone($this->getPhpTimezone()))) !== false + ? $this->dbTypecastDateTime($datetime) + : $value, + }; + } +} diff --git a/tests/AbstractColumnTest.php b/tests/AbstractColumnTest.php index ad918d4cb..f85ab638c 100644 --- a/tests/AbstractColumnTest.php +++ b/tests/AbstractColumnTest.php @@ -23,6 +23,10 @@ public function testPredefinedType(string $className, string $type, string $phpT /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::dbTypecastColumns */ public function testDbTypecastColumns(ColumnInterface $column, array $values) { + // Set the timezone for testing purposes, could be any timezone except UTC + $oldDatetime = date_default_timezone_get(); + date_default_timezone_set('America/New_York'); + foreach ($values as [$expected, $value]) { if (is_object($expected) && !(is_object($value) && $expected::class === $value::class)) { $this->assertEquals($expected, $column->dbTypecast($value)); @@ -30,6 +34,8 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values) $this->assertSame($expected, $column->dbTypecast($value)); } } + + date_default_timezone_set($oldDatetime); } /** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::phpTypecastColumns */ diff --git a/tests/Common/CommonColumnTest.php b/tests/Common/CommonColumnTest.php new file mode 100644 index 000000000..a044878da --- /dev/null +++ b/tests/Common/CommonColumnTest.php @@ -0,0 +1,111 @@ +getSchema(); + $command = $db->createCommand(); + + if ($schema->hasTable(static::DATETIME_COLUMN_TABLE)) { + $command->dropTable(static::DATETIME_COLUMN_TABLE)->execute(); + } + + $command->createTable(static::DATETIME_COLUMN_TABLE, [ + 'timestamp' => static::COLUMN_BUILDER::timestamp()->defaultValue(new Expression('CURRENT_TIMESTAMP')), + 'datetime' => static::COLUMN_BUILDER::datetime()->defaultValue('2025-04-19 14:11:35'), + 'datetime3' => static::COLUMN_BUILDER::datetime(3)->defaultValue(new Stringable('2025-04-19 14:11:35.123')), + 'datetimetz' => static::COLUMN_BUILDER::datetimeWithTimezone()->defaultValue(new DateTime('2025-04-19 14:11:35 +02:00')), + 'datetimetz6' => static::COLUMN_BUILDER::datetimeWithTimezone(6)->defaultValue(new DateTimeImmutable('2025-04-19 14:11:35.123456 +02:00')), + 'time' => static::COLUMN_BUILDER::time()->defaultValue('14:11:35'), + 'time3' => static::COLUMN_BUILDER::time(3)->defaultValue(new Stringable('14:11:35.123')), + 'timetz' => static::COLUMN_BUILDER::timeWithTimezone()->defaultValue(new DateTime('14:11:35 +02:00')), + 'timetz6' => static::COLUMN_BUILDER::timeWithTimezone(6)->defaultValue(new DateTimeImmutable('14:11:35.123456 +02:00')), + 'date' => static::COLUMN_BUILDER::date()->defaultValue('2025-04-19'), + ])->execute(); + } + + public function testDateTimeColumnDefaults(): void + { + $db = $this->getConnection(); + + $this->createDateTimeColumnTable($db); + + $columns = $db->getTableSchema(static::DATETIME_COLUMN_TABLE)->getColumns(); + + $utcTimezone = new DateTimeZone('UTC'); + + $this->assertEquals( + new Expression(match ($db->getDriverName()) { + 'sqlsrv' => 'getdate()', + 'pgsql' => version_compare($db->getServerInfo()->getVersion(), '10', '<') + ? 'now()' + : 'CURRENT_TIMESTAMP', + default => 'CURRENT_TIMESTAMP', + }), + $columns['timestamp']->getDefaultValue(), + ); + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35', $utcTimezone), $columns['datetime']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35.123', $utcTimezone), $columns['datetime3']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35 +02:00', $utcTimezone), $columns['datetimetz']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('2025-04-19 14:11:35.123456 +02:00', $utcTimezone), $columns['datetimetz6']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('14:11:35', $utcTimezone), $columns['time']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('14:11:35.123', $utcTimezone), $columns['time3']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('14:11:35 +02:00', $utcTimezone), $columns['timetz']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('14:11:35.123456 +02:00', $utcTimezone), $columns['timetz6']->getDefaultValue()); + $this->assertEquals(new DateTimeImmutable('2025-04-19', $utcTimezone), $columns['date']->getDefaultValue()); + + $db->close(); + } + + #[DataProviderExternal(ColumnProvider::class, 'dateTimeColumn')] + public function testDateTimeColumn(float|int|string $value, array $expected): void + { + $db = $this->getConnection(); + $command = $db->createCommand(); + + $this->createDateTimeColumnTable($db); + + $values = array_fill_keys(array_keys($expected), $value); + + $expected = array_map(static fn (string $value) => new DateTimeImmutable($value, new DateTimeZone('UTC')), $expected); + + $command->insert(static::DATETIME_COLUMN_TABLE, $values)->execute(); + + $result = $command + ->setSql('SELECT * FROM [[' . static::DATETIME_COLUMN_TABLE . ']]') + ->withPhpTypecasting() + ->queryOne(); + + $this->assertEquals($expected, $result); + + $db->close(); + } +} diff --git a/tests/Db/Driver/Pdo/PdoServerInfoTest.php b/tests/Db/Driver/Pdo/PdoServerInfoTest.php index c37d03eed..2a7062527 100644 --- a/tests/Db/Driver/Pdo/PdoServerInfoTest.php +++ b/tests/Db/Driver/Pdo/PdoServerInfoTest.php @@ -5,17 +5,31 @@ namespace Yiisoft\Db\Tests\Db\Driver\Pdo; use PHPUnit\Framework\TestCase; +use Yiisoft\Db\Exception\NotSupportedException; use Yiisoft\Db\Tests\Support\TestTrait; class PdoServerInfoTest extends TestCase { use TestTrait; + public function testGetTimezone(): void + { + $db = $this->getConnection(); + $serverInfo = $db->getServerInfo(); + + $this->expectException(NotSupportedException::class); + $this->expectExceptionMessage('Yiisoft\Db\Driver\Pdo\PdoServerInfo::getTimezone is not supported by this DBMS.'); + + $serverInfo->getTimezone(); + } + public function testGetVersion(): void { $db = $this->getConnection(); $serverInfo = $db->getServerInfo(); $this->assertIsString($serverInfo->getVersion()); + + $db->close(); } } diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php index fd5054f0a..633a3a8c2 100644 --- a/tests/Provider/ColumnBuilderProvider.php +++ b/tests/Provider/ColumnBuilderProvider.php @@ -10,6 +10,7 @@ use Yiisoft\Db\Schema\Column\BitColumn; use Yiisoft\Db\Schema\Column\BooleanColumn; use Yiisoft\Db\Schema\Column\ColumnBuilder; +use Yiisoft\Db\Schema\Column\DateTimeColumn; use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonColumn; @@ -80,13 +81,17 @@ public static function buildingMethods(): array 'binary()' => ['binary', [], BinaryColumn::class, ColumnType::BINARY], 'binary(8)' => ['binary', [8], BinaryColumn::class, ColumnType::BINARY, ['getSize' => 8]], 'uuid()' => ['uuid', [], StringColumn::class, ColumnType::UUID], - 'datetime()' => ['datetime', [], StringColumn::class, ColumnType::DATETIME, ['getSize' => 0]], - 'datetime(3)' => ['datetime', [3], StringColumn::class, ColumnType::DATETIME, ['getSize' => 3]], - 'timestamp()' => ['timestamp', [], StringColumn::class, ColumnType::TIMESTAMP, ['getSize' => 0]], - 'timestamp(3)' => ['timestamp', [3], StringColumn::class, ColumnType::TIMESTAMP, ['getSize' => 3]], - 'date()' => ['date', [], StringColumn::class, ColumnType::DATE], - 'time()' => ['time', [], StringColumn::class, ColumnType::TIME, ['getSize' => 0]], - 'time(3)' => ['time', [3], StringColumn::class, ColumnType::TIME, ['getSize' => 3]], + 'timestamp()' => ['timestamp', [], DateTimeColumn::class, ColumnType::TIMESTAMP, ['getSize' => 0]], + 'timestamp(3)' => ['timestamp', [3], DateTimeColumn::class, ColumnType::TIMESTAMP, ['getSize' => 3]], + 'datetime()' => ['datetime', [], DateTimeColumn::class, ColumnType::DATETIME, ['getSize' => 0]], + 'datetime(3)' => ['datetime', [3], DateTimeColumn::class, ColumnType::DATETIME, ['getSize' => 3]], + 'datetimeWithTimezone()' => ['datetimeWithTimezone', [], DateTimeColumn::class, ColumnType::DATETIMETZ, ['getSize' => 0]], + 'datetimeWithTimezone(3)' => ['datetimeWithTimezone', [3], DateTimeColumn::class, ColumnType::DATETIMETZ, ['getSize' => 3]], + 'time()' => ['time', [], DateTimeColumn::class, ColumnType::TIME, ['getSize' => 0]], + 'time(3)' => ['time', [3], DateTimeColumn::class, ColumnType::TIME, ['getSize' => 3]], + 'timeWithTimezone()' => ['timeWithTimezone', [], DateTimeColumn::class, ColumnType::TIMETZ, ['getSize' => 0]], + 'timeWithTimezone(3)' => ['timeWithTimezone', [3], DateTimeColumn::class, ColumnType::TIMETZ, ['getSize' => 3]], + 'date()' => ['date', [], DateTimeColumn::class, ColumnType::DATE], 'array()' => ['array', [], ArrayColumn::class, ColumnType::ARRAY], 'array($column)' => ['array', [$column], ArrayColumn::class, ColumnType::ARRAY, ['getColumn' => $column]], 'structured()' => ['structured', [], StructuredColumn::class, ColumnType::STRUCTURED], diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index f428e8e92..96064f2df 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -11,6 +11,7 @@ use Yiisoft\Db\Schema\Column\BigIntColumn; use Yiisoft\Db\Schema\Column\BinaryColumn; use Yiisoft\Db\Schema\Column\BooleanColumn; +use Yiisoft\Db\Schema\Column\DateTimeColumn; use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonColumn; @@ -65,10 +66,12 @@ public static function types(): array 'double' => [ColumnType::DOUBLE, ColumnType::DOUBLE, DoubleColumn::class], 'decimal' => [ColumnType::DECIMAL, ColumnType::DECIMAL, DoubleColumn::class], 'money' => [ColumnType::MONEY, ColumnType::MONEY, StringColumn::class], - 'datetime' => [ColumnType::DATETIME, ColumnType::DATETIME, StringColumn::class], - 'timestamp' => [ColumnType::TIMESTAMP, ColumnType::TIMESTAMP, StringColumn::class], - 'time' => [ColumnType::TIME, ColumnType::TIME, StringColumn::class], - 'date' => [ColumnType::DATE, ColumnType::DATE, StringColumn::class], + 'timestamp' => [ColumnType::TIMESTAMP, ColumnType::TIMESTAMP, DateTimeColumn::class], + 'datetime' => [ColumnType::DATETIME, ColumnType::DATETIME, DateTimeColumn::class], + 'datetimetz' => [ColumnType::DATETIMETZ, ColumnType::DATETIMETZ, DateTimeColumn::class], + 'time' => [ColumnType::TIME, ColumnType::TIME, DateTimeColumn::class], + 'timetz' => [ColumnType::TIMETZ, ColumnType::TIMETZ, DateTimeColumn::class], + 'date' => [ColumnType::DATE, ColumnType::DATE, DateTimeColumn::class], 'array' => [ColumnType::ARRAY, ColumnType::ARRAY, ArrayColumn::class], 'structured' => [ColumnType::STRUCTURED, ColumnType::STRUCTURED, StructuredColumn::class], 'json' => [ColumnType::JSON, ColumnType::JSON, JsonColumn::class], diff --git a/tests/Provider/ColumnProvider.php b/tests/Provider/ColumnProvider.php index f8148018b..d6b8c4b3a 100644 --- a/tests/Provider/ColumnProvider.php +++ b/tests/Provider/ColumnProvider.php @@ -5,6 +5,9 @@ namespace Yiisoft\Db\Tests\Provider; use ArrayIterator; +use DateTime; +use DateTimeImmutable; +use DateTimeZone; use PDO; use stdClass; use Yiisoft\Db\Command\Param; @@ -21,6 +24,7 @@ use Yiisoft\Db\Schema\Column\BinaryColumn; use Yiisoft\Db\Schema\Column\BitColumn; use Yiisoft\Db\Schema\Column\BooleanColumn; +use Yiisoft\Db\Schema\Column\DateTimeColumn; use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonColumn; @@ -32,6 +36,7 @@ use Yiisoft\Db\Schema\Data\JsonLazyArray; use Yiisoft\Db\Schema\Data\StructuredLazyArray; use Yiisoft\Db\Tests\Support\IntEnum; +use Yiisoft\Db\Tests\Support\Stringable; use Yiisoft\Db\Tests\Support\StringEnum; use function fopen; @@ -49,6 +54,7 @@ public static function predefinedTypes(): array 'binary' => [BinaryColumn::class, ColumnType::BINARY, PhpType::MIXED], 'bit' => [BitColumn::class, ColumnType::BIT, PhpType::INT], 'boolean' => [BooleanColumn::class, ColumnType::BOOLEAN, PhpType::BOOL], + 'datetime' => [DateTimeColumn::class, ColumnType::DATETIME, DateTimeImmutable::class], 'array' => [ArrayColumn::class, ColumnType::ARRAY, PhpType::ARRAY], 'structured' => [StructuredColumn::class, ColumnType::STRUCTURED, PhpType::ARRAY], 'json' => [JsonColumn::class, ColumnType::JSON, PhpType::MIXED], @@ -157,6 +163,250 @@ public static function dbTypecastColumns(): array [$expression = new Expression('expression'), $expression], ], ], + 'timestamp' => [ + new DateTimeColumn(ColumnType::TIMESTAMP, size: 0), + [ + [null, null], + [null, ''], + ['2025-04-19 00:00:00', '2025-04-19'], + ['2025-04-19 14:11:35', '2025-04-19 14:11:35'], + ['2025-04-19 14:11:35', '2025-04-19 14:11:35.123456'], + ['2025-04-19 12:11:35', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19 12:11:35', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19 14:11:35', '1745071895'], + ['2025-04-19 14:11:35', '1745071895.123'], + ['2025-04-19 14:11:35', 1745071895], + ['2025-04-19 14:11:35', 1745071895.123], + ['2025-04-19 14:11:35', new DateTimeImmutable('2025-04-19 14:11:35')], + ['2025-04-19 14:11:35', new DateTime('2025-04-19 14:11:35')], + ['2025-04-19 14:11:35', new Stringable('2025-04-19 14:11:35')], + [$expression = new Expression("'2025-04-19 14:11:35'"), $expression], + ], + ], + 'timestamp6' => [ + new DateTimeColumn(ColumnType::TIMESTAMP, size: 6), + [ + [null, null], + [null, ''], + ['2025-04-19 00:00:00.000000', '2025-04-19'], + ['2025-04-19 14:11:35.000000', '2025-04-19 14:11:35'], + ['2025-04-19 14:11:35.123456', '2025-04-19 14:11:35.123456'], + ['2025-04-19 12:11:35.000000', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19 12:11:35.123456', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19 14:11:35.000000', '1745071895'], + ['2025-04-19 14:11:35.123000', '1745071895.123'], + ['2025-04-19 14:11:35.000000', 1745071895], + ['2025-04-19 14:11:35.123000', 1745071895.123], + ['2025-04-19 14:11:35.123456', new DateTimeImmutable('2025-04-19 14:11:35.123456')], + ['2025-04-19 14:11:35.123456', new DateTime('2025-04-19 14:11:35.123456')], + ['2025-04-19 14:11:35.123456', new Stringable('2025-04-19 14:11:35.123456')], + [$expression = new Expression("'2025-04-19 14:11:35.123456'"), $expression], + ], + ], + 'datetime' => [ + new DateTimeColumn(size: 0), + [ + [null, null], + [null, ''], + ['2025-04-19 00:00:00', '2025-04-19'], + ['2025-04-19 14:11:35', '2025-04-19 14:11:35'], + ['2025-04-19 14:11:35', '2025-04-19 14:11:35.123456'], + ['2025-04-19 12:11:35', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19 12:11:35', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19 14:11:35', '1745071895'], + ['2025-04-19 14:11:35', '1745071895.123'], + ['2025-04-19 14:11:35', 1745071895], + ['2025-04-19 14:11:35', 1745071895.123], + ['2025-04-19 14:11:35', new DateTimeImmutable('2025-04-19 14:11:35')], + ['2025-04-19 14:11:35', new DateTime('2025-04-19 14:11:35')], + ['2025-04-19 14:11:35', new Stringable('2025-04-19 14:11:35')], + [$expression = new Expression("'2025-04-19 14:11:35'"), $expression], + ], + ], + 'datetime6' => [ + new DateTimeColumn(size: 6), + [ + [null, null], + [null, ''], + ['2025-04-19 00:00:00.000000', '2025-04-19'], + ['2025-04-19 14:11:35.000000', '2025-04-19 14:11:35'], + ['2025-04-19 14:11:35.123456', '2025-04-19 14:11:35.123456'], + ['2025-04-19 12:11:35.000000', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19 12:11:35.123456', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19 14:11:35.000000', '1745071895'], + ['2025-04-19 14:11:35.123000', '1745071895.123'], + ['2025-04-19 14:11:35.000000', 1745071895], + ['2025-04-19 14:11:35.123000', 1745071895.123], + ['2025-04-19 14:11:35.123456', new DateTimeImmutable('2025-04-19 14:11:35.123456')], + ['2025-04-19 14:11:35.123456', new DateTime('2025-04-19 14:11:35.123456')], + ['2025-04-19 14:11:35.123456', new Stringable('2025-04-19 14:11:35.123456')], + [$expression = new Expression("'2025-04-19 14:11:35.123456'"), $expression], + ], + ], + 'datetimetz' => [ + new DateTimeColumn(ColumnType::DATETIMETZ, size: 0), + [ + [null, null], + [null, ''], + ['2025-04-19 00:00:00+00:00', '2025-04-19'], + ['2025-04-19 14:11:35+00:00', '2025-04-19 14:11:35'], + ['2025-04-19 14:11:35+00:00', '2025-04-19 14:11:35.123456'], + ['2025-04-19 14:11:35+02:00', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19 14:11:35+02:00', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19 14:11:35+00:00', '1745071895'], + ['2025-04-19 14:11:35+00:00', '1745071895.123'], + ['2025-04-19 14:11:35+00:00', 1745071895], + ['2025-04-19 14:11:35+00:00', 1745071895.123], + ['2025-04-19 14:11:35+02:00', new DateTimeImmutable('2025-04-19 14:11:35 +02:00')], + ['2025-04-19 14:11:35+02:00', new DateTime('2025-04-19 14:11:35 +02:00')], + ['2025-04-19 14:11:35+02:00', new Stringable('2025-04-19 14:11:35 +02:00')], + [$expression = new Expression("'2025-04-19 14:11:35 +02:00'"), $expression], + ], + ], + 'datetimetz6' => [ + new DateTimeColumn(ColumnType::DATETIMETZ, size: 6), + [ + [null, null], + [null, ''], + ['2025-04-19 00:00:00.000000+00:00', '2025-04-19'], + ['2025-04-19 14:11:35.000000+00:00', '2025-04-19 14:11:35'], + ['2025-04-19 14:11:35.123456+00:00', '2025-04-19 14:11:35.123456'], + ['2025-04-19 14:11:35.000000+02:00', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19 14:11:35.123456+02:00', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19 14:11:35.000000+00:00', '1745071895'], + ['2025-04-19 14:11:35.123000+00:00', '1745071895.123'], + ['2025-04-19 14:11:35.000000+00:00', 1745071895], + ['2025-04-19 14:11:35.123000+00:00', 1745071895.123], + ['2025-04-19 14:11:35.123456+02:00', new DateTimeImmutable('2025-04-19 14:11:35.123456 +02:00')], + ['2025-04-19 14:11:35.123456+02:00', new DateTime('2025-04-19 14:11:35.123456 +02:00')], + ['2025-04-19 14:11:35.123456+02:00', new Stringable('2025-04-19 14:11:35.123456 +02:00')], + [$expression = new Expression("'2025-04-19 14:11:35.123456 +02:00'"), $expression], + ], + ], + 'time' => [ + new DateTimeColumn(ColumnType::TIME, size: 0), + [ + [null, null], + [null, ''], + ['00:00:00', '2025-04-19'], + ['14:11:35', '14:11:35'], + ['14:11:35', '14:11:35.123456'], + ['12:11:35', '14:11:35 +02:00'], + ['12:11:35', '14:11:35.123456 +02:00'], + ['14:11:35', '2025-04-19 14:11:35'], + ['14:11:35', '2025-04-19 14:11:35.123456'], + ['12:11:35', '2025-04-19 14:11:35 +02:00'], + ['12:11:35', '2025-04-19 14:11:35.123456 +02:00'], + ['14:11:35', '1745071895'], + ['14:11:35', '1745071895.123'], + ['14:11:35', 1745071895], + ['14:11:35', 1745071895.123], + ['14:11:35', 51095], + ['14:11:35', 51095.123456], + ['14:11:35', new DateTimeImmutable('14:11:35')], + ['14:11:35', new DateTime('14:11:35')], + ['14:11:35', new Stringable('14:11:35')], + [$expression = new Expression("'14:11:35'"), $expression], + ], + ], + 'time6' => [ + new DateTimeColumn(ColumnType::TIME, size: 6), + [ + [null, null], + [null, ''], + ['00:00:00.000000', '2025-04-19'], + ['14:11:35.000000', '14:11:35'], + ['14:11:35.123456', '14:11:35.123456'], + ['12:11:35.000000', '14:11:35 +02:00'], + ['12:11:35.123456', '14:11:35.123456 +02:00'], + ['14:11:35.000000', '2025-04-19 14:11:35'], + ['14:11:35.123456', '2025-04-19 14:11:35.123456'], + ['12:11:35.000000', '2025-04-19 14:11:35 +02:00'], + ['12:11:35.123456', '2025-04-19 14:11:35.123456 +02:00'], + ['14:11:35.000000', '1745071895'], + ['14:11:35.123000', '1745071895.123'], + ['14:11:35.000000', 1745071895], + ['14:11:35.123000', 1745071895.123], + ['14:11:35.000000', 51095], + ['14:11:35.123456', 51095.123456], + ['14:11:35.123456', new DateTimeImmutable('14:11:35.123456')], + ['14:11:35.123456', new DateTime('14:11:35.123456')], + ['14:11:35.123456', new Stringable('14:11:35.123456')], + [$expression = new Expression("'14:11:35.123456'"), $expression], + ], + ], + 'timetz' => [ + new DateTimeColumn(ColumnType::TIMETZ, size: 0), + [ + [null, null], + [null, ''], + ['00:00:00+00:00', '2025-04-19'], + ['14:11:35+00:00', '14:11:35'], + ['14:11:35+00:00', '14:11:35.123456'], + ['14:11:35+02:00', '14:11:35 +02:00'], + ['14:11:35+02:00', '14:11:35.123456 +02:00'], + ['14:11:35+00:00', '2025-04-19 14:11:35'], + ['14:11:35+00:00', '2025-04-19 14:11:35.123456'], + ['14:11:35+02:00', '2025-04-19 14:11:35 +02:00'], + ['14:11:35+02:00', '2025-04-19 14:11:35.123456 +02:00'], + ['14:11:35+00:00', '1745071895'], + ['14:11:35+00:00', '1745071895.123'], + ['14:11:35+00:00', 1745071895], + ['14:11:35+00:00', 1745071895.123], + ['14:11:35+00:00', 51095], + ['14:11:35+00:00', 51095.123456], + ['14:11:35+02:00', new DateTimeImmutable('14:11:35 +02:00')], + ['14:11:35+02:00', new DateTime('14:11:35 +02:00')], + ['14:11:35+02:00', new Stringable('14:11:35 +02:00')], + [$expression = new Expression("'14:11:35 +02:00'"), $expression], + ], + ], + 'timetz6' => [ + new DateTimeColumn(ColumnType::TIMETZ, size: 6), + [ + [null, null], + [null, ''], + ['00:00:00.000000+00:00', '2025-04-19'], + ['14:11:35.000000+00:00', '14:11:35'], + ['14:11:35.123456+00:00', '14:11:35.123456'], + ['14:11:35.000000+02:00', '14:11:35 +02:00'], + ['14:11:35.123456+02:00', '14:11:35.123456 +02:00'], + ['14:11:35.000000+00:00', '2025-04-19 14:11:35'], + ['14:11:35.123456+00:00', '2025-04-19 14:11:35.123456'], + ['14:11:35.000000+02:00', '2025-04-19 14:11:35 +02:00'], + ['14:11:35.123456+02:00', '2025-04-19 14:11:35.123456 +02:00'], + ['14:11:35.000000+00:00', '1745071895'], + ['14:11:35.123000+00:00', '1745071895.123'], + ['14:11:35.000000+00:00', 1745071895], + ['14:11:35.123000+00:00', 1745071895.123], + ['14:11:35.000000+00:00', 51095], + ['14:11:35.123456+00:00', 51095.123456], + ['14:11:35.123456+02:00', new DateTimeImmutable('14:11:35.123456 +02:00')], + ['14:11:35.123456+02:00', new DateTime('14:11:35.123456 +02:00')], + ['14:11:35.123456+02:00', new Stringable('14:11:35.123456 +02:00')], + [$expression = new Expression("'14:11:35.123456 +02:00'"), $expression], + ], + ], + 'date' => [ + new DateTimeColumn(ColumnType::DATE), + [ + [null, null], + [null, ''], + ['2025-04-19', '2025-04-19'], + ['2025-04-19', '2025-04-19 14:11:35'], + ['2025-04-19', '2025-04-19 14:11:35.123456'], + ['2025-04-19', '2025-04-19 14:11:35 +02:00'], + ['2025-04-19', '2025-04-19 14:11:35.123456 +02:00'], + ['2025-04-19', '1745071895'], + ['2025-04-19', '1745071895.123'], + ['2025-04-19', 1745071895], + ['2025-04-19', 1745071895.123], + ['2025-04-19', new DateTimeImmutable('2025-04-19 14:11:35')], + ['2025-04-19', new DateTime('2025-04-19 14:11:35')], + ['2025-04-19', new Stringable('2025-04-19 14:11:35')], + [$expression = new Expression("'2025-04-19'"), $expression], + ], + ], 'json' => [ new JsonColumn(), [ @@ -201,6 +451,8 @@ public static function dbTypecastColumns(): array public static function phpTypecastColumns(): array { + $utcTimezone = new DateTimeZone('UTC'); + return [ 'integer' => [ new IntegerColumn(), @@ -266,6 +518,48 @@ public static function phpTypecastColumns(): array [false, "\0"], ], ], + 'timestamp' => [ + new DateTimeColumn(ColumnType::TIMESTAMP), + [ + [null, null], + [new DateTimeImmutable('2025-04-19 14:11:35.123456', $utcTimezone), '2025-04-19 14:11:35.123456'], + ], + ], + 'datetime' => [ + new DateTimeColumn(), + [ + [null, null], + [new DateTimeImmutable('2025-04-19 14:11:35.123456', $utcTimezone), '2025-04-19 14:11:35.123456'], + ], + ], + 'datetimetz' => [ + new DateTimeColumn(ColumnType::DATETIMETZ), + [ + [null, null], + [new DateTimeImmutable('2025-04-19 14:11:35.123456 +02:00'), '2025-04-19 14:11:35.123456+02:00'], + ], + ], + 'time' => [ + new DateTimeColumn(ColumnType::TIME), + [ + [null, null], + [new DateTimeImmutable('14:11:35.123456', $utcTimezone), '14:11:35.123456'], + ], + ], + 'timetz' => [ + new DateTimeColumn(ColumnType::TIMETZ), + [ + [null, null], + [new DateTimeImmutable('14:11:35.123456 +02:00'), '14:11:35.123456+02:00'], + ], + ], + 'date' => [ + new DateTimeColumn(ColumnType::DATE), + [ + [null, null], + [new DateTimeImmutable('2025-04-19', $utcTimezone), '2025-04-19'], + ], + ], 'json' => [ new JsonColumn(), [ @@ -405,6 +699,33 @@ public static function dbTypecastArrayColumns() ]], [[1, "\x10", $resource, null]]], ], ], + ColumnType::DATETIME => [ + new DateTimeColumn(), + [ + [1, [ + '2025-04-19 14:11:35', + '2025-04-19 14:11:35', + '2025-04-19 14:11:35', + null, + ], [ + '2025-04-19 14:11:35', + new DateTime('2025-04-19 14:11:35'), + new DateTimeImmutable('2025-04-19 14:11:35'), + null, + ]], + [2, [[ + '2025-04-19 14:11:35', + '2025-04-19 14:11:35', + '2025-04-19 14:11:35', + null, + ]], [[ + '2025-04-19 14:11:35', + new DateTime('2025-04-19 14:11:35'), + new DateTimeImmutable('2025-04-19 14:11:35'), + null, + ]]], + ], + ], ColumnType::JSON => [ new JsonColumn(), [ @@ -493,4 +814,85 @@ public static function construct(): array ['unsigned', false, 'isUnsigned', false], ]; } + + public static function dateTimeColumn(): array + { + return [ + 'date and time' => [ + '2025-04-19 14:11:35', + [ + 'timestamp' => '2025-04-19 14:11:35', + 'datetime' => '2025-04-19 14:11:35', + 'datetime3' => '2025-04-19 14:11:35', + 'datetimetz' => '2025-04-19 14:11:35', + 'datetimetz6' => '2025-04-19 14:11:35', + 'time' => '14:11:35', + 'time3' => '14:11:35', + 'timetz' => '14:11:35', + 'timetz6' => '14:11:35', + 'date' => '2025-04-19', + ], + ], + 'date, time and milliseconds' => [ + '2025-04-19 14:11:35.123456', + [ + 'timestamp' => '2025-04-19 14:11:35', + 'datetime' => '2025-04-19 14:11:35', + 'datetime3' => '2025-04-19 14:11:35.123', + 'datetimetz' => '2025-04-19 14:11:35', + 'datetimetz6' => '2025-04-19 14:11:35.123456', + 'time' => '14:11:35', + 'time3' => '14:11:35.123', + 'timetz' => '14:11:35', + 'timetz6' => '14:11:35.123456', + 'date' => '2025-04-19', + ], + ], + 'date, time, milliseconds and timezone' => [ + '2025-04-19 14:11:35.123456 +02:00', + [ + 'timestamp' => '2025-04-19 12:11:35', + 'datetime' => '2025-04-19 12:11:35', + 'datetime3' => '2025-04-19 12:11:35.123', + 'datetimetz' => '2025-04-19 14:11:35 +02:00', + 'datetimetz6' => '2025-04-19 14:11:35.123456 +02:00', + 'time' => '12:11:35', + 'time3' => '12:11:35.123', + 'timetz' => '14:11:35 +02:00', + 'timetz6' => '14:11:35.123456 +02:00', + 'date' => '2025-04-19', + ], + ], + 'integer' => [ + 1745071895, + [ + 'timestamp' => '2025-04-19 14:11:35', + 'datetime' => '2025-04-19 14:11:35', + 'datetime3' => '2025-04-19 14:11:35', + 'datetimetz' => '2025-04-19 14:11:35', + 'datetimetz6' => '2025-04-19 14:11:35', + 'time' => '14:11:35', + 'time3' => '14:11:35', + 'timetz' => '14:11:35', + 'timetz6' => '14:11:35', + 'date' => '2025-04-19', + ], + ], + 'float' => [ + 1745071895.123, + [ + 'timestamp' => '2025-04-19 14:11:35', + 'datetime' => '2025-04-19 14:11:35', + 'datetime3' => '2025-04-19 14:11:35.123', + 'datetimetz' => '2025-04-19 14:11:35', + 'datetimetz6' => '2025-04-19 14:11:35.123', + 'time' => '14:11:35', + 'time3' => '14:11:35.123', + 'timetz' => '14:11:35', + 'timetz6' => '14:11:35.123', + 'date' => '2025-04-19', + ], + ], + ]; + } } diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index d553c0d16..d46425d89 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -1675,17 +1675,23 @@ public static function buildColumnDefinition(): array 'binary()' => ['binary', ColumnBuilder::binary()], 'binary(1000)' => ['binary(1000)', ColumnBuilder::binary(1000)], 'uuid()' => ['uuid', ColumnBuilder::uuid()], - 'datetime()' => ['datetime(0)', ColumnBuilder::datetime()], - 'datetime(6)' => ['datetime(6)', ColumnBuilder::datetime(6)], - 'datetime(null)' => ['datetime', ColumnBuilder::datetime(null)], 'timestamp()' => ['timestamp(0)', ColumnBuilder::timestamp()], 'timestamp(6)' => ['timestamp(6)', ColumnBuilder::timestamp(6)], 'timestamp(null)' => ['timestamp', ColumnBuilder::timestamp(null)], - 'date()' => ['date', ColumnBuilder::date()], - 'date(100)' => ['date', ColumnBuilder::date()->size(100)], + 'datetime()' => ['datetime(0)', ColumnBuilder::datetime()], + 'datetime(6)' => ['datetime(6)', ColumnBuilder::datetime(6)], + 'datetime(null)' => ['datetime', ColumnBuilder::datetime(null)], + 'datetimeWithTimezone()' => ['datetimetz(0)', ColumnBuilder::datetimeWithTimezone()], + 'datetimeWithTimezone(6)' => ['datetimetz(6)', ColumnBuilder::datetimeWithTimezone(6)], + 'datetimeWithTimezone(null)' => ['datetimetz', ColumnBuilder::datetimeWithTimezone(null)], 'time()' => ['time(0)', ColumnBuilder::time()], 'time(6)' => ['time(6)', ColumnBuilder::time(6)], 'time(null)' => ['time', ColumnBuilder::time(null)], + 'timeWithTimezone()' => ['timetz(0)', ColumnBuilder::timeWithTimezone()], + 'timeWithTimezone(6)' => ['timetz(6)', ColumnBuilder::timeWithTimezone(6)], + 'timeWithTimezone(null)' => ['timetz', ColumnBuilder::timeWithTimezone(null)], + 'date()' => ['date', ColumnBuilder::date()], + 'date(100)' => ['date', ColumnBuilder::date()->size(100)], 'array()' => ['json', ColumnBuilder::array()], 'structured()' => ['json', ColumnBuilder::structured()], "structured('json')" => ['json', ColumnBuilder::structured('json')], diff --git a/tests/Support/Stub/ColumnDefinitionBuilder.php b/tests/Support/Stub/ColumnDefinitionBuilder.php index 14776e38a..918a21c04 100644 --- a/tests/Support/Stub/ColumnDefinitionBuilder.php +++ b/tests/Support/Stub/ColumnDefinitionBuilder.php @@ -25,9 +25,11 @@ final class ColumnDefinitionBuilder extends AbstractColumnDefinitionBuilder 'varchar', 'text', 'binary', - 'datetime', 'timestamp', + 'datetime', + 'datetimetz', 'time', + 'timetz', ]; protected const TYPES_WITH_SCALE = [ @@ -71,10 +73,12 @@ protected function getDbType(ColumnInterface $column): string ColumnType::TEXT => 'text', ColumnType::BINARY => 'binary', ColumnType::UUID => 'uuid', - ColumnType::DATETIME => 'datetime', ColumnType::TIMESTAMP => 'timestamp', - ColumnType::DATE => 'date', + ColumnType::DATETIME => 'datetime', + ColumnType::DATETIMETZ => 'datetimetz', ColumnType::TIME => 'time', + ColumnType::TIMETZ => 'timetz', + ColumnType::DATE => 'date', ColumnType::ARRAY => 'json', ColumnType::STRUCTURED => 'json', ColumnType::JSON => 'json',