Skip to content

Commit de9cd10

Browse files
committed
fix: preserve sub-second precision in BaseModel datetime timestamps
When a model's $dateFormat is 'datetime' and the connection's dateFormat['datetime'] mask includes .v/.u (millisecond/microsecond precision), auto-generated created_at/updated_at timestamps always rendered .000000 instead of the real sub-second value. setDate() converted the current Time instance to a Unix timestamp integer before formatting it, which discards anything below whole-second precision. timeToDate() already formats a Time object directly and retains sub-second precision, but nothing routed the no-explicit-date case through it. Fixes #10540
1 parent 94036f1 commit de9cd10

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

‎system/BaseModel.php‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,9 +1425,11 @@ protected function doProtectFieldsForInsert(array $row): array
14251425
*/
14261426
protected function setDate(?int $userDate = null)
14271427
{
1428-
$currentDate = $userDate ?? Time::now()->getTimestamp();
1428+
if ($userDate === null && $this->dateFormat === 'datetime') {
1429+
return $this->timeToDate(Time::now());
1430+
}
14291431

1430-
return $this->intToDate($currentDate);
1432+
return $this->intToDate($userDate ?? Time::now()->getTimestamp());
14311433
}
14321434

14331435
/**

‎tests/system/Models/GeneralModelTest.php‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515

1616
use CodeIgniter\Database\BaseConnection;
1717
use CodeIgniter\Exceptions\BadMethodCallException;
18+
use CodeIgniter\I18n\Time;
1819
use CodeIgniter\Model;
1920
use CodeIgniter\Test\CIUnitTestCase;
21+
use CodeIgniter\Test\Mock\MockConnection;
22+
use PHPUnit\Framework\Attributes\DataProvider;
2023
use PHPUnit\Framework\Attributes\Group;
2124
use Tests\Support\Models\JobModel;
2225
use Tests\Support\Models\UserModel;
@@ -39,6 +42,7 @@ protected function tearDown(): void
3942
{
4043
parent::tearDown();
4144
$this->resetServices();
45+
Time::setTestNow();
4246
}
4347

4448
/**
@@ -111,6 +115,28 @@ public function testSetAllowedFields(): void
111115
$this->assertSame($allowed2, $this->getPrivateProperty($model, 'allowedFields'));
112116
}
113117

118+
#[DataProvider('provideCurrentTimestampPreservesSubseconds')]
119+
public function testCurrentTimestampPreservesSubseconds(string $format, string $expected): void
120+
{
121+
Time::setTestNow('2024-07-09 09:13:34.654321');
122+
123+
$db = new MockConnection(['dateFormat' => ['datetime' => $format]]);
124+
$model = $this->createModel(UserModel::class, $db);
125+
$date = self::getPrivateMethodInvoker($model, 'setDate');
126+
127+
$this->assertSame($expected, $date());
128+
}
129+
130+
/**
131+
* @return iterable<string, array{string, string}>
132+
*/
133+
public static function provideCurrentTimestampPreservesSubseconds(): iterable
134+
{
135+
yield 'milliseconds' => ['Y-m-d H:i:s.v', '2024-07-09 09:13:34.654'];
136+
137+
yield 'microseconds' => ['Y-m-d H:i:s.u', '2024-07-09 09:13:34.654321'];
138+
}
139+
114140
public function testBuilderUsesModelTable(): void
115141
{
116142
$builder = $this->createModel(UserModel::class)->builder();

‎user_guide_src/source/changelogs/v4.7.5.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Deprecations
3636
Bugs Fixed
3737
**********
3838

39+
- **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.
3940
- **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.
4041
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.
4142
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.

0 commit comments

Comments
 (0)