diff --git a/system/BaseModel.php b/system/BaseModel.php index 64d083fe4d2b..ad79f6f42ec6 100644 --- a/system/BaseModel.php +++ b/system/BaseModel.php @@ -1425,9 +1425,11 @@ protected function doProtectFieldsForInsert(array $row): array */ protected function setDate(?int $userDate = null) { - $currentDate = $userDate ?? Time::now()->getTimestamp(); + if ($userDate === null && $this->dateFormat === 'datetime') { + return $this->timeToDate(Time::now()); + } - return $this->intToDate($currentDate); + return $this->intToDate($userDate ?? Time::now()->getTimestamp()); } /** diff --git a/tests/system/Models/GeneralModelTest.php b/tests/system/Models/GeneralModelTest.php index bd9f2d14109e..eb72f6aec5eb 100644 --- a/tests/system/Models/GeneralModelTest.php +++ b/tests/system/Models/GeneralModelTest.php @@ -15,8 +15,11 @@ use CodeIgniter\Database\BaseConnection; use CodeIgniter\Exceptions\BadMethodCallException; +use CodeIgniter\I18n\Time; use CodeIgniter\Model; use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\Mock\MockConnection; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use Tests\Support\Models\JobModel; use Tests\Support\Models\UserModel; @@ -39,6 +42,7 @@ protected function tearDown(): void { parent::tearDown(); $this->resetServices(); + Time::setTestNow(); } /** @@ -111,6 +115,28 @@ public function testSetAllowedFields(): void $this->assertSame($allowed2, $this->getPrivateProperty($model, 'allowedFields')); } + #[DataProvider('provideCurrentTimestampPreservesSubseconds')] + public function testCurrentTimestampPreservesSubseconds(string $format, string $expected): void + { + Time::setTestNow('2024-07-09 09:13:34.654321'); + + $db = new MockConnection(['dateFormat' => ['datetime' => $format]]); + $model = $this->createModel(UserModel::class, $db); + $date = self::getPrivateMethodInvoker($model, 'setDate'); + + $this->assertSame($expected, $date()); + } + + /** + * @return iterable + */ + public static function provideCurrentTimestampPreservesSubseconds(): iterable + { + yield 'milliseconds' => ['Y-m-d H:i:s.v', '2024-07-09 09:13:34.654']; + + yield 'microseconds' => ['Y-m-d H:i:s.u', '2024-07-09 09:13:34.654321']; + } + public function testBuilderUsesModelTable(): void { $builder = $this->createModel(UserModel::class)->builder(); diff --git a/user_guide_src/source/changelogs/v4.7.5.rst b/user_guide_src/source/changelogs/v4.7.5.rst index 1f10e03cdf21..83e0bf47742a 100644 --- a/user_guide_src/source/changelogs/v4.7.5.rst +++ b/user_guide_src/source/changelogs/v4.7.5.rst @@ -40,6 +40,7 @@ Bugs Fixed ********** - **Autoloader:** Fixed a bug where ``FileLocatorCached::deleteCache()`` left the deleted data in memory, so it could be written back to the cache file on shutdown. ``spark optimize`` and ``spark cache:clear`` now clear the shared locator's cache instead of a separate instance. +- **BaseModel:** Fixed a bug where auto-generated ``created_at``/``updated_at`` timestamps always rendered ``.000000`` for a ``'datetime'`` ``$dateFormat`` whose connection ``dateFormat`` mask includes ``.v``/``.u``, instead of the real sub-second value. - **CLI:** Fixed a bug where pressing backspace in a ``CLI::prompt()`` erased the prompt text when the ``readline`` extension is enabled. The prompt is now passed to ``readline()`` so line redraws repaint it. ANSI color codes in the prompt (e.g., option defaults) are wrapped in readline's non-printing markers under GNU readline so cursor positioning stays accurate. On Windows, where the ``readline`` extension is built on WinEditLine, the prompt is written to STDOUT first because WinEditLine reports no library version and prints ANSI sequences literally.